Sfoglia il codice sorgente

Merge branch 'Version3'

# Conflicts:
#	index.js
#	package-lock.json
#	package.json
Eugene Lazutkin 3 anni fa
parent
commit
a5d208dc2b
73 ha cambiato i file con 3011 aggiunte e 1976 eliminazioni
  1. 1 1
      .prettierrc
  2. 1 1
      LICENSE
  3. 0 22
      defs.js
  4. 0 203
      index.js
  5. 0 68
      package-lock.json
  6. 39 10
      package.json
  7. 133 0
      src/asStream.js
  8. 63 0
      src/defs.js
  9. 132 0
      src/fun.js
  10. 84 0
      src/gen.js
  11. 204 0
      src/index.js
  12. 12 0
      src/jsonl/parser.js
  13. 17 0
      src/jsonl/parserStream.js
  14. 48 0
      src/jsonl/stringerStream.js
  15. 23 0
      src/utils/batch.js
  16. 31 0
      src/utils/fixUtf8Stream.js
  17. 24 0
      src/utils/fold.js
  18. 24 0
      src/utils/lines.js
  19. 131 0
      src/utils/readableFrom.js
  20. 1 0
      src/utils/reduce.js
  21. 43 0
      src/utils/reduceStream.js
  22. 11 0
      src/utils/scan.js
  23. 7 0
      src/utils/skip.js
  24. 23 0
      src/utils/skipWhile.js
  25. 7 0
      src/utils/take.js
  26. 23 0
      src/utils/takeWhile.js
  27. 8 0
      src/utils/takeWithSkip.js
  28. BIN
      tests/data/sample.jsonl.gz
  29. 0 25
      tests/helpers.js
  30. 51 0
      tests/helpers.mjs
  31. 27 0
      tests/manual/asStreamTest.js
  32. 75 0
      tests/manual/streamEventsTest.js
  33. 99 0
      tests/test-asStream.mjs
  34. 29 0
      tests/test-batch.mjs
  35. 98 0
      tests/test-dataSource.mjs
  36. 87 0
      tests/test-demo.mjs
  37. 19 0
      tests/test-errors.mjs
  38. 115 0
      tests/test-fold.mjs
  39. 215 0
      tests/test-fun.mjs
  40. 235 0
      tests/test-gen.mjs
  41. 104 0
      tests/test-jsonl-parser.mjs
  42. 106 0
      tests/test-jsonl-parserStream.mjs
  43. 155 0
      tests/test-jsonl-stringerStream.mjs
  44. 86 0
      tests/test-readWrite.mjs
  45. 88 0
      tests/test-readableFrom.mjs
  46. 113 0
      tests/test-simple.mjs
  47. 44 0
      tests/test-skip.mjs
  48. 75 0
      tests/test-take.mjs
  49. 100 0
      tests/test-transducers.mjs
  50. 0 95
      tests/test_FromIterable.js
  51. 0 181
      tests/test_comp.js
  52. 0 53
      tests/test_demo.js
  53. 0 24
      tests/test_errors.js
  54. 0 82
      tests/test_fold.js
  55. 0 204
      tests/test_gen.js
  56. 0 96
      tests/test_readWrite.js
  57. 0 124
      tests/test_simple.js
  58. 0 46
      tests/test_skip.js
  59. 0 57
      tests/test_take.js
  60. 0 73
      tests/test_transducers.js
  61. 0 23
      tests/tests.js
  62. 0 94
      utils/FromIterable.js
  63. 0 40
      utils/Reduce.js
  64. 0 85
      utils/asFun.js
  65. 0 77
      utils/asGen.js
  66. 0 20
      utils/comp.js
  67. 0 43
      utils/fold.js
  68. 0 24
      utils/gen.js
  69. 0 41
      utils/scan.js
  70. 0 32
      utils/skip.js
  71. 0 46
      utils/skipWhile.js
  72. 0 39
      utils/take.js
  73. 0 47
      utils/takeWhile.js

+ 1 - 1
.prettierrc

@@ -1,5 +1,5 @@
 {
-  "printWidth": 160,
+  "printWidth": 100,
   "singleQuote": true,
   "bracketSpacing": false,
   "arrowParens": "avoid",

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright 2019 Eugene Lazutkin
+Copyright 2005-2022 Eugene Lazutkin
 
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 

+ 0 - 22
defs.js

@@ -1,22 +0,0 @@
-'use strict';
-
-const none = Symbol.for('object-stream.none');
-const finalSymbol = Symbol.for('object-stream.final');
-const manySymbol = Symbol.for('object-stream.many');
-
-const final = value => ({[finalSymbol]: value});
-const many = values => ({[manySymbol]: values});
-
-const isFinal = o => o && typeof o == 'object' && finalSymbol in o;
-const isMany = o => o && typeof o == 'object' && manySymbol in o;
-
-const getFinalValue = o => o[finalSymbol];
-const getManyValues = o => o[manySymbol];
-
-module.exports.none = none;
-module.exports.final = final;
-module.exports.isFinal = isFinal;
-module.exports.getFinalValue = getFinalValue;
-module.exports.many = many;
-module.exports.isMany = isMany;
-module.exports.getManyValues = getManyValues;

+ 0 - 203
index.js

@@ -1,203 +0,0 @@
-'use strict';
-
-const {Duplex, Transform} = require('stream');
-
-const none = Symbol.for('object-stream.none');
-const finalSymbol = Symbol.for('object-stream.final');
-const manySymbol = Symbol.for('object-stream.many');
-
-const final = value => ({[finalSymbol]: value});
-const many = values => ({[manySymbol]: values});
-
-const isFinal = o => o && typeof o == 'object' && finalSymbol in o;
-const isMany = o => o && typeof o == 'object' && manySymbol in o;
-
-const getFinalValue = o => o[finalSymbol];
-const getManyValues = o => o[manySymbol];
-
-const runAsyncGenerator = async (gen, stream) => {
-  for (;;) {
-    let data = gen.next();
-    if (data && typeof data.then == 'function') {
-      data = await data;
-    }
-    if (data.done) break;
-    let value = data.value;
-    if (value && typeof value.then == 'function') {
-      value = await value;
-    }
-    Chain.sanitize(value, stream);
-  }
-};
-
-const wrapFunction = fn =>
-  new Transform({
-    writableObjectMode: true,
-    readableObjectMode: true,
-    transform(chunk, encoding, callback) {
-      try {
-        const result = fn.call(this, chunk, encoding);
-        if (result && typeof result.then == 'function') {
-          // thenable
-          result.then(
-            result => (Chain.sanitize(result, this), callback(null)),
-            error => callback(error)
-          );
-          return;
-        }
-        if (result && typeof result.next == 'function') {
-          // generator
-          runAsyncGenerator(result, this).then(
-            () => callback(null),
-            error => callback(error)
-          );
-          return;
-        }
-        Chain.sanitize(result, this);
-        callback(null);
-      } catch (error) {
-        callback(error);
-      }
-    }
-  });
-
-const wrapArray = fns =>
-  new Transform({
-    writableObjectMode: true,
-    readableObjectMode: true,
-    transform(chunk, encoding, callback) {
-      try {
-        let value = chunk;
-        for (let i = 0; i < fns.length; ++i) {
-          const result = fns[i].call(this, value, encoding);
-          if (result === Chain.none) {
-            callback(null);
-            return;
-          }
-          if (Chain.isFinal(result)) {
-            value = Chain.getFinalValue(result);
-            break;
-          }
-          value = result;
-        }
-        Chain.sanitize(value, this);
-        callback(null);
-      } catch (error) {
-        callback(error);
-      }
-    }
-  });
-
-// is*NodeStream functions taken from https://github.com/nodejs/node/blob/master/lib/internal/streams/utils.js
-const isReadableNodeStream = obj =>
-  obj &&
-  typeof obj.pipe === 'function' &&
-  typeof obj.on === 'function' &&
-  (!obj._writableState || (typeof obj._readableState === 'object' ? obj._readableState.readable : null) !== false) && // Duplex
-  (!obj._writableState || obj._readableState); // Writable has .pipe.
-
-const isWritableNodeStream = obj =>
-  obj &&
-  typeof obj.write === 'function' &&
-  typeof obj.on === 'function' &&
-  (!obj._readableState || (typeof obj._writableState === 'object' ? obj._writableState.writable : null) !== false); // Duplex
-
-const isDuplexNodeStream = obj =>
-  obj && typeof obj.pipe === 'function' && obj._readableState && typeof obj.on === 'function' && typeof obj.write === 'function';
-
-class Chain extends Duplex {
-  constructor(fns, options) {
-    super(options || {writableObjectMode: true, readableObjectMode: true});
-
-    if (!(fns instanceof Array) || !fns.length) {
-      throw Error("Chain's argument should be a non-empty array.");
-    }
-
-    this.streams = fns
-      .filter(fn => fn)
-      .map((fn, index, fns) => {
-        if (typeof fn === 'function' || fn instanceof Array) return Chain.convertToTransform(fn);
-        if (isDuplexNodeStream(fn) || (!index && isReadableNodeStream(fn)) || (index === fns.length - 1 && isWritableNodeStream(fn))) {
-          return fn;
-        }
-        throw Error('Arguments should be functions, arrays or streams.');
-      })
-      .filter(s => s);
-    this.input = this.streams[0];
-    this.output = this.streams.reduce((output, stream) => (output && output.pipe(stream)) || stream);
-
-    if (!isWritableNodeStream(this.input)) {
-      this._write = (_1, _2, callback) => callback(null);
-      this._final = callback => callback(null); // unavailable in Node 6
-      this.input.on('end', () => this.end());
-    }
-
-    if (isReadableNodeStream(this.output)) {
-      this.output.on('data', chunk => !this.push(chunk) && this.output.pause());
-      this.output.on('end', () => this.push(null));
-    } else {
-      this._read = () => {}; // nop
-      this.resume();
-      this.output.on('finish', () => this.push(null));
-    }
-
-    // connect events
-    if (!options || !options.skipEvents) {
-      this.streams.forEach(stream => stream.on('error', error => this.emit('error', error)));
-    }
-  }
-  _write(chunk, encoding, callback) {
-    let error = null;
-    try {
-      this.input.write(chunk, encoding, e => callback(e || error));
-    } catch (e) {
-      error = e;
-    }
-  }
-  _final(callback) {
-    let error = null;
-    try {
-      this.input.end(null, null, e => callback(e || error));
-    } catch (e) {
-      error = e;
-    }
-  }
-  _read() {
-    this.output.resume();
-  }
-  static make(fns, options) {
-    return new Chain(fns, options);
-  }
-  static sanitize(result, stream) {
-    if (Chain.isFinal(result)) {
-      result = Chain.getFinalValue(result);
-    } else if (Chain.isMany(result)) {
-      result = Chain.getManyValues(result);
-    }
-    if (result !== undefined && result !== null && result !== Chain.none) {
-      if (result instanceof Array) {
-        result.forEach(value => value !== undefined && value !== null && stream.push(value));
-      } else {
-        stream.push(result);
-      }
-    }
-  }
-  static convertToTransform(fn) {
-    if (typeof fn === 'function') return wrapFunction(fn);
-    if (fn instanceof Array) return fn.length ? wrapArray(fn) : null;
-    return null;
-  }
-}
-
-Chain.none = none;
-Chain.final = final;
-Chain.isFinal = isFinal;
-Chain.getFinalValue = getFinalValue;
-Chain.many = many;
-Chain.isMany = isMany;
-Chain.getManyValues = getManyValues;
-
-Chain.chain = Chain.make;
-Chain.make.Constructor = Chain;
-
-module.exports = Chain;

+ 0 - 68
package-lock.json

@@ -1,68 +0,0 @@
-{
-  "name": "stream-chain",
-  "version": "2.2.5",
-  "lockfileVersion": 2,
-  "requires": true,
-  "packages": {
-    "": {
-      "name": "stream-chain",
-      "version": "2.2.5",
-      "license": "BSD-3-Clause",
-      "devDependencies": {
-        "heya-unit": "^0.3.0"
-      }
-    },
-    "node_modules/heya-ice": {
-      "version": "0.1.11",
-      "resolved": "https://registry.npmjs.org/heya-ice/-/heya-ice-0.1.11.tgz",
-      "integrity": "sha1-XW2lnGC1nHAjqDRw+26XcddwWEk=",
-      "dev": true
-    },
-    "node_modules/heya-unify": {
-      "version": "0.2.7",
-      "resolved": "https://registry.npmjs.org/heya-unify/-/heya-unify-0.2.7.tgz",
-      "integrity": "sha512-d/4NacYl52tt4ofbP7gz+YmbjLrI2jkrRxSSd1a26yXfRS1vQxmZkZ6L+O1xUsgDSwx4HCDWR5U+ZFykdoHVig==",
-      "dev": true,
-      "dependencies": {
-        "heya-ice": "^0.1.11"
-      }
-    },
-    "node_modules/heya-unit": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/heya-unit/-/heya-unit-0.3.0.tgz",
-      "integrity": "sha1-eXR4IIyBnUxbf+NWrEwbhO67ubc=",
-      "dev": true,
-      "dependencies": {
-        "heya-ice": ">=0.1",
-        "heya-unify": ">=0.2"
-      }
-    }
-  },
-  "dependencies": {
-    "heya-ice": {
-      "version": "0.1.11",
-      "resolved": "https://registry.npmjs.org/heya-ice/-/heya-ice-0.1.11.tgz",
-      "integrity": "sha1-XW2lnGC1nHAjqDRw+26XcddwWEk=",
-      "dev": true
-    },
-    "heya-unify": {
-      "version": "0.2.7",
-      "resolved": "https://registry.npmjs.org/heya-unify/-/heya-unify-0.2.7.tgz",
-      "integrity": "sha512-d/4NacYl52tt4ofbP7gz+YmbjLrI2jkrRxSSd1a26yXfRS1vQxmZkZ6L+O1xUsgDSwx4HCDWR5U+ZFykdoHVig==",
-      "dev": true,
-      "requires": {
-        "heya-ice": "^0.1.11"
-      }
-    },
-    "heya-unit": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/heya-unit/-/heya-unit-0.3.0.tgz",
-      "integrity": "sha1-eXR4IIyBnUxbf+NWrEwbhO67ubc=",
-      "dev": true,
-      "requires": {
-        "heya-ice": ">=0.1",
-        "heya-unify": ">=0.2"
-      }
-    }
-  }
-}

+ 39 - 10
package.json

@@ -1,11 +1,36 @@
 {
   "name": "stream-chain",
-  "version": "2.2.5",
+  "version": "3.0.0",
   "description": "Chain functions as transform streams.",
-  "main": "index.js",
+  "type": "commonjs",
+  "main": "src/index.js",
+  "exports": {
+    ".": "./src/index.js",
+    "./*.js": "./src/*.js",
+    "./utils/*": "./src/utils/*.js",
+    "./asStream.js": "./src/asStream.js",
+    "./defs.js": "./src/defs.js",
+    "./fun.js": "./src/fun.js",
+    "./gen.js": "./src/gen.js",
+    "./jsonl/parser.js": "./src/jsonl/parser.js",
+    "./jsonl/stringer.js": "./src/jsonl/stringer.js",
+    "./utils/batch.js": "./src/utils/batch.js",
+    "./utils/fixUtf8Stream.js": "./src/utils/fixUtf8Stream.js",
+    "./utils/fold.js": "./src/utils/fold.js",
+    "./utils/fromIterable.js": "./src/utils/fromIterable.js",
+    "./utils/lines.js": "./src/utils/lines.js",
+    "./utils/reduce.js": "./src/utils/reduce.js",
+    "./utils/reduceStream.js": "./src/utils/reduceStream.js",
+    "./utils/scan.js": "./src/utils/scan.js",
+    "./utils/skip.js": "./src/utils/skip.js",
+    "./utils/skipWhile.js": "./src/utils/skipWhile.js",
+    "./utils/take.js": "./src/utils/take.js",
+    "./utils/takeWhile.js": "./src/utils/takeWhile.js",
+    "./utils/takeWithSkip.js": "./src/utils/takeWithSkip.js"
+  },
   "scripts": {
     "debug": "node --inspect-brk tests/tests.js",
-    "test": "node tests/tests.js"
+    "test": "tape6 --flags FO"
   },
   "repository": {
     "type": "git",
@@ -15,17 +40,21 @@
     "stream",
     "chain"
   ],
-  "author": "Eugene Lazutkin <eugene.lazutkin@gmail.com> (http://lazutkin.com/)",
+  "author": "Eugene Lazutkin <eugene.lazutkin@gmail.com> (https://www.lazutkin.com/)",
   "license": "BSD-3-Clause",
   "bugs": {
     "url": "https://github.com/uhop/stream-chain/issues"
   },
   "homepage": "https://github.com/uhop/stream-chain#readme",
-  "devDependencies": {
-    "heya-unit": "^0.3.0"
-  },
   "files": [
-    "/*.js",
-    "/utils"
-  ]
+    "src"
+  ],
+  "tape6": {
+    "tests": [
+      "/tests/test-*.mjs"
+    ]
+  },
+  "devDependencies": {
+    "tape-six": "^0.9.3"
+  }
 }

+ 133 - 0
src/asStream.js

@@ -0,0 +1,133 @@
+'use strict';
+
+const {Duplex} = require('stream');
+const defs = require('./defs');
+
+const asStream = (fn, options) => {
+  if (typeof fn != 'function')
+    throw TypeError(
+      'Only a function is accepted as the first argument'
+    );
+
+  // pump variables
+  let paused = Promise.resolve(),
+    resolvePaused = null;
+  const queue = [];
+
+  // pause/resume
+  const resume = () => {
+    if (!resolvePaused) return;
+    resolvePaused();
+    resolvePaused = null;
+    paused = Promise.resolve();
+  };
+  const pause = () => {
+    if (resolvePaused) return;
+    paused = new Promise(resolve => (resolvePaused = resolve));
+  };
+
+  let stream = null; // will be assigned later
+
+  // data processing
+  const pushResults = values => {
+    if (values && typeof values.next == 'function') {
+      // generator
+      queue.push(values);
+      return;
+    }
+    // array
+    queue.push(values[Symbol.iterator]());
+  };
+  const pump = async () => {
+    while (queue.length) {
+      await paused;
+      const gen = queue[queue.length - 1];
+      let result = gen.next();
+      if (result && typeof result.then == 'function') {
+        result = await result;
+      }
+      if (result.done) {
+        queue.pop();
+        continue;
+      }
+      let value = result.value;
+      if (value && typeof value.then == 'function') {
+        value = await value;
+      }
+      await sanitize(value);
+    }
+  };
+  const sanitize = async value => {
+    if (value === undefined || value === null || value === defs.none) return;
+    if (value === defs.stop) throw new defs.Stop();
+
+    if (defs.isMany(value)) {
+      pushResults(defs.getManyValues(value));
+      return pump();
+    }
+
+    if (defs.isFinalValue(value)) {
+      // a final value is not supported, it is treated as a regular value
+      value = defs.getFinalValue(value);
+      return processValue(value);
+    }
+
+    if (!stream.push(value)) {
+      pause();
+    }
+  };
+  const processChunk = async (chunk, encoding) => {
+    try {
+      const value = fn(chunk, encoding);
+      await processValue(value);
+    } catch (error) {
+      if (error instanceof defs.Stop) {
+        stream.push(null);
+        stream.destroy();
+        return;
+      }
+      throw error;
+    }
+  };
+  const processValue = async value => {
+    if (value && typeof value.then == 'function') {
+      // thenable
+      return value.then(value => processValue(value));
+    }
+    if (value && typeof value.next == 'function') {
+      // generator
+      pushResults(value);
+      return pump();
+    }
+    return sanitize(value);
+  };
+
+  stream = new Duplex(
+    Object.assign({writableObjectMode: true, readableObjectMode: true}, options, {
+      write(chunk, encoding, callback) {
+        processChunk(chunk, encoding).then(
+          () => callback(null),
+          error => callback(error)
+        );
+      },
+      final(callback) {
+        if (!defs.isFlushable(fn)) {
+          stream.push(null);
+          callback(null);
+          return;
+        }
+        processChunk(defs.none, null).then(
+          () => (stream.push(null), callback(null)),
+          error => callback(error)
+        );
+      },
+      read() {
+        resume();
+      }
+    })
+  );
+
+  return stream;
+};
+
+module.exports = asStream;

+ 63 - 0
src/defs.js

@@ -0,0 +1,63 @@
+'use strict';
+
+const none = Symbol.for('object-stream.none');
+const stop = Symbol.for('object-stream.stop');
+
+const finalSymbol = Symbol.for('object-stream.final');
+const manySymbol = Symbol.for('object-stream.many');
+const flushSymbol = Symbol.for('object-stream.flush');
+const fListSymbol = Symbol.for('object-stream.fList');
+
+const finalValue = value => ({[finalSymbol]: 1, value});
+const many = values => ({[manySymbol]: 1, values});
+
+const isFinalValue = o => o && o[finalSymbol] === 1;
+const isMany = o => o && o[manySymbol] === 1;
+const isFlushable = o => o && o[flushSymbol] === 1;
+const isFunctionList = o => o && o[fListSymbol] === 1;
+
+const getFinalValue = o => o.value;
+const getManyValues = o => o.values;
+const getFunctionList = o => o.fList;
+
+const flushable = (write, final = null) => {
+  const fn = final ? value => (value === none ? final() : write(value)) : write;
+  fn[flushSymbol] = 1;
+  return fn;
+};
+
+const setFunctionList = (o, fns) => {
+  o.fList = fns;
+  o[fListSymbol] = 1;
+  return o;
+}
+
+class Stop extends Error {}
+
+// old aliases
+const final = finalValue;
+
+module.exports.none = none;
+module.exports.stop = stop;
+module.exports.Stop = Stop;
+
+module.exports.finalSymbol = finalSymbol;
+module.exports.finalValue = finalValue;
+module.exports.final = final;
+module.exports.isFinalValue = isFinalValue;
+module.exports.getFinalValue = getFinalValue;
+
+module.exports.manySymbol = manySymbol;
+module.exports.many = many;
+module.exports.isMany = isMany;
+module.exports.getManyValues = getManyValues;
+module.exports.getFunctionList = getFunctionList;
+
+module.exports.flushSymbol = flushSymbol;
+module.exports.flushable = flushable;
+module.exports.isFlushable = isFlushable;
+
+module.exports.fListSymbol = fListSymbol;
+module.exports.isFunctionList = isFunctionList;
+module.exports.getFunctionList = getFunctionList;
+module.exports.setFunctionList = setFunctionList;

+ 132 - 0
src/fun.js

@@ -0,0 +1,132 @@
+'use strict';
+
+const defs = require('./defs');
+
+const next = async (value, fns, index, collect) => {
+  let cleanIndex;
+  try {
+    for (let i = index; i <= fns.length; ++i) {
+      if (value && typeof value.then == 'function') {
+        // thenable
+        value = await value;
+      }
+      if (value === defs.none) break;
+      if (value === defs.stop) {
+        cleanIndex = i - 1;
+        throw new defs.Stop();
+      }
+      if (defs.isFinalValue(value)) {
+        collect(defs.getFinalValue(value));
+        break;
+      }
+      if (defs.isMany(value)) {
+        const values = defs.getManyValues(value);
+        if (i == fns.length) {
+          values.forEach(val => collect(val));
+        } else {
+          for (let j = 0; j < values.length; ++j) {
+            await next(values[j], fns, i, collect);
+          }
+        }
+        break;
+      }
+      if (value && typeof value.next == 'function') {
+        // generator
+        for (;;) {
+          let data = value.next();
+          if (data && typeof data.then == 'function') {
+            data = await data;
+          }
+          if (data.done) break;
+          if (i == fns.length) {
+            collect(data.value);
+          } else {
+            await next(data.value, fns, i, collect);
+          }
+        }
+        break;
+      }
+      if (i == fns.length) {
+        collect(value);
+        break;
+      }
+      cleanIndex = i + 1;
+      const f = fns[i];
+      value = f(value);
+    }
+  } catch (error) {
+    if (error instanceof defs.Stop) {
+      await flush(fns, cleanIndex, collect);
+    }
+    throw error;
+  }
+};
+
+const flush = async (fns, index, collect) => {
+  for (let i = index; i < fns.length; ++i) {
+    const f = fns[i];
+    if (defs.isFlushable(f)) {
+      await next(f(defs.none), fns, i + 1, collect);
+    }
+  }
+};
+
+const collect = (collect, fns) => {
+  fns = fns
+    .filter(fn => fn)
+    .flat(Infinity)
+    .map(fn => (defs.isFunctionList(fn) ? defs.getFunctionList(fn) : fn))
+    .flat(Infinity);
+  if (!fns.length) {
+    fns = [x => x];
+  }
+  let flushed = false;
+  let g = async value => {
+    if (flushed) throw Error('Call to a flushed pipe.');
+    if (value !== defs.none) {
+      await next(value, fns, 0, collect);
+    } else {
+      flushed = true;
+      await flush(fns, 0, collect);
+    }
+  };
+  const needToFlush = fns.some(fn => defs.isFlushable(fn));
+  if (needToFlush) g = defs.flushable(g);
+  return defs.setFunctionList(g, fns);
+};
+
+const asArray = (...fns) => {
+  let results = null;
+  const f = collect(value => results.push(value), fns);
+  let g = async value => {
+    results = [];
+    await f(value);
+    const r = results;
+    results = null;
+    return r;
+  };
+  if (defs.isFlushable(f)) g = defs.flushable(g);
+  return defs.setFunctionList(g, defs.getFunctionList(f));
+};
+
+const fun = (...fns) => {
+  const f = asArray(...fns);
+  let g = value =>
+    f(value).then(results => {
+      switch (results.length) {
+        case 0:
+          return defs.none;
+        case 1:
+          return results[0];
+      }
+      return {[defs.manySymbol]: 1, values: results};
+    });
+  if (defs.isFlushable(f)) g = defs.flushable(g);
+  return defs.setFunctionList(g, defs.getFunctionList(f));
+};
+
+module.exports = fun;
+
+module.exports.next = next;
+module.exports.collect = collect;
+module.exports.asArray = asArray;

+ 84 - 0
src/gen.js

@@ -0,0 +1,84 @@
+'use strict';
+
+const defs = require('./defs');
+
+const next = async function* (value, fns, index) {
+  for (let i = index; i <= fns.length; ++i) {
+    if (value && typeof value.then == 'function') {
+      // thenable
+      value = await value;
+    }
+    if (value === defs.none) break;
+    if (value === defs.stop) throw new defs.Stop();
+    if (defs.isFinalValue(value)) {
+      yield defs.getFinalValue(value);
+      break;
+    }
+    if (defs.isMany(value)) {
+      const values = defs.getManyValues(value);
+      if (i == fns.length) {
+        yield* values;
+      } else {
+        for (let j = 0; j < values.length; ++j) {
+          yield* next(values[j], fns, i);
+        }
+      }
+      break;
+    }
+    if (value && typeof value.next == 'function') {
+      // generator
+      for (;;) {
+        let data = value.next();
+        if (data && typeof data.then == 'function') {
+          data = await data;
+        }
+        if (data.done) break;
+        if (i == fns.length) {
+          yield data.value;
+        } else {
+          yield* next(data.value, fns, i);
+        }
+      }
+      break;
+    }
+    if (i == fns.length) {
+      yield value;
+      break;
+    }
+    const f = fns[i];
+    value = f(value);
+  }
+};
+
+const gen = (...fns) => {
+  fns = fns
+    .filter(fn => fn)
+    .flat(Infinity)
+    .map(fn => (defs.isFunctionList(fn) ? defs.getFunctionList(fn) : fn))
+    .flat(Infinity);
+  if (!fns.length) {
+    fns = [x => x];
+  }
+  let flushed = false;
+  let g = async function* (value) {
+    if (flushed) throw Error('Call to a flushed pipe.');
+    if (value !== defs.none) {
+      yield* next(value, fns, 0);
+    } else {
+      flushed = true;
+      for (let i = 0; i < fns.length; ++i) {
+        const f = fns[i];
+        if (defs.isFlushable(f)) {
+          yield* next(f(defs.none), fns, i + 1);
+        }
+      }
+    }
+  };
+  const needToFlush = fns.some(fn => defs.isFlushable(fn));
+  if (needToFlush) g = defs.flushable(g);
+  return defs.setFunctionList(g, fns);
+};
+
+module.exports = gen;
+
+module.exports.next = next;

+ 204 - 0
src/index.js

@@ -0,0 +1,204 @@
+'use strict';
+
+const {Duplex} = require('stream');
+const defs = require('./defs');
+const gen = require('./gen');
+const asStream = require('./asStream');
+
+// is*NodeStream functions taken from https://github.com/nodejs/node/blob/master/lib/internal/streams/utils.js
+const isReadableNodeStream = obj =>
+  obj &&
+  typeof obj.pipe === 'function' &&
+  typeof obj.on === 'function' &&
+  (!obj._writableState ||
+    (typeof obj._readableState === 'object' ? obj._readableState.readable : null) !== false) && // Duplex
+  (!obj._writableState || obj._readableState); // Writable has .pipe.
+
+const isWritableNodeStream = obj =>
+  obj &&
+  typeof obj.write === 'function' &&
+  typeof obj.on === 'function' &&
+  (!obj._readableState ||
+    (typeof obj._writableState === 'object' ? obj._writableState.writable : null) !== false); // Duplex
+
+const isDuplexNodeStream = obj =>
+  obj &&
+  typeof obj.pipe === 'function' &&
+  obj._readableState &&
+  typeof obj.on === 'function' &&
+  typeof obj.write === 'function';
+
+const groupFunctions = (output, fn, index, fns) => {
+  if (
+    isDuplexNodeStream(fn) ||
+    (!index && isReadableNodeStream(fn)) ||
+    (index === fns.length - 1 && isWritableNodeStream(fn))
+  ) {
+    output.push(fn);
+    return output;
+  }
+  if (typeof fn != 'function')
+    throw TypeError('Item #' + index + ' is not a proper stream, nor a function.');
+  if (!output.length) output.push([]);
+  const last = output[output.length - 1];
+  if (Array.isArray(last)) {
+    last.push(fn);
+  } else {
+    output.push([fn]);
+  }
+  return output;
+};
+
+const produceStreams = item => {
+  if (Array.isArray(item)) {
+    if (!item.length) return null;
+    if (item.length == 1) return item[0] && chain.asStream(item[0]);
+    return chain.asStream(chain.gen(...item));
+  }
+  return item;
+};
+
+const wrapFunctions = (fn, index, fns) => {
+  if (
+    isDuplexNodeStream(fn) ||
+    (!index && isReadableNodeStream(fn)) ||
+    (index === fns.length - 1 && isWritableNodeStream(fn))
+  ) {
+    return fn; // an acceptable stream
+  }
+  if (typeof fn == 'function') return chain.asStream(fn); // a function
+  throw TypeError('Item #' + index + ' is not a proper stream, nor a function.');
+};
+
+// default implementation of required stream methods
+
+const write = (input, chunk, encoding, callback) => {
+  let error = null;
+  try {
+    input.write(chunk, encoding, e => callback(e || error));
+  } catch (e) {
+    error = e;
+  }
+};
+
+const final = (input, callback) => {
+  let error = null;
+  try {
+    input.end(null, null, e => callback(e || error));
+  } catch (e) {
+    error = e;
+  }
+};
+
+const read = output => {
+  output.resume();
+};
+
+// the chain creator
+
+const chain = (fns, options) => {
+  if (!Array.isArray(fns) || !fns.length) {
+    throw TypeError("Chain's first argument should be a non-empty array.");
+  }
+
+  fns = fns.filter(fn => fn).flat(Infinity);
+
+  const streams = (
+      options && options.noGrouping
+        ? fns.map(wrapFunctions)
+        : fns
+            .map(fn => (defs.isFunctionList(fn) ? defs.getFunctionList(fn) : fn))
+            .flat(Infinity)
+            .reduce(groupFunctions, [])
+            .map(produceStreams)
+    ).filter(s => s),
+    input = streams[0],
+    output = streams.reduce((output, item) => (output && output.pipe(item)) || item);
+
+  let stream = null; // will be assigned later
+
+  let writeMethod = (chunk, encoding, callback) => write(input, chunk, encoding, callback),
+    finalMethod = callback => final(input, callback),
+    readMethod = () => read(output);
+
+  if (!isWritableNodeStream(input)) {
+    writeMethod = (_1, _2, callback) => callback(null);
+    finalMethod = callback => callback(null);
+    input.on('end', () => stream.end());
+  }
+
+  if (isReadableNodeStream(output)) {
+    output.on('data', chunk => !stream.push(chunk) && output.pause());
+    output.on('end', () => stream.push(null));
+  } else {
+    readMethod = () => {}; // nop
+    output.on('finish', () => stream.push(null));
+  }
+
+  stream = new Duplex(
+    Object.assign({writableObjectMode: true, readableObjectMode: true}, options, {
+      readable: isReadableNodeStream(output),
+      writable: isWritableNodeStream(input),
+      write: writeMethod,
+      final: finalMethod,
+      read: readMethod
+    })
+  );
+  stream.streams = streams;
+  stream.input = input;
+  stream.output = output;
+
+  if (!isReadableNodeStream(output)) {
+    stream.resume();
+  }
+
+  // connect events
+  if (!options || !options.skipEvents) {
+    streams.forEach(item => item.on('error', error => stream.emit('error', error)));
+  }
+
+  return stream;
+};
+
+const dataSource = fn => {
+  if (typeof fn == 'function') return fn;
+  if (fn) {
+    if (typeof fn[Symbol.asyncIterator] == 'function') return fn[Symbol.asyncIterator].bind(fn);
+    if (typeof fn[Symbol.iterator] == 'function') return fn[Symbol.iterator].bind(fn);
+  }
+  throw new TypeError('The argument should be a function or an iterable object.');
+};
+
+module.exports = chain;
+
+// from defs.js
+module.exports.none = defs.none;
+module.exports.stop = defs.stop;
+module.exports.Stop = defs.Stop;
+
+module.exports.finalSymbol = defs.finalSymbol;
+module.exports.finalValue = defs.finalValue;
+module.exports.final = defs.final;
+module.exports.isFinalValue = defs.isFinalValue;
+module.exports.getFinalValue = defs.getFinalValue;
+
+module.exports.manySymbol = defs.manySymbol;
+module.exports.many = defs.many;
+module.exports.isMany = defs.isMany;
+module.exports.getManyValues = defs.getManyValues;
+module.exports.getFunctionList = defs.getFunctionList;
+
+module.exports.flushSymbol = defs.flushSymbol;
+module.exports.flushable = defs.flushable;
+module.exports.isFlushable = defs.isFlushable;
+
+module.exports.fListSymbol = defs.fListSymbol;
+module.exports.isFunctionList = defs.isFunctionList;
+module.exports.getFunctionList = defs.getFunctionList;
+module.exports.setFunctionList = defs.setFunctionList;
+
+module.exports.chain = chain; // for compatibility with 2.x
+module.exports.gen = gen;
+module.exports.asStream = asStream;
+
+module.exports.dataSource = dataSource;

+ 12 - 0
src/jsonl/parser.js

@@ -0,0 +1,12 @@
+'use strict';
+
+const gen = require('../gen.js');
+const fixUtf8Stream = require('../utils/fixUtf8Stream');
+const lines = require('../utils/lines');
+
+const parser = reviver => {
+  let counter = 0;
+  return gen(fixUtf8Stream(), lines(), string => ({key: counter++, value: JSON.parse(string, reviver)}));
+};
+
+module.exports = parser;

+ 17 - 0
src/jsonl/parserStream.js

@@ -0,0 +1,17 @@
+'use strict';
+
+const gen = require('../gen');
+const asStream = require('../asStream');
+const fixUtf8Stream = require('../utils/fixUtf8Stream');
+const lines = require('../utils/lines');
+
+const parserStream = options => {
+  const reviver = options && options.reviver;
+  let counter = 0;
+  return asStream(
+    gen(fixUtf8Stream(), lines(), string => ({key: counter++, value: JSON.parse(string, reviver)})),
+    Object.assign({writableObjectMode: false, readableObjectMode: true}, options)
+  );
+};
+
+module.exports = parserStream;

+ 48 - 0
src/jsonl/stringerStream.js

@@ -0,0 +1,48 @@
+'use strict';
+
+const {Transform} = require('stream');
+
+const stringer = options => {
+  let first = true,
+    prefix = '',
+    suffix = '',
+    separator = '\n',
+    emptyValue,
+    replacer,
+    space;
+  if (options) {
+    if (typeof options.prefix == 'string') prefix = options.prefix;
+    if (typeof options.suffix == 'string') suffix = options.suffix;
+    if (typeof options.separator == 'string') separator = options.separator;
+    if (typeof options.emptyValue == 'string') emptyValue = options.emptyValue;
+    replacer = options.replacer;
+    space = options.space;
+  }
+  return new Transform(
+    Object.assign({writableObjectMode: true}, options, {
+      transform(value, _, callback) {
+        let result = JSON.stringify(value, replacer, space);
+        if (first) {
+          first = false;
+          result = prefix + result;
+        } else {
+          result = separator + result;
+        }
+        this.push(result);
+        callback(null);
+      },
+      flush(callback) {
+        let output;
+        if (first) {
+          output = typeof emptyValue == 'string' ? emptyValue : prefix + suffix;
+        } else {
+          output = suffix;
+        }
+        output && this.push(output);
+        callback(null);
+      }
+    })
+  );
+};
+
+module.exports = stringer;

+ 23 - 0
src/utils/batch.js

@@ -0,0 +1,23 @@
+'use strict';
+
+const {none, flushable} = require('../defs');
+
+const batch = (n = 100) => {
+  let buffer = [];
+  return flushable(value => {
+    if (value === none) {
+      // clean up buffer
+      if (!buffer.length) return none;
+      const result = buffer;
+      buffer = null;
+      return result;
+    }
+    buffer.push(value);
+    if (buffer.length < n) return none;
+    const result = buffer;
+    buffer = [];
+    return result;
+  });
+};
+
+module.exports = batch;

+ 31 - 0
src/utils/fixUtf8Stream.js

@@ -0,0 +1,31 @@
+'use strict';
+
+const {StringDecoder} = require('string_decoder');
+
+const {none, flushable} = require('../defs');
+
+const fixUtf8Stream = () => {
+  const stringDecoder = new StringDecoder();
+  let input = '';
+  return flushable(chunk => {
+    if (chunk === none) {
+      const result = input + stringDecoder.end();
+      input = '';
+      return result;
+    }
+    if (typeof chunk == 'string') {
+      if (!input) return chunk;
+      const result = input + chunk;
+      input = '';
+      return result;
+    }
+    if (chunk instanceof Buffer) {
+      const result = input + stringDecoder.write(chunk);
+      input = '';
+      return result;
+    }
+    throw new TypeError('Expected a string or a Buffer');
+  });
+};
+
+module.exports = fixUtf8Stream;

+ 24 - 0
src/utils/fold.js

@@ -0,0 +1,24 @@
+'use strict';
+
+const {none, flushable} = require('../defs');
+
+const fold = (f, acc) =>
+  flushable(value => {
+    if (value === none) {
+      // clean up acc
+      const result = acc;
+      acc = null;
+      return result;
+    }
+    const result = f(acc, value);
+    if (result && typeof result.then == 'function') {
+      return result.then(result => {
+        acc = result;
+        return none;
+      });
+    }
+    acc = result;
+    return none;
+  });
+
+module.exports = fold;

+ 24 - 0
src/utils/lines.js

@@ -0,0 +1,24 @@
+'use strict';
+
+const {none, flushable} = require('../defs');
+
+const lines = () => {
+  let rest = '';
+  return flushable(function* (value) {
+    if (value === none) {
+      if (!rest) return;
+      const result = rest;
+      rest = '';
+      yield result;
+      return;
+    }
+    const lines = value.split('\n');
+    rest += lines[0];
+    if (lines.length < 2) return;
+    lines[0] = rest;
+    rest = lines.pop();
+    yield* lines;
+  });
+};
+
+module.exports = lines;

+ 131 - 0
src/utils/readableFrom.js

@@ -0,0 +1,131 @@
+'use strict';
+
+const {Readable} = require('stream');
+const defs = require('../defs');
+
+const readableFrom = options => {
+  if (!options || !options.iterable) {
+    options = {iterable: options};
+  }
+  let fn = options && options.iterable;
+  if (fn && typeof fn != 'function') {
+    if (typeof fn[Symbol.asyncIterator] == 'function') {
+      fn = fn[Symbol.asyncIterator].bind(fn);
+    } else if (typeof fn[Symbol.iterator] == 'function') {
+      fn = fn[Symbol.iterator].bind(fn);
+    } else {
+      fn = null;
+    }
+  }
+  if (!fn)
+    throw TypeError(
+      'Only a function or an object with an iterator is accepted as the first argument'
+    );
+
+  // pump variables
+  let paused = Promise.resolve(),
+    resolvePaused = null;
+  const queue = [];
+
+  // pause/resume
+  const resume = () => {
+    if (!resolvePaused) return;
+    resolvePaused();
+    resolvePaused = null;
+    paused = Promise.resolve();
+  };
+  const pause = () => {
+    if (resolvePaused) return;
+    paused = new Promise(resolve => (resolvePaused = resolve));
+  };
+
+  let stream = null; // will be assigned later
+
+  // data processing
+  const pushResults = values => {
+    if (values && typeof values.next == 'function') {
+      // generator
+      queue.push(values);
+      return;
+    }
+    // array
+    queue.push(values[Symbol.iterator]());
+  };
+  const pump = async () => {
+    while (queue.length) {
+      await paused;
+      const gen = queue[queue.length - 1];
+      let result = gen.next();
+      if (result && typeof result.then == 'function') {
+        result = await result;
+      }
+      if (result.done) {
+        queue.pop();
+        continue;
+      }
+      let value = result.value;
+      if (value && typeof value.then == 'function') {
+        value = await value;
+      }
+      await sanitize(value);
+    }
+  };
+  const sanitize = async value => {
+    if (value === undefined || value === null || value === defs.none) return;
+    if (value === defs.stop) throw new defs.Stop();
+
+    if (defs.isMany(value)) {
+      pushResults(defs.getManyValues(value));
+      return pump();
+    }
+
+    if (defs.isFinalValue(value)) {
+      // a final value is not supported, it is treated as a regular value
+      value = defs.getFinalValue(value);
+      return processValue(value);
+    }
+
+    if (!stream.push(value)) {
+      pause();
+    }
+  };
+  const startPump = async () => {
+    try {
+      const value = fn();
+      await processValue(value);
+      stream.push(null);
+    } catch (error) {
+      if (error instanceof defs.Stop) {
+        stream.push(null);
+        stream.destroy();
+        return;
+      }
+      throw error;
+    }
+  };
+  const processValue = async value => {
+    if (value && typeof value.then == 'function') {
+      // thenable
+      return value.then(value => processValue(value));
+    }
+    if (value && typeof value.next == 'function') {
+      // generator
+      pushResults(value);
+      return pump();
+    }
+    return sanitize(value);
+  };
+
+  stream = new Readable(
+    Object.assign({objectMode: true}, options, {
+      read() {
+        resume();
+      }
+    })
+  );
+
+  startPump();
+  return stream;
+};
+
+module.exports = readableFrom;

+ 1 - 0
src/utils/reduce.js

@@ -0,0 +1 @@
+module.exports = require('./fold');

+ 43 - 0
src/utils/reduceStream.js

@@ -0,0 +1,43 @@
+'use strict';
+
+const {Writable} = require('stream');
+
+const defaultInitial = 0;
+const defaultReducer = (acc, value) => value;
+
+const reduceStream = (options, initial) => {
+  if (!options || !options.reducer) {
+    options = {reducer: options, initial};
+  }
+  let accumulator = defaultInitial,
+    reducer = defaultReducer;
+  if (options) {
+    'initial' in options && (accumulator = options.initial);
+    'reducer' in options && (reducer = options.reducer);
+  }
+
+  const stream = new Writable(
+    Object.assign({objectMode: true}, options, {
+      write(chunk, _, callback) {
+        const result = reducer.call(this, this.accumulator, chunk);
+        if (result && typeof result.then == 'function') {
+          result.then(
+            value => {
+              this.accumulator = value;
+              callback(null);
+            },
+            error => callback(error)
+          );
+        } else {
+          this.accumulator = result;
+          callback(null);
+        }
+      }
+    })
+  );
+  stream.accumulator = accumulator;
+
+  return stream;
+};
+
+module.exports = reduceStream;

+ 11 - 0
src/utils/scan.js

@@ -0,0 +1,11 @@
+'use strict';
+
+const scan = (f, acc) => value => {
+  const result = f(acc, value);
+  if (result && typeof result.then == 'function') {
+    return result.then(result => (acc = result));
+  }
+  return (acc = result);
+};
+
+module.exports = scan;

+ 7 - 0
src/utils/skip.js

@@ -0,0 +1,7 @@
+'use strict';
+
+const {none} = require('../defs');
+
+const skip = n => value => (n > 0 ? (--n, none) : value);
+
+module.exports = skip;

+ 23 - 0
src/utils/skipWhile.js

@@ -0,0 +1,23 @@
+'use strict';
+
+const {none} = require('../defs');
+
+const skipWhile = f => {
+  let test = true;
+  return value => {
+    if (!test) return value;
+    const result = f(value);
+    if (result && typeof result.then == 'function') {
+      return result.then(result => {
+        if (result) return none;
+        test = false;
+        return value;
+      });
+    }
+    if (result) return none;
+    test = false;
+    return value;
+  };
+};
+
+module.exports = skipWhile;

+ 7 - 0
src/utils/take.js

@@ -0,0 +1,7 @@
+'use strict';
+
+const {none} = require('../defs');
+
+const take = (n, finalValue = none) => value => (n > 0 ? (--n, value) : finalValue);
+
+module.exports = take;

+ 23 - 0
src/utils/takeWhile.js

@@ -0,0 +1,23 @@
+'use strict';
+
+const {none} = require('../defs');
+
+const takeWhile = (f, finalValue = none) => {
+  let test = true;
+  return value => {
+    if (!test) return finalValue;
+    const result = f(value);
+    if (result && typeof result.then == 'function') {
+      return result.then(result => {
+        if (result) return value;
+        test = false;
+        return finalValue;
+      });
+    }
+    if (result) return value;
+    test = false;
+    return finalValue;
+  };
+};
+
+module.exports = takeWhile;

+ 8 - 0
src/utils/takeWithSkip.js

@@ -0,0 +1,8 @@
+'use strict';
+
+const {none} = require('../defs');
+
+const takeWithSkip = (n, skip = 0, finalValue = none) => value =>
+  skip > 0 ? (--skip, none) : n > 0 ? (--n, value) : finalValue;
+
+module.exports = takeWithSkip;

BIN
tests/data/sample.jsonl.gz


+ 0 - 25
tests/helpers.js

@@ -1,25 +0,0 @@
-'use strict';
-
-const {Writable} = require('stream');
-
-const streamToArray = array =>
-  new Writable({
-    objectMode: true,
-    write(chunk, encoding, callback) {
-      array.push(chunk);
-      callback(null);
-    }
-  });
-
-const delay = (fn, ms = 20) => async (...args) =>
-  new Promise((resolve, reject) => {
-    setTimeout(() => {
-      try {
-        resolve(fn(...args));
-      } catch (error) {
-        reject(error);
-      }
-    }, ms);
-  });
-
-module.exports = {streamToArray, delay};

+ 51 - 0
tests/helpers.mjs

@@ -0,0 +1,51 @@
+'use strict';
+
+import {Readable, Writable} from 'stream';
+
+export const streamToArray = array =>
+  new Writable({
+    objectMode: true,
+    write(chunk, _, callback) {
+      array.push(chunk);
+      callback(null);
+    }
+  });
+
+export const readString = (string, quant) => new Readable({
+  read() {
+    if (isNaN(quant) || quant < 1) {
+      this.push(string);
+    } else if (string instanceof Buffer) {
+      for (let i = 0; i < string.length; i += quant) {
+        this.push(string.slice(i, i + quant));
+      }
+    } else {
+      for (let i = 0; i < string.length; i += quant) {
+        this.push(string.substr(i, quant));
+      }
+    }
+    this.push(null);
+  }
+});
+
+export const writeToArray = array => new Writable({
+  write(chunk, _, callback) {
+    if (typeof chunk == 'string') {
+      array.push(chunk);
+    } else {
+      array.push(chunk.toString('utf8'));
+    }
+    callback(null);
+  }
+});
+
+export const delay = (fn, ms = 20) => (...args) =>
+  new Promise((resolve, reject) => {
+    setTimeout(() => {
+      try {
+        resolve(fn(...args));
+      } catch (error) {
+        reject(error);
+      }
+    }, ms);
+  });

+ 27 - 0
tests/manual/asStreamTest.js

@@ -0,0 +1,27 @@
+
+const {PassThrough} = require('stream');
+
+const defs = require('../../src/defs');
+const asStream = require('../../src/AsStream');
+
+const s = asStream(x => x * x);
+// const s = asStream(async x => x * x);
+// const s = asStream(function* (x) { for (let i = 0; i < x; ++i) yield i; });
+// const s = asStream(async function* (x) { for (let i = 0; i < x; ++i) yield i; });
+
+// const s = asStream(x => defs.none);
+// const s = asStream(x => defs.finalValue(42));
+// const s = asStream(x => defs.many(['a', x, 'b']));
+// const s = asStream(x => defs.stop);
+
+const h = new PassThrough({writableObjectMode: true, readableObjectMode: true});
+const p = h.pipe(s);
+
+p.on('data', data => console.log('DATA:', data));
+p.on('end', () => console.log('END'));
+
+h.write(1);
+h.write(2);
+h.write(3);
+h.write(4);
+h.end();

+ 75 - 0
tests/manual/streamEventsTest.js

@@ -0,0 +1,75 @@
+const {Writable, Transform} = require('stream');
+
+const makeStreamT = id => {
+  const stream = new Transform({
+    readableObjectMode: true,
+    writableObjectMode: true,
+    transform(chunk, encoding, callback) {
+      console.log(id + ':', 'transform', chunk, encoding);
+      const flag = this.push(chunk, encoding);
+      console.log(id + ':', 'transform-push', flag);
+      callback(null);
+    },
+    flush(callback) {
+      console.log(id + ':', 'flush');
+      callback(null);
+    }
+  });
+  stream._id = id;
+  stream.on('error', error => console.log(id + ':', 'event-error', error));
+  stream.on('end', () => console.log(id + ':', 'event-end'));
+  stream.on('finish', () => console.log(id + ':', 'event-finish'));
+  stream.on('close', () => console.log(id + ':', 'event-close'));
+  stream.on('pipe', src => console.log(id + ':', 'event-pipe', src._id));
+  stream.on('unpipe', src => console.log(id + ':', 'event-unpipe', src._id));
+  return stream;
+};
+
+const makeStreamW = id => {
+  const stream = new Writable({
+    objectMode: true,
+    write(chunk, encoding, callback) {
+      console.log(id + ':', 'write', chunk, encoding);
+      callback(null);
+    },
+    final(callback) {
+      console.log(id + ':', 'final');
+      callback(null);
+    },
+    destroy(error, callback) {
+      console.log(id + ':', 'destroy', error);
+      callback(null);
+    }
+  });
+  stream._id = id;
+  stream.on('error', error => console.log(id + ':', 'event-error', error));
+  stream.on('finish', () => console.log(id + ':', 'event-finish'));
+  stream.on('close', () => console.log(id + ':', 'event-close'));
+  stream.on('pipe', src => console.log(id + ':', 'event-pipe', src._id));
+  stream.on('unpipe', src => console.log(id + ':', 'event-unpipe', src._id));
+  return stream;
+};
+
+console.log('Creating streams ...');
+
+const a = makeStreamT('A'),
+  b = makeStreamT('B'),
+  c = makeStreamT('C'),
+  w = makeStreamW('W');
+
+console.log('Connecting streams ...');
+
+a.pipe(b).pipe(c).pipe(w);
+
+console.log('Passing a value ...');
+
+a.write({a: 1});
+// a.end();
+
+console.log('Destroying B ...');
+
+// a.destroy();
+a.unpipe(b);
+b.end();
+
+console.log('Done.');

+ 99 - 0
tests/test-asStream.mjs

@@ -0,0 +1,99 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {streamToArray, delay} from './helpers.mjs';
+import {none} from '../src/defs.js';
+
+import asStream from '../src/asStream.js';
+
+test.asPromise('asStream: smoke test', (t, resolve) => {
+  const pattern = [0, 1, true, false, {}, [], {a: 'b'}, ['c']],
+    result = [],
+    stream = asStream(x => x),
+    pipeline = stream.pipe(streamToArray(result));
+
+  pipeline.on('finish', () => {
+    t.deepEqual(result, pattern);
+    resolve();
+  });
+
+  pattern.forEach(value => stream.write(value));
+  stream.end();
+});
+
+test.asPromise('asStream: function', (t, resolve) => {
+  const pattern = [0, 1, true, false, {}, [], {a: 'b'}, ['c']],
+    result = [],
+    stream = asStream(x => (x ? x : none)),
+    pipeline = stream.pipe(streamToArray(result));
+
+  pipeline.on('finish', () => {
+    t.deepEqual(
+      result,
+      pattern.filter(x => x)
+    );
+    resolve();
+  });
+
+  pattern.forEach(value => stream.write(value));
+  stream.end();
+});
+
+test.asPromise('asStream: async function', (t, resolve) => {
+  const pattern = [0, 1, true, false, {}, [], {a: 'b'}, ['c']],
+    result = [],
+    stream = asStream(delay(x => (x ? x : none))),
+    pipeline = stream.pipe(streamToArray(result));
+
+  pipeline.on('finish', () => {
+    t.deepEqual(
+      result,
+      pattern.filter(x => x)
+    );
+    resolve();
+  });
+
+  pattern.forEach(value => stream.write(value));
+  stream.end();
+});
+
+test.asPromise('asStream: generator', (t, resolve) => {
+  const pattern = [1, 2, 3],
+    result = [],
+    stream = asStream(function* () {
+      yield* pattern;
+    }),
+    pipeline = stream.pipe(streamToArray(result));
+
+  pipeline.on('finish', () => {
+    t.deepEqual(result, pattern);
+    resolve();
+  });
+
+  stream.end(1);
+});
+
+test.asPromise('asStream: async generator', (t, resolve) => {
+  const pattern = [1, 2, 3],
+    result = [],
+    stream = asStream(async function* () {
+      const fn = delay(x => x);
+      yield* pattern.map(value => fn(value));
+    }),
+    pipeline = stream.pipe(streamToArray(result));
+
+  pipeline.on('finish', () => {
+    t.deepEqual(result, pattern);
+    resolve();
+  });
+
+  stream.end(1);
+});
+
+test('asStream: wrong argument', t => {
+  t.throws(() => {
+    asStream(1);
+    t.fail("shouldn't be here");
+  });
+});

+ 29 - 0
tests/test-batch.mjs

@@ -0,0 +1,29 @@
+'use strict';
+
+import test from 'tape-six';
+
+import chain from '../src/index.js';
+
+import {readString} from './helpers.mjs';
+import parser from '../src/jsonl/parser.js';
+
+import batch from '../src/utils/batch.js';
+
+test.asPromise('batch: smoke test', (t, resolve) => {
+  const pattern = [0, 1, true, false, null, {}, [], {a: 'b'}, ['c']],
+    result = [],
+    pipeline = chain([
+      readString(pattern.map(value => JSON.stringify(value)).join('\n')),
+      parser(),
+      batch(2)
+    ]);
+
+  pipeline.output.on('data', batch => {
+    t.ok(batch.length == 2 || batch.length == 1);
+    batch.forEach(object => (result[object.key] = object.value));
+  });
+  pipeline.output.on('end', () => {
+    t.deepEqual(pattern, result);
+    resolve();
+  });
+});

+ 98 - 0
tests/test-dataSource.mjs

@@ -0,0 +1,98 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {streamToArray, delay} from './helpers.mjs';
+import chain, {dataSource} from '../src/index.js';
+
+test.asPromise('dataSource: smoke test', (t, resolve) => {
+  const output = [],
+    c = chain([dataSource([1, 2, 3]), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 2, 3]);
+    resolve();
+  });
+
+  c.end(1); // start the chain
+});
+
+test.asPromise('dataSource: function', (t, resolve) => {
+  const output = [],
+    c = chain([dataSource(() => 0), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [0]);
+    resolve();
+  });
+
+  c.end(1); // start the chain
+});
+
+test.asPromise('dataSource: async function', (t, resolve) => {
+  const output = [],
+    c = chain([dataSource(delay(() => 0)), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [0]);
+    resolve();
+  });
+
+  c.end(1); // start the chain
+});
+
+test.asPromise('dataSource: generator', (t, resolve) => {
+  const output = [],
+    c = chain([
+      dataSource(function* () {
+        yield 0;
+        yield 1;
+      }),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [0, 1]);
+    resolve();
+  });
+
+  c.end(1); // start the chain
+});
+
+test.asPromise('dataSource: async generator', (t, resolve) => {
+  const output = [],
+    c = chain([
+      dataSource(async function* () {
+        yield delay(() => 0)();
+        yield delay(() => 1)();
+      }),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [0, 1]);
+    resolve();
+  });
+
+  c.end(1); // start the chain
+});
+
+test.asPromise('dataSource: nextable', (t, resolve) => {
+  const output = [],
+    c = chain([
+      dataSource(
+        (function* () {
+          yield 0;
+          yield 1;
+        })()
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [0, 1]);
+    resolve();
+  });
+
+  c.end(1); // start the chain
+});

+ 87 - 0
tests/test-demo.mjs

@@ -0,0 +1,87 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {Transform} from 'stream';
+import chain from '../src/index.js';
+import readableFrom from '../src/utils/readableFrom.js';
+
+const getTotalFromDatabaseByKey = async x =>
+  new Promise(resolve => {
+    setTimeout(() => {
+      resolve(Math.min(x % 10, 3));
+    }, 20);
+  });
+
+test.asPromise('demo: default', (t, resolve) => {
+  const c = chain([
+      // transforms a value
+      x => x * x,
+      // returns several values
+      x => chain.many([x - 1, x, x + 1]),
+      // waits for an asynchronous operation
+      async x => await getTotalFromDatabaseByKey(x),
+      // returns multiple values with a generator
+      function* (x) {
+        for (let i = x; i > 0; --i) {
+          yield i;
+        }
+        return 0;
+      },
+      // filters out even values
+      x => (x % 2 ? x : null),
+      // uses an arbitrary transform stream
+      new Transform({
+        objectMode: true,
+        transform(x, _, callback) {
+          callback(null, x + 1);
+        }
+      })
+    ]),
+    output = [];
+  c.on('data', data => output.push(data));
+  c.on('end', () => {
+    t.deepEqual(output, [2, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2]);
+    resolve();
+  });
+
+  readableFrom([1, 2, 3]).pipe(c);
+});
+
+test.asPromise('demo: no grouping', (t, resolve) => {
+  const c = chain(
+      [
+        // transforms a value
+        x => x * x,
+        // returns several values
+        x => chain.many([x - 1, x, x + 1]),
+        // waits for an asynchronous operation
+        async x => await getTotalFromDatabaseByKey(x),
+        // returns multiple values with a generator
+        function* (x) {
+          for (let i = x; i > 0; --i) {
+            yield i;
+          }
+          return 0;
+        },
+        // filters out even values
+        x => (x % 2 ? x : null),
+        // uses an arbitrary transform stream
+        new Transform({
+          objectMode: true,
+          transform(x, _, callback) {
+            callback(null, x + 1);
+          }
+        })
+      ],
+      {noGrouping: true}
+    ),
+    output = [];
+  c.on('data', data => output.push(data));
+  c.on('end', () => {
+    t.deepEqual(output, [2, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2]);
+    resolve();
+  });
+
+  readableFrom([1, 2, 3]).pipe(c);
+});

+ 19 - 0
tests/test-errors.mjs

@@ -0,0 +1,19 @@
+'use strict';
+
+import test from 'tape-six';
+
+import chain from '../src/index.js';
+
+test('errors: no streams', t => {
+  t.throws(() => {
+    chain([]);
+    t.fail("shouldn't be here");
+  });
+});
+
+test('errors: wrong stream', t => {
+  t.throws(() => {
+    chain([1]);
+    t.fail("shouldn't be here");
+  });
+});

+ 115 - 0
tests/test-fold.mjs

@@ -0,0 +1,115 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {streamToArray, delay} from './helpers.mjs';
+import chain from '../src/index.js';
+import readableFrom from '../src/utils/readableFrom.js';
+
+import fold from '../src/utils/fold.js';
+import scan from '../src/utils/scan.js';
+import reduce from '../src/utils/reduce.js';
+import reduceStream from '../src/utils/reduceStream.js';
+
+test.asPromise('fold: smoke test', (t, resolve) => {
+  const output = [],
+    c = chain([readableFrom([1, 2, 3]), fold((acc, x) => acc + x, 0), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [6]);
+    resolve();
+  });
+});
+
+test.asPromise('fold: async', (t, resolve) => {
+  const output = [],
+    c = chain([
+      readableFrom([1, 2, 3]),
+      fold(
+        delay((acc, x) => acc + x),
+        0
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [6]);
+    resolve();
+  });
+});
+
+test.asPromise('fold: scan', (t, resolve) => {
+  const output = [],
+    c = chain([readableFrom([1, 2, 3]), scan((acc, x) => acc + x, 0), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 3, 6]);
+    resolve();
+  });
+});
+
+test.asPromise('fold: scan async', (t, resolve) => {
+  const output = [],
+    c = chain([
+      readableFrom([1, 2, 3]),
+      scan(
+        delay((acc, x) => acc + x),
+        0
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 3, 6]);
+    resolve();
+  });
+});
+
+test.asPromise('fold: reduce', (t, resolve) => {
+  const output = [],
+    c = chain([readableFrom([1, 2, 3]), fold((acc, x) => acc + x, 0), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [6]);
+    resolve();
+  });
+});
+
+test.asPromise('fold: reduce async', (t, resolve) => {
+  const output = [],
+    c = chain([
+      readableFrom([1, 2, 3]),
+      reduce(
+        delay((acc, x) => acc + x),
+        0
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [6]);
+    resolve();
+  });
+});
+
+test.asPromise('fold: reduce stream', (t, resolve) => {
+  const r = reduceStream((acc, x) => acc + x, 0);
+
+  readableFrom([1, 2, 3]).pipe(r);
+
+  r.on('finish', () => {
+    t.deepEqual(r.accumulator, 6);
+    resolve();
+  });
+});
+
+test.asPromise('fold: reduce stream async', (t, resolve) => {
+  const r = reduceStream({reducer: delay((acc, x) => acc + x), initial: 0});
+
+  readableFrom([1, 2, 3]).pipe(r);
+
+  r.on('finish', () => {
+    t.deepEqual(r.accumulator, 6);
+    resolve();
+  });
+});

+ 215 - 0
tests/test-fun.mjs

@@ -0,0 +1,215 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {streamToArray, delay} from './helpers.mjs';
+import chain, {none, finalValue, many} from '../src/index.js';
+import fromIterable from '../src/utils/readableFrom.js';
+
+import fun from '../src/fun.js';
+
+test.asPromise('fun: smoke test', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      fun(
+        x => x * x,
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('fun: final', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      fun(
+        x => x * x,
+        x => finalValue(x),
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 4, 9]);
+    resolve();
+  });
+});
+
+test.asPromise('fun: nothing', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      fun(
+        x => x * x,
+        () => none,
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, []);
+    resolve();
+  });
+});
+
+test.asPromise('fun: empty', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), x => x * x, fun(), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 4, 9]);
+    resolve();
+  });
+});
+
+test.asPromise('fun: async', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      fun(
+        delay(x => x * x),
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('fun: generator', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      fun(
+        x => x * x,
+        function* (x) {
+          yield x;
+          yield x + 1;
+          yield x + 2;
+        },
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 5, 7, 9, 11, 13, 19, 21, 23]);
+    resolve();
+  });
+});
+
+test.asPromise('fun: many', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      fun(
+        x => x * x,
+        x => many([x, x + 1, x + 2]),
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 5, 7, 9, 11, 13, 19, 21, 23]);
+    resolve();
+  });
+});
+
+test.asPromise('fun: combined', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2]),
+      fun(
+        delay(x => -x),
+        x => many([x, x * 10]),
+        function* (x) {
+          yield x;
+          yield x - 1;
+        },
+        x => -x
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 2, 10, 11, 2, 3, 20, 21]);
+    resolve();
+  });
+});
+
+test.asPromise('fun: combined final', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2]),
+      fun(
+        delay(x => -x),
+        x => many([x, x * 10]),
+        function* (x) {
+          yield x;
+          yield finalValue(x - 1);
+        },
+        x => -x
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, -2, 10, -11, 2, -3, 20, -21]);
+    resolve();
+  });
+});
+
+test.asPromise('fun: as fun', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2]),
+      fun(
+        delay(x => -x),
+        x => many([x, x * 10]),
+        function* (x) {
+          yield x;
+          yield finalValue(x - 1);
+        },
+        x => -x
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, -2, 10, -11, 2, -3, 20, -21]);
+    resolve();
+  });
+});
+
+test.asPromise('fun: array', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), [x => x * x, x => 2 * x + 1], streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('fun: embedded arrays', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), [x => x * x, [x => 2 * x + 1, []]], streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});

+ 235 - 0
tests/test-gen.mjs

@@ -0,0 +1,235 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {streamToArray, delay} from './helpers.mjs';
+import chain, {none, finalValue, many, gen} from '../src/index.js';
+import fromIterable from '../src/utils/readableFrom.js';
+
+test.asPromise('gen: smoke test', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      gen(
+        x => x * x,
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: final', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      gen(
+        x => x * x,
+        x => finalValue(x),
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 4, 9]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: nothing', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      gen(
+        x => x * x,
+        () => none,
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, []);
+    resolve();
+  });
+});
+
+test.asPromise('gen: empty', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), x => x * x, gen(), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 4, 9]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: async', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      gen(
+        delay(x => x * x),
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: generator', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      gen(
+        x => x * x,
+        function* (x) {
+          yield x;
+          yield x + 1;
+          yield x + 2;
+        },
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 5, 7, 9, 11, 13, 19, 21, 23]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: many', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      gen(
+        x => x * x,
+        x => many([x, x + 1, x + 2]),
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 5, 7, 9, 11, 13, 19, 21, 23]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: combined', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2]),
+      gen(
+        delay(x => -x),
+        x => many([x, x * 10]),
+        function* (x) {
+          yield x;
+          yield x - 1;
+        },
+        x => -x
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 2, 10, 11, 2, 3, 20, 21]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: combined final', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2]),
+      gen(
+        delay(x => -x),
+        x => many([x, x * 10]),
+        function* (x) {
+          yield x;
+          yield finalValue(x - 1);
+        },
+        x => -x
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, -2, 10, -11, 2, -3, 20, -21]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: iterator', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2]),
+      gen(
+        delay(x => -x),
+        x => many([x, x * 10]),
+        function* (x) {
+          yield x;
+          yield finalValue(x - 1);
+        },
+        x => -x
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, -2, 10, -11, 2, -3, 20, -21]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: async iterator', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2]),
+      gen(
+        delay(x => -x),
+        x => many([x, x * 10]),
+        async function* (x) {
+          yield delay(x => x)(x);
+          yield delay(x => finalValue(x - 1))(x);
+        },
+        x => -x
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, -2, 10, -11, 2, -3, 20, -21]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: array', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), [x => x * x, x => 2 * x + 1], streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('gen: embedded arrays', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), [x => x * x, [x => 2 * x + 1, []]], streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});

+ 104 - 0
tests/test-jsonl-parser.mjs

@@ -0,0 +1,104 @@
+'use strict';
+
+import test from 'tape-six';
+
+import fs from 'fs';
+import path from 'path';
+import zlib from 'zlib';
+import {Writable} from 'stream';
+
+import {readString} from './helpers.mjs';
+import chain from '../src/index.js';
+
+import parser from '../src/jsonl/parser.js';
+
+const roundtrip = (t, resolve, len, quant) => {
+  const objects = [];
+  for (let n = 0; n < len; n += 1) {
+    objects.push({
+      stringWithTabsAndNewlines: "Did it work?\nNo...\t\tI don't think so...",
+      anArray: [n + 1, n + 2, true, 'tabs?\t\t\t\u0001\u0002\u0003', false],
+      n
+    });
+  }
+
+  const json = [];
+  for (let n = 0; n < objects.length; n += 1) {
+    json.push(JSON.stringify(objects[n]));
+  }
+
+  const input = json.join('\n'),
+    result = [];
+  chain([
+    readString(input, quant),
+    parser(),
+    new Writable({
+      objectMode: true,
+      write(chunk, _, callback) {
+        result.push(chunk.value);
+        callback(null);
+      },
+      final(callback) {
+        t.deepEqual(objects, result);
+        resolve();
+        callback(null);
+      }
+    })
+  ]);
+};
+
+test.asPromise('jsonl parser: smoke test', (t, resolve) => roundtrip(t, resolve));
+
+for (let i = 1; i <= 12; ++i) {
+  test.asPromise('jsonl parser: roundtrip with a set of objects - ' + i, (t, resolve) => {
+    roundtrip(t, resolve, i);
+  });
+}
+
+for (let i = 1; i <= 12; ++i) {
+  test.asPromise('jsonl parser: roundtrip with different window sizes - ' + i, (t, resolve) => {
+    roundtrip(t, resolve, 10, i);
+  });
+}
+
+test.asPromise('jsonl parser: read file', (t, resolve) => {
+  if (!/^file:\/\//.test(import.meta.url)) throw Error('Cannot get the current working directory');
+  const isWindows = path.sep === '\\',
+    fileName = path.join(
+      path.dirname(import.meta.url.substring(isWindows ? 8 : 7)),
+      './data/sample.jsonl.gz'
+    );
+  let count = 0;
+  chain([
+    fs.createReadStream(fileName),
+    zlib.createGunzip(),
+    parser(),
+    new Writable({
+      objectMode: true,
+      write(chunk, _, callback) {
+        t.equal(count, chunk.key);
+        ++count;
+        callback(null);
+      },
+      final(callback) {
+        t.equal(count, 100);
+        resolve();
+        callback(null);
+      }
+    })
+  ]);
+});
+
+test.asPromise('jsonl parser: bad json', (t, resolve) => {
+  const pipeline = chain([readString(' not json '), parser()]);
+
+  pipeline.on('data', () => t.fail("We shouldn't be here."));
+  pipeline.on('error', e => {
+    t.ok(e);
+    resolve();
+  });
+  pipeline.on('end', value => {
+    t.fail("We shouldn't be here.");
+    resolve();
+  });
+});

+ 106 - 0
tests/test-jsonl-parserStream.mjs

@@ -0,0 +1,106 @@
+'use strict';
+
+import test from 'tape-six';
+
+import fs from 'fs';
+import path from 'path';
+import zlib from 'zlib';
+import {Writable} from 'stream';
+
+import {readString} from './helpers.mjs';
+
+import parserStream from '../src/jsonl/parserStream.js';
+
+const roundtrip = (t, resolve, len, quant) => {
+  const objects = [];
+  for (let n = 0; n < len; n += 1) {
+    objects.push({
+      stringWithTabsAndNewlines: "Did it work?\nNo...\t\tI don't think so...",
+      anArray: [n + 1, n + 2, true, 'tabs?\t\t\t\u0001\u0002\u0003', false],
+      n
+    });
+  }
+
+  const json = [];
+  for (let n = 0; n < objects.length; n += 1) {
+    json.push(JSON.stringify(objects[n]));
+  }
+
+  const input = json.join('\n'),
+    result = [];
+  readString(input, quant)
+    .pipe(parserStream())
+    .pipe(
+      new Writable({
+        objectMode: true,
+        write(chunk, _, callback) {
+          result.push(chunk.value);
+          callback(null);
+        },
+        final(callback) {
+          t.deepEqual(objects, result);
+          resolve();
+          callback(null);
+        }
+      })
+    );
+};
+
+test.asPromise('jsonl parserStream: smoke test', (t, resolve) => roundtrip(t, resolve));
+
+for (let i = 1; i <= 12; ++i) {
+  test.asPromise('jsonl parserStream: roundtrip with a set of objects - ' + i, (t, resolve) => {
+    roundtrip(t, resolve, i);
+  });
+}
+
+for (let i = 1; i <= 12; ++i) {
+  test.asPromise(
+    'jsonl parserStream: roundtrip with different window sizes - ' + i,
+    (t, resolve) => {
+      roundtrip(t, resolve, 10, i);
+    }
+  );
+}
+
+test.asPromise('jsonl parserStream: read file', (t, resolve) => {
+  if (!/^file:\/\//.test(import.meta.url)) throw Error('Cannot get the current working directory');
+  const isWindows = path.sep === '\\',
+    fileName = path.join(
+      path.dirname(import.meta.url.substring(isWindows ? 8 : 7)),
+      './data/sample.jsonl.gz'
+    );
+  let count = 0;
+  fs.createReadStream(fileName)
+    .pipe(zlib.createGunzip())
+    .pipe(parserStream())
+    .pipe(
+      new Writable({
+        objectMode: true,
+        write(chunk, _, callback) {
+          t.equal(count, chunk.key);
+          ++count;
+          callback(null);
+        },
+        final(callback) {
+          t.equal(count, 100);
+          resolve();
+          callback(null);
+        }
+      })
+    );
+});
+
+test.asPromise('jsonl parserStream: bad json', (t, resolve) => {
+  const pipeline = readString(' not json ').pipe(parserStream());
+
+  pipeline.on('data', () => t.fail("We shouldn't be here."));
+  pipeline.on('error', e => {
+    t.ok(e);
+    resolve();
+  });
+  pipeline.on('end', value => {
+    t.fail("We shouldn't be here.");
+    resolve();
+  });
+});

+ 155 - 0
tests/test-jsonl-stringerStream.mjs

@@ -0,0 +1,155 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {Writable, Transform} from 'stream';
+
+import {readString, writeToArray} from './helpers.mjs';
+
+import parserStream from '../src/jsonl/parserStream.js';
+import stringerStream from '../src/jsonl/stringerStream.js';
+
+test.asPromise('jsonl stringerStream: smoke test', (t, resolve) => {
+  const pattern = {
+      a: [[[]]],
+      b: {a: 1},
+      c: {a: 1, b: 2},
+      d: [true, 1, "'x\"y'", null, false, true, {}, [], ''],
+      e: 1,
+      f: '',
+      g: true,
+      h: false,
+      i: null,
+      j: [],
+      k: {}
+    },
+    string = JSON.stringify(pattern);
+
+  let buffer = '';
+  readString(string)
+    .pipe(parserStream())
+    .pipe(
+      new Transform({
+        writableObjectMode: true,
+        readableObjectMode: true,
+        transform(chunk, _, callback) {
+          this.push(chunk.value);
+          callback(null);
+        }
+      })
+    )
+    .pipe(stringerStream())
+    .pipe(
+      new Writable({
+        write(chunk, _, callback) {
+          buffer += chunk;
+          callback(null);
+        },
+        final(callback) {
+          t.deepEqual(string, buffer);
+          resolve();
+          callback(null);
+        }
+      })
+    );
+});
+
+test.asPromise('jsonl stringerStream: multiple', (t, resolve) => {
+  const pattern = {
+    a: [[[]]],
+    b: {a: 1},
+    c: {a: 1, b: 2},
+    d: [true, 1, "'x\"y'", null, false, true, {}, [], ''],
+    e: 1,
+    f: '',
+    g: true,
+    h: false,
+    i: null,
+    j: [],
+    k: {}
+  };
+
+  let string = JSON.stringify(pattern),
+    buffer = '';
+  string = string + '\n' + string + '\n' + string;
+
+  readString(string + '\n')
+    .pipe(parserStream())
+    .pipe(
+      new Transform({
+        writableObjectMode: true,
+        readableObjectMode: true,
+        transform(chunk, _, callback) {
+          this.push(chunk.value);
+          callback(null);
+        }
+      })
+    )
+    .pipe(stringerStream())
+    .pipe(
+      new Writable({
+        write(chunk, _, callback) {
+          buffer += chunk;
+          callback(null);
+        },
+        final(callback) {
+          t.deepEqual(string, buffer);
+          resolve();
+          callback(null);
+        }
+      })
+    );
+});
+
+test.asPromise('jsonl stringerStream: custom separators - one value', (t, resolve) => {
+  const output = [],
+    stringer = stringerStream({emptyValue: '{}', prefix: '[', suffix: ']', separator: ','}),
+    pipeline = stringer.pipe(writeToArray(output));
+
+  pipeline.on('finish', () => {
+    t.equal(output.join(''), '[1]');
+    resolve();
+  });
+
+  stringer.end(1);
+});
+
+test.asPromise('jsonl stringerStream: custom separators - two value', (t, resolve) => {
+  const output = [],
+    stringer = stringerStream({emptyValue: '{}', prefix: '[', suffix: ']', separator: ','}),
+    pipeline = stringer.pipe(writeToArray(output));
+
+  pipeline.on('finish', () => {
+    t.equal(output.join(''), '[2,1]');
+    resolve();
+  });
+
+  stringer.write(2);
+  stringer.end(1);
+});
+
+test.asPromise('jsonl stringerStream: custom separators - no value', (t, resolve) => {
+  const output = [],
+    stringer = stringerStream({emptyValue: '{}', prefix: '[', suffix: ']', separator: ','}),
+    pipeline = stringer.pipe(writeToArray(output));
+
+  pipeline.on('finish', () => {
+    t.equal(output.join(''), '{}');
+    resolve();
+  });
+
+  stringer.end();
+});
+
+test.asPromise('jsonl stringerStream: custom separators - no value (default)', (t, resolve) => {
+  const output = [],
+    stringer = stringerStream({prefix: '[', suffix: ']', separator: ','}),
+    pipeline = stringer.pipe(writeToArray(output));
+
+  pipeline.on('finish', () => {
+    t.equal(output.join(''), '[]');
+    resolve();
+  });
+
+  stringer.end();
+});

+ 86 - 0
tests/test-readWrite.mjs

@@ -0,0 +1,86 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {streamToArray} from './helpers.mjs';
+import chain, {dataSource} from '../src/index.js';
+import fromIterable from '../src/utils/readableFrom.js';
+
+test.asPromise('readWrite: readable', (t, resolve) => {
+  const output1 = [],
+    output2 = [],
+    c = chain([fromIterable([1, 2, 3]), x => x * x]);
+
+  c.pipe(streamToArray(output1));
+
+  c.on('data', value => output2.push(value));
+  c.on('end', () => {
+    t.deepEqual(output1, [1, 4, 9]);
+    t.deepEqual(output2, [1, 4, 9]);
+    resolve();
+  });
+});
+
+test.asPromise('readWrite: writable', (t, resolve) => {
+  const output = [],
+    c = chain([x => x * x, streamToArray(output)]);
+
+  fromIterable([1, 2, 3]).pipe(c);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 4, 9]);
+    resolve();
+  });
+});
+
+test.asPromise('readWrite: readable and writable', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), x => x * x, streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 4, 9]);
+    resolve();
+  });
+});
+
+test.asPromise('readWrite: single readable', (t, resolve) => {
+  const output1 = [],
+    output2 = [],
+    c = chain([fromIterable([1, 2, 3])]);
+
+  c.pipe(streamToArray(output1));
+
+  c.on('data', value => output2.push(value));
+  c.on('end', () => {
+    t.deepEqual(output1, [1, 2, 3]);
+    t.deepEqual(output2, [1, 2, 3]);
+    resolve();
+  });
+});
+
+test.asPromise('readWrite: single writable', (t, resolve) => {
+  const output = [],
+    c = chain([streamToArray(output)]);
+
+  fromIterable([1, 2, 3]).pipe(c);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 2, 3]);
+    resolve();
+  });
+});
+
+test.asPromise('readWrite: pipeable', (t, resolve, reject) => {
+  const output1 = [],
+    output2 = [],
+    c = chain([dataSource([1, 2, 3]), streamToArray(output1)]);
+
+  fromIterable([4, 5, 6]).pipe(c).pipe(streamToArray(output2));
+
+  c.on('end', () => {
+    t.deepEqual(output1, [1, 2, 3, 1, 2, 3, 1, 2, 3]);
+    t.deepEqual(output2, []);
+    resolve();
+  });
+  c.on('error', error => reject(error));
+});

+ 88 - 0
tests/test-readableFrom.mjs

@@ -0,0 +1,88 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {streamToArray, delay} from './helpers.mjs';
+import chain from '../src/index.js';
+
+import readableFrom from '../src/utils/readableFrom.js';
+
+test.asPromise('readableFrom: smoke test', (t, resolve) => {
+  const output = [],
+    c = chain([readableFrom([1, 2, 3]), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 2, 3]);
+    resolve();
+  });
+});
+
+test.asPromise('readableFrom: function', (t, resolve) => {
+  const output = [],
+    c = chain([readableFrom(() => 0), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [0]);
+    resolve();
+  });
+});
+
+test.asPromise('readableFrom: async function', (t, resolve) => {
+  const output = [],
+    c = chain([readableFrom(delay(() => 0)), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [0]);
+    resolve();
+  });
+});
+
+test.asPromise('readableFrom: generator', (t, resolve) => {
+  const output = [],
+    c = chain([
+      readableFrom(function* () {
+        yield 0;
+        yield 1;
+      }),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [0, 1]);
+    resolve();
+  });
+});
+
+test.asPromise('readableFrom: async generator', (t, resolve) => {
+  const output = [],
+    c = chain([
+      readableFrom(async function* () {
+        yield delay(() => 0)();
+        yield delay(() => 1)();
+      }),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [0, 1]);
+    resolve();
+  });
+});
+
+test.asPromise('readableFrom: nextable', (t, resolve) => {
+  const output = [],
+    c = chain([
+      readableFrom(
+        (function* () {
+          yield 0;
+          yield 1;
+        })()
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [0, 1]);
+    resolve();
+  });
+});

+ 113 - 0
tests/test-simple.mjs

@@ -0,0 +1,113 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {Transform} from 'stream';
+import {streamToArray, delay} from './helpers.mjs';
+import chain from '../src/index.js';
+import fromIterable from '../src/utils/readableFrom.js';
+
+test.asPromise('simple: smoke test', (t, resolve) => {
+  const c = chain([x => x * x]),
+    output1 = [],
+    output2 = [];
+
+  fromIterable([1, 2, 3]).pipe(c).pipe(streamToArray(output1));
+
+  c.on('data', value => output2.push(value));
+  c.on('end', () => {
+    t.deepEqual(output1, [1, 4, 9]);
+    t.deepEqual(output2, [1, 4, 9]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: generator', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      function* (x) {
+        yield x * x;
+        yield x * x * x;
+        yield 2 * x;
+      },
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 1, 2, 4, 8, 4, 9, 27, 6]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: async function', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), delay(x => x + 1), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [2, 3, 4]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: async function', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      x => chain.many([x * x, x * x * x, 2 * x]),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 1, 2, 4, 8, 4, 9, 27, 6]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: chain', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), x => x * x, x => 2 * x + 1, streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: stream', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      new Transform({
+        objectMode: true,
+        transform(x, _, callback) {
+          callback(null, x * x);
+        }
+      }),
+      x => 2 * x + 1,
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: factory', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      function* (x) {
+        yield x * x;
+        yield x * x * x;
+        yield 2 * x;
+      },
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 1, 2, 4, 8, 4, 9, 27, 6]);
+    resolve();
+  });
+});

+ 44 - 0
tests/test-skip.mjs

@@ -0,0 +1,44 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {streamToArray, delay} from './helpers.mjs';
+import chain from '../src/index.js';
+import fromIterable from '../src/utils/readableFrom.js';
+
+import skip from '../src/utils/skip.js';
+import skipWhile from '../src/utils/skipWhile.js';
+
+test.asPromise('skip: smoke test', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3, 4, 5]), skip(2), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 4, 5]);
+    resolve();
+  });
+});
+
+test.asPromise('skip: while', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3, 4, 5]), skipWhile(x => x != 3), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 4, 5]);
+    resolve();
+  });
+});
+
+test.asPromise('skip: while async', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3, 4, 5]),
+      skipWhile(delay(x => x != 3)),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 4, 5]);
+    resolve();
+  });
+});

+ 75 - 0
tests/test-take.mjs

@@ -0,0 +1,75 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {streamToArray, delay} from './helpers.mjs';
+import chain, {stop} from '../src/index.js';
+import fromIterable from '../src/utils/readableFrom.js';
+
+import take from '../src/utils/take.js';
+import takeWhile from '../src/utils/takeWhile.js';
+import takeWithSkip from '../src/utils/takeWithSkip.js';
+
+test.asPromise('take: smoke test', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3, 4, 5]), take(2), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 2]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: with skip', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3, 4, 5]), takeWithSkip(2, 2), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 4]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: while', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3, 4, 5]), takeWhile(x => x != 3), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 2]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: while async', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3, 4, 5]),
+      takeWhile(delay(x => x != 3)),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 2]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: stop', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3, 4, 5]), take(2, stop), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 2]);
+    resolve();
+  });
+});
+
+test.asPromise('simple: stop with skip', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3, 4, 5]), takeWithSkip(2, 2, stop), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 4]);
+    resolve();
+  });
+});

+ 100 - 0
tests/test-transducers.mjs

@@ -0,0 +1,100 @@
+'use strict';
+
+import test from 'tape-six';
+
+import {streamToArray} from './helpers.mjs';
+import chain, {gen, none, finalValue} from '../src/index.js';
+import fromIterable from '../src/utils/readableFrom.js';
+
+test.asPromise('transducers: smoke test', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      gen(
+        x => x * x,
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('transducers: final', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      gen(
+        x => x * x,
+        x => finalValue(x),
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 4, 9]);
+    resolve();
+  });
+});
+
+test.asPromise('transducers: nothing', (t, resolve) => {
+  const output = [],
+    c = chain([
+      fromIterable([1, 2, 3]),
+      gen(
+        x => x * x,
+        () => none,
+        x => 2 * x + 1
+      ),
+      streamToArray(output)
+    ]);
+
+  c.on('end', () => {
+    t.deepEqual(output, []);
+    resolve();
+  });
+});
+
+test.asPromise('transducers: empty', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), x => x * x, gen(), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [1, 4, 9]);
+    resolve();
+  });
+});
+
+test.asPromise('transducers: one', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), x => x * x, gen(x => 2 * x + 1), streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('transducers: array', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), [x => x * x, x => 2 * x + 1], streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});
+
+test.asPromise('transducers: embedded arrays', (t, resolve) => {
+  const output = [],
+    c = chain([fromIterable([1, 2, 3]), [x => x * x, [x => 2 * x + 1, []]], streamToArray(output)]);
+
+  c.on('end', () => {
+    t.deepEqual(output, [3, 9, 19]);
+    resolve();
+  });
+});

+ 0 - 95
tests/test_FromIterable.js

@@ -1,95 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-const {streamToArray, delay} = require('./helpers');
-
-const {fromIterable} = require('../utils/FromIterable');
-
-unit.add(module, [
-  function test_FromIterable(t) {
-    const async = t.startAsync('test_FromIterable');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 2, 3])'));
-      async.done();
-    });
-  },
-  function test_FromIterableFun(t) {
-    const async = t.startAsync('test_FromIterableFun');
-
-    const output = [],
-      chain = new Chain([fromIterable(() => 0), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [0])'));
-      async.done();
-    });
-  },
-  function test_FromIterableAsyncFun(t) {
-    const async = t.startAsync('test_FromIterableAsyncFun');
-
-    const output = [],
-      chain = new Chain([fromIterable(delay(() => 0)), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [0])'));
-      async.done();
-    });
-  },
-  function test_FromIterableGen(t) {
-    const async = t.startAsync('test_FromIterableGen');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable(function*() {
-          yield 0;
-          yield 1;
-        }),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [0, 1])'));
-      async.done();
-    });
-  },
-  // function test_FromIterableAsyncGen(t) {
-  //   const async = t.startAsync('test_FromIterableAsyncGen');
-
-  //   const output = [],
-  //     chain = new Chain([
-  //       fromIterable(async function*() {
-  //         yield delay(() => 0)();
-  //         yield delay(() => 1)();
-  //       }),
-  //       streamToArray(output)
-  //     ]);
-
-  //   chain.on('end', () => {
-  //     eval(t.TEST('t.unify(output, [0, 1])'));
-  //     async.done();
-  //   });
-  // },
-  function test_FromIterableNextable(t) {
-    const async = t.startAsync('test_FromIterableNextable');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable((function*() {
-          yield 0;
-          yield 1;
-        })()),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [0, 1])'));
-      async.done();
-    });
-  }
-]);

+ 0 - 181
tests/test_comp.js

@@ -1,181 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-const {streamToArray, delay} = require('./helpers');
-
-const {fromIterable} = require('../utils/FromIterable');
-const comp = require('../utils/comp');
-const asFun = require('../utils/asFun');
-
-const {none, final, many} = Chain;
-
-unit.add(module, [
-  function test_comp(t) {
-    const async = t.startAsync('test_comp');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), comp(x => x * x, x => 2 * x + 1), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 9, 19])'));
-      async.done();
-    });
-  },
-  function test_compFinal(t) {
-    const async = t.startAsync('test_compFinal');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2, 3]),
-        comp(x => x * x, x => final(x), x => 2 * x + 1),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 4, 9])'));
-      async.done();
-    });
-  },
-  function test_compNothing(t) {
-    const async = t.startAsync('test_compNothing');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), comp(x => x * x, () => none, x => 2 * x + 1), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [])'));
-      async.done();
-    });
-  },
-  function test_compEmpty(t) {
-    const async = t.startAsync('test_compEmpty');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), x => x * x, comp(), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 4, 9])'));
-      async.done();
-    });
-  },
-  function test_compAsync(t) {
-    const async = t.startAsync('test_compAsync');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), comp(delay(x => x * x), x => 2 * x + 1), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 9, 19])'));
-      async.done();
-    });
-  },
-  function test_compGenerator(t) {
-    const async = t.startAsync('test_compGenerator');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2, 3]),
-        comp(
-          x => x * x,
-          function*(x) {
-            yield x;
-            yield x + 1;
-            yield x + 2;
-          },
-          x => 2 * x + 1
-        ),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 5, 7, 9, 11, 13, 19, 21, 23])'));
-      async.done();
-    });
-  },
-  function test_compMany(t) {
-    const async = t.startAsync('test_compMany');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2, 3]),
-        comp(x => x * x, x => many([x, x + 1, x + 2]), x => 2 * x + 1),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 5, 7, 9, 11, 13, 19, 21, 23])'));
-      async.done();
-    });
-  },
-  function test_compCombined(t) {
-    const async = t.startAsync('test_compCombined');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2]),
-        comp(
-          delay(x => -x),
-          x => many([x, x * 10]),
-          function*(x) {
-            yield x;
-            yield x - 1;
-          },
-          x => -x
-        ),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 2, 10, 11, 2, 3, 20, 21])'));
-      async.done();
-    });
-  },
-  function test_compCombinedFinal(t) {
-    const async = t.startAsync('test_compCombinedFinal');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2]),
-        comp(
-          delay(x => -x),
-          x => many([x, x * 10]),
-          function*(x) {
-            yield x;
-            yield final(x - 1);
-          },
-          x => -x
-        ),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, -2, 10, -11, 2, -3, 20, -21])'));
-      async.done();
-    });
-  },
-  function test_compAsFun(t) {
-    const async = t.startAsync('test_compAsFun');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2]),
-        asFun(
-          delay(x => -x),
-          x => many([x, x * 10]),
-          function*(x) {
-            yield x;
-            yield final(x - 1);
-          },
-          x => -x
-        ),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, -2, 10, -11, 2, -3, 20, -21])'));
-      async.done();
-    });
-  }
-]);

+ 0 - 53
tests/test_demo.js

@@ -1,53 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-const {fromIterable} = require('../utils/FromIterable');
-const {Transform} = require('stream');
-
-unit.add(module, [
-  function test_demo(t) {
-    const async = t.startAsync('test_demo');
-
-    const getTotalFromDatabaseByKey = async x =>
-      new Promise(resolve => {
-        setTimeout(() => {
-          resolve(Math.min(x % 10, 3));
-        }, 20);
-      });
-
-    const chain = new Chain([
-        // transforms a value
-        x => x * x,
-        // returns several values
-        x => [x - 1, x, x + 1],
-        // waits for an asynchronous operation
-        async x => await getTotalFromDatabaseByKey(x),
-        // returns multiple values with a generator
-        function*(x) {
-          for (let i = x; i > 0; --i) {
-            yield i;
-          }
-          return 0;
-        },
-        // filters out even values
-        x => (x % 2 ? x : null),
-        // uses an arbitrary transform stream
-        new Transform({
-          objectMode: true,
-          transform(x, _, callback) {
-            callback(null, x + 1);
-          }
-        })
-      ]),
-      output = [];
-    chain.on('data', data => output.push(data));
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [2, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2])'));
-      async.done();
-    });
-
-    fromIterable([1, 2, 3]).pipe(chain);
-  }
-]);

+ 0 - 24
tests/test_errors.js

@@ -1,24 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-
-unit.add(module, [
-  function test_errorsNoStreams(t) {
-    try {
-      const chain = new Chain([]);
-      t.test(false); // shouldn't be here
-    } catch (e) {
-      eval(t.TEST('e instanceof Error'));
-    }
-  },
-  function test_errorsWrongStreams(t) {
-    try {
-      const chain = new Chain([1]);
-      t.test(false); // shouldn't be here
-    } catch (e) {
-      eval(t.TEST('e instanceof Error'));
-    }
-  }
-]);

+ 0 - 82
tests/test_fold.js

@@ -1,82 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-const {streamToArray, delay} = require('./helpers');
-
-const {fromIterable} = require('../utils/FromIterable');
-const fold = require('../utils/fold');
-const scan = require('../utils/scan');
-const {reduce} = require('../utils/Reduce');
-
-unit.add(module, [
-  function test_fold(t) {
-    const async = t.startAsync('test_fold');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), fold((acc, x) => acc + x, 0), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [6])'));
-      async.done();
-    });
-  },
-  function test_foldAsync(t) {
-    const async = t.startAsync('test_foldAsync');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), fold(delay((acc, x) => acc + x), 0), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [6])'));
-      async.done();
-    });
-  },
-  function test_foldScan(t) {
-    const async = t.startAsync('test_foldScan');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), scan((acc, x) => acc + x, 0), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 3, 6])'));
-      async.done();
-    });
-  },
-  function test_foldScanAsync(t) {
-    const async = t.startAsync('test_foldScanAsync');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), scan(delay((acc, x) => acc + x), 0), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 3, 6])'));
-      async.done();
-    });
-  },
-  function test_foldReduce(t) {
-    const async = t.startAsync('test_foldReduce');
-
-    const r = reduce((acc, x) => acc + x, 0);
-
-    fromIterable([1, 2, 3]).pipe(r);
-
-    r.on('finish', () => {
-      eval(t.TEST('t.unify(r.accumulator, 6)'));
-      async.done();
-    });
-  },
-  function test_foldReduceAsync(t) {
-    const async = t.startAsync('test_foldReduceAsync');
-
-    const r = reduce({reducer: delay((acc, x) => acc + x), initial: 0});
-
-    fromIterable([1, 2, 3]).pipe(r);
-
-    r.on('finish', () => {
-      eval(t.TEST('t.unify(r.accumulator, 6)'));
-      async.done();
-    });
-  }
-]);

+ 0 - 204
tests/test_gen.js

@@ -1,204 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-const {streamToArray, delay} = require('./helpers');
-
-const {fromIterable} = require('../utils/FromIterable');
-const gen = require('../utils/gen');
-const asGen = require('../utils/asGen');
-
-const {none, final, many} = Chain;
-
-unit.add(module, [
-  function test_gen(t) {
-    const async = t.startAsync('test_gen');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), gen(x => x * x, x => 2 * x + 1), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 9, 19])'));
-      async.done();
-    });
-  },
-  function test_genFinal(t) {
-    const async = t.startAsync('test_genFinal');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2, 3]),
-        gen(x => x * x, x => final(x), x => 2 * x + 1),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 4, 9])'));
-      async.done();
-    });
-  },
-  function test_compNothing(t) {
-    const async = t.startAsync('test_compNothing');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), gen(x => x * x, () => none, x => 2 * x + 1), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [])'));
-      async.done();
-    });
-  },
-  function test_genEmpty(t) {
-    const async = t.startAsync('test_genEmpty');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), x => x * x, gen(), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 4, 9])'));
-      async.done();
-    });
-  },
-  function test_genAsync(t) {
-    const async = t.startAsync('test_genAsync');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), gen(delay(x => x * x), x => 2 * x + 1), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 9, 19])'));
-      async.done();
-    });
-  },
-  function test_genGenerator(t) {
-    const async = t.startAsync('test_genGenerator');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2, 3]),
-        gen(
-          x => x * x,
-          function*(x) {
-            yield x;
-            yield x + 1;
-            yield x + 2;
-          },
-          x => 2 * x + 1
-        ),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 5, 7, 9, 11, 13, 19, 21, 23])'));
-      async.done();
-    });
-  },
-  function test_genMany(t) {
-    const async = t.startAsync('test_genMany');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2, 3]),
-        gen(x => x * x, x => many([x, x + 1, x + 2]), x => 2 * x + 1),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 5, 7, 9, 11, 13, 19, 21, 23])'));
-      async.done();
-    });
-  },
-  function test_genCombined(t) {
-    const async = t.startAsync('test_genCombined');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2]),
-        gen(
-          delay(x => -x),
-          x => many([x, x * 10]),
-          function*(x) {
-            yield x;
-            yield x - 1;
-          },
-          x => -x
-        ),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 2, 10, 11, 2, 3, 20, 21])'));
-      async.done();
-    });
-  },
-  function test_genCombinedFinal(t) {
-    const async = t.startAsync('test_genCombinedFinal');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2]),
-        gen(
-          delay(x => -x),
-          x => many([x, x * 10]),
-          function*(x) {
-            yield x;
-            yield final(x - 1);
-          },
-          x => -x
-        ),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, -2, 10, -11, 2, -3, 20, -21])'));
-      async.done();
-    });
-  },
-  function test_genAsGen(t) {
-    const async = t.startAsync('test_genAsGen');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2]),
-        asGen(
-          delay(x => -x),
-          x => many([x, x * 10]),
-          function*(x) {
-            yield x;
-            yield final(x - 1);
-          },
-          x => -x
-        ),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, -2, 10, -11, 2, -3, 20, -21])'));
-      async.done();
-    });
-  },
-  function test_genAsAsyncGen(t) {
-    const async = t.startAsync('test_genAsAsyncGen');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2]),
-        asGen(
-          delay(x => -x),
-          x => many([x, x * 10]),
-          async function*(x) {
-            yield delay(x => x)(x);
-            yield delay(x => final(x - 1))(x);
-          },
-          x => -x
-        ),
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, -2, 10, -11, 2, -3, 20, -21])'));
-      async.done();
-    });
-  }
-]);

+ 0 - 96
tests/test_readWrite.js

@@ -1,96 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-const {streamToArray} = require('./helpers');
-const {fromIterable} = require('../utils/FromIterable');
-
-unit.add(module, [
-  function test_readWriteReadable(t) {
-    const async = t.startAsync('test_readWriteReadable');
-
-    const output1 = [],
-      output2 = [],
-      chain = new Chain([fromIterable([1, 2, 3]), x => x * x]);
-
-    chain.pipe(streamToArray(output1));
-
-    chain.on('data', value => output2.push(value));
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output1, [1, 4, 9])'));
-      eval(t.TEST('t.unify(output2, [1, 4, 9])'));
-      async.done();
-    });
-  },
-  function test_readWriteWritable(t) {
-    const async = t.startAsync('test_readWriteWritable');
-
-    const output = [],
-      chain = new Chain([x => x * x, streamToArray(output)]);
-
-    fromIterable([1, 2, 3]).pipe(chain);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 4, 9])'));
-      async.done();
-    });
-  },
-  function test_readWriteReadableWritable(t) {
-    const async = t.startAsync('test_readWriteReadableWritable');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), x => x * x, streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 4, 9])'));
-      async.done();
-    });
-  },
-  function test_readWriteSingleReadable(t) {
-    const async = t.startAsync('test_readWriteSingleReadable');
-
-    const output1 = [],
-      output2 = [],
-      chain = new Chain([fromIterable([1, 2, 3])]);
-
-    chain.pipe(streamToArray(output1));
-
-    chain.on('data', value => output2.push(value));
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output1, [1, 2, 3])'));
-      eval(t.TEST('t.unify(output2, [1, 2, 3])'));
-      async.done();
-    });
-  },
-  function test_readWriteSingleWritable(t) {
-    const async = t.startAsync('test_readWriteSingleWritable');
-
-    const output = [],
-      chain = new Chain([streamToArray(output)]);
-
-    fromIterable([1, 2, 3]).pipe(chain);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 2, 3])'));
-      async.done();
-    });
-  },
-  function test_readWritePipeable(t) {
-    const async = t.startAsync('test_readWritePipeable');
-
-    const output1 = [],
-      output2 = [],
-      chain = new Chain([fromIterable([1, 2, 3]), streamToArray(output1)]);
-
-    fromIterable([4, 5, 6])
-      .pipe(chain)
-      .pipe(streamToArray(output2));
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output1, [1, 2, 3])'));
-      eval(t.TEST('t.unify(output2, [])'));
-      async.done();
-    });
-  }
-]);

+ 0 - 124
tests/test_simple.js

@@ -1,124 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-const {streamToArray, delay} = require('./helpers');
-const {fromIterable} = require('../utils/FromIterable');
-const {Transform} = require('stream');
-
-unit.add(module, [
-  function test_simpleGeneric(t) {
-    const async = t.startAsync('test_simpleGeneric');
-
-    const chain = new Chain([x => x * x]),
-      output1 = [],
-      output2 = [];
-
-    fromIterable([1, 2, 3])
-      .pipe(chain)
-      .pipe(streamToArray(output1));
-
-    chain.on('data', value => output2.push(value));
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output1, [1, 4, 9])'));
-      eval(t.TEST('t.unify(output2, [1, 4, 9])'));
-      async.done();
-    });
-  },
-  function test_simpleGenerator(t) {
-    const async = t.startAsync('test_simpleGenerator');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2, 3]),
-        function*(x) {
-          yield x * x;
-          yield x * x * x;
-          yield 2 * x;
-        },
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 1, 2, 4, 8, 4, 9, 27, 6])'));
-      async.done();
-    });
-  },
-  function test_simpleAsync(t) {
-    const async = t.startAsync('test_simpleAsync');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), delay(x => x + 1), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [2, 3, 4])'));
-      async.done();
-    });
-  },
-  function test_simpleArray(t) {
-    const async = t.startAsync('test_simpleArray');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), x => [x * x, x * x * x, 2 * x], streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 1, 2, 4, 8, 4, 9, 27, 6])'));
-      async.done();
-    });
-  },
-  function test_simpleMany(t) {
-    const async = t.startAsync('test_simpleMany');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), x => Chain.many([x * x, x * x * x, 2 * x]), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 1, 2, 4, 8, 4, 9, 27, 6])'));
-      async.done();
-    });
-  },
-  function test_simpleChain(t) {
-    const async = t.startAsync('test_simpleChain');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), x => x * x, x => 2 * x + 1, streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 9, 19])'));
-      async.done();
-    });
-  },
-  function test_simpleStream(t) {
-    const async = t.startAsync('test_simpleStream');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2, 3]),
-        new Transform({
-          objectMode: true,
-          transform(x, _, callback) {
-            callback(null, x * x);
-          }
-        }),
-        x => 2 * x + 1,
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 9, 19])'));
-      async.done();
-    });
-  },
-  function test_simpleFactory(t) {
-    const async = t.startAsync('test_simpleChain');
-
-    const output = [],
-      chain = Chain.chain([fromIterable([1, 2, 3]), x => x * x, x => 2 * x + 1, streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 9, 19])'));
-      async.done();
-    });
-  }
-]);

+ 0 - 46
tests/test_skip.js

@@ -1,46 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-const {streamToArray, delay} = require('./helpers');
-
-const {fromIterable} = require('../utils/FromIterable');
-const skip = require('../utils/skip');
-const skipWhile = require('../utils/skipWhile');
-
-unit.add(module, [
-  function test_skip(t) {
-    const async = t.startAsync('test_skip');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3, 4, 5]), skip(2), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 4, 5])'));
-      async.done();
-    });
-  },
-  function test_skipWhile(t) {
-    const async = t.startAsync('test_skipWhile');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3, 4, 5]), skipWhile(x => x != 3), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 4, 5])'));
-      async.done();
-    });
-  },
-  function test_skipWhileAsync(t) {
-    const async = t.startAsync('test_skipWhileAsync');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3, 4, 5]), skipWhile(delay(x => x != 3)), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 4, 5])'));
-      async.done();
-    });
-  }
-]);

+ 0 - 57
tests/test_take.js

@@ -1,57 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-const {streamToArray, delay} = require('./helpers');
-
-const {fromIterable} = require('../utils/FromIterable');
-const take = require('../utils/take');
-const takeWhile = require('../utils/takeWhile');
-
-unit.add(module, [
-  function test_take(t) {
-    const async = t.startAsync('test_take');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3, 4, 5]), take(2), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 2])'));
-      async.done();
-    });
-  },
-  function test_takeWithSkip(t) {
-    const async = t.startAsync('test_takeWithSkip');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3, 4, 5]), take({n: 2, skip: 2}), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 4])'));
-      async.done();
-    });
-  },
-  function test_takeWhile(t) {
-    const async = t.startAsync('test_takeWhile');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3, 4, 5]), takeWhile(x => x != 3), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 2])'));
-      async.done();
-    });
-  },
-  function test_takeWhileAsync(t) {
-    const async = t.startAsync('test_takeWhileAsync');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3, 4, 5]), takeWhile(delay(x => x != 3)), streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 2])'));
-      async.done();
-    });
-  }
-]);

+ 0 - 73
tests/test_transducers.js

@@ -1,73 +0,0 @@
-'use strict';
-
-const unit = require('heya-unit');
-
-const Chain = require('../index');
-const {streamToArray} = require('./helpers');
-const {fromIterable} = require('../utils/FromIterable');
-
-unit.add(module, [
-  function test_transducers(t) {
-    const async = t.startAsync('test_transducers');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), [x => x * x, x => 2 * x + 1], streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 9, 19])'));
-      async.done();
-    });
-  },
-  function test_transducersFinal(t) {
-    const async = t.startAsync('test_transducersFinal');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2, 3]),
-        [x => x * x, x => Chain.final(x), x => 2 * x + 1],
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 4, 9])'));
-      async.done();
-    });
-  },
-  function test_transducersNothing(t) {
-    const async = t.startAsync('test_transducersNothing');
-
-    const output = [],
-      chain = new Chain([
-        fromIterable([1, 2, 3]),
-        [x => x * x, () => Chain.none, x => 2 * x + 1],
-        streamToArray(output)
-      ]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [])'));
-      async.done();
-    });
-  },
-  function test_transducersEmpty(t) {
-    const async = t.startAsync('test_transducersEmpty');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), x => x * x, [], streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [1, 4, 9])'));
-      async.done();
-    });
-  },
-  function test_transducersOne(t) {
-    const async = t.startAsync('test_transducersOne');
-
-    const output = [],
-      chain = new Chain([fromIterable([1, 2, 3]), x => x * x, [x => 2 * x + 1], streamToArray(output)]);
-
-    chain.on('end', () => {
-      eval(t.TEST('t.unify(output, [3, 9, 19])'));
-      async.done();
-    });
-  }
-]);

+ 0 - 23
tests/tests.js

@@ -1,23 +0,0 @@
-'use strict';
-
-const nodeVersion = /^v?(\d+)\./.exec(process.version);
-
-const unit = require('heya-unit');
-
-require('./test_FromIterable');
-
-require('./test_simple');
-require('./test_readWrite');
-require('./test_errors');
-
-require('./test_transducers');
-require('./test_comp');
-nodeVersion && +nodeVersion[1] >= 10 && require('./test_gen');
-
-require('./test_take');
-require('./test_skip');
-require('./test_fold');
-
-require('./test_demo');
-
-unit.run();

+ 0 - 94
utils/FromIterable.js

@@ -1,94 +0,0 @@
-'use strict';
-
-const {Readable} = require('stream');
-
-class FromIterable extends Readable {
-  constructor(options) {
-    super(Object.assign({}, options, {objectMode: true}));
-    this._iterable = null;
-    this._next = null;
-    if (options) {
-      'iterable' in options && (this._iterable = options.iterable);
-    }
-    !this._iterable && (this._read = this._readStop);
-  }
-
-  _read() {
-    if (Symbol.asyncIterator && typeof this._iterable[Symbol.asyncIterator] == 'function') {
-      this._next = this._iterable[Symbol.asyncIterator]();
-      this._iterable = null;
-      this._read = this._readNext;
-      this._readNext();
-      return;
-    }
-    if (Symbol.iterator && typeof this._iterable[Symbol.iterator] == 'function') {
-      this._next = this._iterable[Symbol.iterator]();
-      this._iterable = null;
-      this._read = this._readNext;
-      this._readNext();
-      return;
-    }
-    if (typeof this._iterable.next == 'function') {
-      this._next = this._iterable;
-      this._iterable = null;
-      this._read = this._readNext;
-      this._readNext();
-      return;
-    }
-    const result = this._iterable();
-    this._iterable = null;
-    if (result && typeof result.then == 'function') {
-      result.then(value => this.push(value), error => this.emit('error', error));
-      this._read = this._readStop;
-      return;
-    }
-    if (result && typeof result.next == 'function') {
-      this._next = result;
-      this._read = this._readNext;
-      this._readNext();
-      return;
-    }
-    this.push(result);
-    this._read = this._readStop;
-  }
-
-  _readNext() {
-    for (;;) {
-      const result = this._next.next();
-      if (result && typeof result.then == 'function') {
-        result.then(
-          value => {
-            if (value.done || value.value === null) {
-              this.push(null);
-              this._next = null;
-              this._read = this._readStop;
-            } else {
-              this.push(value.value);
-            }
-          },
-          error => this.emit('error', error)
-        );
-        break;
-      }
-      if (result.done || result.value === null) {
-        this.push(null);
-        this._next = null;
-        this._read = this._readStop;
-        break;
-      }
-      if (!this.push(result.value)) break;
-    }
-  }
-
-  _readStop() {
-    this.push(null);
-  }
-
-  static make(iterable) {
-    return new FromIterable(typeof iterable == 'object' && iterable.iterable ? iterable : {iterable});
-  }
-}
-FromIterable.fromIterable = FromIterable.make;
-FromIterable.make.Constructor = FromIterable;
-
-module.exports = FromIterable;

+ 0 - 40
utils/Reduce.js

@@ -1,40 +0,0 @@
-'use strict';
-
-const {Writable} = require('stream');
-
-const defaultInitial = 0;
-const defaultReducer = (acc, value) => value;
-
-class Reduce extends Writable {
-  constructor(options) {
-    super(Object.assign({}, options, {objectMode: true}));
-    this.accumulator = defaultInitial;
-    this._reducer = defaultReducer;
-    if (options) {
-      'initial' in options && (this.accumulator = options.initial);
-      'reducer' in options && (this._reducer = options.reducer);
-    }
-  }
-  _write(chunk, encoding, callback) {
-    const result = this._reducer.call(this, this.accumulator, chunk);
-    if (result && typeof result.then == 'function') {
-      result.then(
-        value => {
-          this.accumulator = value;
-          callback(null);
-        },
-        error => callback(error)
-      );
-    } else {
-      this.accumulator = result;
-      callback(null);
-    }
-  }
-  static make(reducer, initial) {
-    return new Reduce(typeof reducer == 'object' ? reducer : {reducer, initial});
-  }
-}
-Reduce.reduce = Reduce.make;
-Reduce.make.Constructor = Reduce;
-
-module.exports = Reduce;

+ 0 - 85
utils/asFun.js

@@ -1,85 +0,0 @@
-'use strict';
-
-const {none, final, isFinal, getFinalValue, many, isMany, getManyValues} = require('../defs');
-
-const next = async (value, fns, index, push) => {
-  for (let i = index; i <= fns.length; ++i) {
-    if (value && typeof value.then == 'function') {
-      // thenable
-      value = await value;
-    }
-    if (value === none) break;
-    if (isFinal(value)) {
-      const val = getFinalValue(value);
-      val !== none && push(val);
-      break;
-    }
-    if (isMany(value)) {
-      const values = getManyValues(value);
-      if (i == fns.length) {
-        values.forEach(val => push(val));
-      } else {
-        for (let j = 0; j < values.length; ++j) {
-          await next(values[j], fns, i, push);
-        }
-      }
-      break;
-    }
-    if (value && typeof value.next == 'function') {
-      // generator
-      for (;;) {
-        let data = value.next();
-        if (data && typeof data.then == 'function') {
-          data = await data;
-        }
-        if (data.done) break;
-        if (i == fns.length) {
-          push(data.value);
-        } else {
-          await next(data.value, fns, i, push);
-        }
-      }
-      break;
-    }
-    if (i == fns.length) {
-      push(value);
-      break;
-    }
-    value = fns[i](value);
-  }
-};
-
-const nop = () => {};
-
-const asFun = (...fns) => {
-  fns = fns.filter(fn => fn);
-  if (!fns.length) return nop;
-  if (Symbol.asyncIterator && fns[0][Symbol.asyncIterator]) {
-    fns[0] = fns[0][Symbol.asyncIterator];
-  } else if (Symbol.iterator && fns[0][Symbol.iterator]) {
-    fns[0] = fns[0][Symbol.iterator];
-  }
-  return async value => {
-    const results = [];
-    await next(value, fns, 0, value => results.push(value));
-    switch (results.length) {
-      case 0:
-        return none;
-      case 1:
-        return results[0];
-    }
-    return many(results);
-  };
-};
-
-asFun.next = next;
-
-asFun.none = none;
-asFun.final = final;
-asFun.isFinal = isFinal;
-asFun.getFinalValue = getFinalValue;
-asFun.many = many;
-asFun.isMany = isMany;
-asFun.getManyValues = getManyValues;
-
-module.exports = asFun;

+ 0 - 77
utils/asGen.js

@@ -1,77 +0,0 @@
-'use strict';
-
-const {none, final, isFinal, getFinalValue, many, isMany, getManyValues} = require('../defs');
-
-const next = async function*(value, fns, index) {
-  for (let i = index; i <= fns.length; ++i) {
-    if (value && typeof value.then == 'function') {
-      // thenable
-      value = await value;
-    }
-    if (value === none) break;
-    if (isFinal(value)) {
-      const val = getFinalValue(value);
-      if (val !== none) yield val;
-      break;
-    }
-    if (isMany(value)) {
-      const values = getManyValues(value);
-      if (i == fns.length) {
-        yield* values;
-      } else {
-        for (let j = 0; j < values.length; ++j) {
-          yield* next(values[j], fns, i);
-        }
-      }
-      break;
-    }
-    if (value && typeof value.next == 'function') {
-      // generator
-      for (;;) {
-        let data = value.next();
-        if (data && typeof data.then == 'function') {
-          data = await data;
-        }
-        if (data.done) break;
-        if (i == fns.length) {
-          yield data.value;
-        } else {
-          yield* next(data.value, fns, i);
-        }
-      }
-      break;
-    }
-    if (i == fns.length) {
-      yield value;
-      break;
-    }
-    value = fns[i](value);
-  }
-};
-
-const nop = async function*() {};
-
-const asGen = (...fns) => {
-  fns = fns.filter(fn => fn);
-  if (!fns.length) return nop;
-  if (Symbol.asyncIterator && fns[0][Symbol.asyncIterator]) {
-    fns[0] = fns[0][Symbol.asyncIterator];
-  } else if (Symbol.iterator && fns[0][Symbol.iterator]) {
-    fns[0] = fns[0][Symbol.iterator];
-  }
-  return async function*(value) {
-    yield* next(value, fns, 0);
-  };
-};
-
-asGen.next = next;
-
-asGen.none = none;
-asGen.final = final;
-asGen.isFinal = isFinal;
-asGen.getFinalValue = getFinalValue;
-asGen.many = many;
-asGen.isMany = isMany;
-asGen.getManyValues = getManyValues;
-
-module.exports = asGen;

+ 0 - 20
utils/comp.js

@@ -1,20 +0,0 @@
-'use strict';
-
-const {Transform} = require('stream');
-const {next} = require('./asFun');
-const {sanitize} = require('../index');
-
-const comp = (...fns) => {
-  fns = fns.filter(fn => fn);
-  return fns.length
-    ? new Transform({
-        writableObjectMode: true,
-        readableObjectMode: true,
-        transform(chunk, encoding, callback) {
-          next(chunk, fns, 0, value => sanitize(value, this)).then(() => callback(null), error => callback(error));
-        }
-      })
-    : null;
-};
-
-module.exports = comp;

+ 0 - 43
utils/fold.js

@@ -1,43 +0,0 @@
-'use strict';
-
-const {Transform} = require('stream');
-
-const defaultInitial = 0;
-const defaultReducer = (acc, value) => value;
-
-class Fold extends Transform {
-  constructor(options) {
-    super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: true}));
-    this._accumulator = defaultInitial;
-    this._reducer = defaultReducer;
-    if (options) {
-      'initial' in options && (this._accumulator = options.initial);
-      'reducer' in options && (this._reducer = options.reducer);
-    }
-  }
-  _transform(chunk, encoding, callback) {
-    const result = this._reducer.call(this, this._accumulator, chunk);
-    if (result && typeof result.then == 'function') {
-      result.then(
-        value => {
-          this._accumulator = value;
-          callback(null);
-        },
-        error => callback(error)
-      );
-    } else {
-      this._accumulator = result;
-      callback(null);
-    }
-  }
-  _final(callback) {
-    this.push(this._accumulator);
-    callback(null);
-  }
-  static make(reducer, initial) {
-    return new Fold(typeof reducer == 'object' ? reducer : {reducer, initial});
-  }
-}
-Fold.make.Constructor = Fold;
-
-module.exports = Fold.make;

+ 0 - 24
utils/gen.js

@@ -1,24 +0,0 @@
-'use strict';
-
-const {Transform} = require('stream');
-const {next} = require('./asGen');
-const {sanitize} = require('../index');
-
-const gen = (...fns) => {
-  fns = fns.filter(fn => fn);
-  return fns.length
-    ? new Transform({
-        writableObjectMode: true,
-        readableObjectMode: true,
-        transform(chunk, encoding, callback) {
-          (async () => {
-            for await (let value of next(chunk, fns, 0)) {
-              sanitize(value, this);
-            }
-          })().then(() => callback(null), error => callback(error));
-        }
-      })
-    : null;
-};
-
-module.exports = gen;

+ 0 - 41
utils/scan.js

@@ -1,41 +0,0 @@
-'use strict';
-
-const {Transform} = require('stream');
-
-const defaultInitial = 0;
-const defaultReducer = (acc, value) => value;
-
-class Scan extends Transform {
-  constructor(options) {
-    super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: true}));
-    this._accumulator = defaultInitial;
-    this._reducer = defaultReducer;
-    if (options) {
-      'initial' in options && (this._accumulator = options.initial);
-      'reducer' in options && (this._reducer = options.reducer);
-    }
-  }
-  _transform(chunk, encoding, callback) {
-    const result = this._reducer.call(this, this._accumulator, chunk);
-    if (result && typeof result.then == 'function') {
-      result.then(
-        value => {
-          this._accumulator = value;
-          this.push(this._accumulator);
-          callback(null);
-        },
-        error => callback(error)
-      );
-    } else {
-      this._accumulator = result;
-      this.push(this._accumulator);
-      callback(null);
-    }
-  }
-  static make(reducer, initial) {
-    return new Scan(typeof reducer == 'object' ? reducer : {reducer, initial});
-  }
-}
-Scan.make.Constructor = Scan;
-
-module.exports = Scan.make;

+ 0 - 32
utils/skip.js

@@ -1,32 +0,0 @@
-'use strict';
-
-const {Transform} = require('stream');
-
-class Skip extends Transform {
-  constructor(options) {
-    super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: true}));
-    this._n = 0;
-    if (options) {
-      'n' in options && (this._n = options.n);
-    }
-    if (this._n <= 0) {
-      this._transform = this._passThrough;
-    }
-  }
-  _transform(chunk, encoding, callback) {
-    if (--this._n <= 0) {
-      this._transform = this._passThrough;
-    }
-    callback(null);
-  }
-  _passThrough(chunk, encoding, callback) {
-    this.push(chunk);
-    callback(null);
-  }
-  static make(n) {
-    return new Skip(typeof n == 'object' ? n : {n});
-  }
-}
-Skip.make.Constructor = Skip;
-
-module.exports = Skip.make;

+ 0 - 46
utils/skipWhile.js

@@ -1,46 +0,0 @@
-'use strict';
-
-const {Transform} = require('stream');
-
-const alwaysFalse = () => false;
-
-class SkipWhile extends Transform {
-  constructor(options) {
-    super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: true}));
-    this._condition = alwaysFalse;
-    if (options) {
-      'condition' in options && (this._condition = options.condition);
-    }
-  }
-  _transform(chunk, encoding, callback) {
-    const result = this._condition.call(this, chunk);
-    if (result && typeof result.then == 'function') {
-      result.then(
-        flag => {
-          if (!flag) {
-            this._transform = this._passThrough;
-            this.push(chunk);
-          }
-          callback(null);
-        },
-        error => callback(error)
-      );
-    } else {
-      if (!result) {
-        this._transform = this._passThrough;
-        this.push(chunk);
-      }
-      callback(null);
-    }
-  }
-  _passThrough(chunk, encoding, callback) {
-    this.push(chunk);
-    callback(null);
-  }
-  static make(condition) {
-    return new SkipWhile(typeof condition == 'object' ? condition : {condition});
-  }
-}
-SkipWhile.make.Constructor = SkipWhile;
-
-module.exports = SkipWhile.make;

+ 0 - 39
utils/take.js

@@ -1,39 +0,0 @@
-'use strict';
-
-const {Transform} = require('stream');
-
-class Take extends Transform {
-  constructor(options) {
-    super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: true}));
-    this._n = this._skip = 0;
-    if (options) {
-      'n' in options && (this._n = options.n);
-      'skip' in options && (this._skip = options.skip);
-    }
-    if (this._skip <= 0) {
-      this._transform = this._n > 0 ? this._countValues : this._doNothing;
-    }
-  }
-  _transform(chunk, encoding, callback) {
-    if (--this._skip <= 0) {
-      this._transform = this._n > 0 ? this._countValues : this._doNothing;
-    }
-    callback(null);
-  }
-  _countValues(chunk, encoding, callback) {
-    if (--this._n <= 0) {
-      this._transform = this._doNothing;
-    }
-    this.push(chunk);
-    callback(null);
-  }
-  _doNothing(chunk, encoding, callback) {
-    callback(null);
-  }
-  static make(n) {
-    return new Take(typeof n == 'object' ? n : {n});
-  }
-}
-Take.make.Constructor = Take;
-
-module.exports = Take.make;

+ 0 - 47
utils/takeWhile.js

@@ -1,47 +0,0 @@
-'use strict';
-
-const {Transform} = require('stream');
-
-const alwaysTrue = () => true;
-
-class TakeWhile extends Transform {
-  constructor(options) {
-    super(Object.assign({}, options, {writableObjectMode: true, readableObjectMode: true}));
-    this._condition = alwaysTrue;
-    if (options) {
-      'condition' in options && (this._condition = options.condition);
-    }
-  }
-  _transform(chunk, encoding, callback) {
-    const result = this._condition.call(this, chunk);
-    if (result && typeof result.then == 'function') {
-      result.then(
-        flag => {
-          if (flag) {
-            this.push(chunk);
-          } else {
-            this._transform = this._doNothing;
-          }
-          callback(null);
-        },
-        error => callback(error)
-      );
-    } else {
-      if (result) {
-        this.push(chunk);
-      } else {
-        this._transform = this._doNothing;
-      }
-      callback(null);
-    }
-  }
-  _doNothing(chunk, encoding, callback) {
-    callback(null);
-  }
-  static make(condition) {
-    return new TakeWhile(typeof condition == 'object' ? condition : {condition});
-  }
-}
-TakeWhile.make.Constructor = TakeWhile;
-
-module.exports = TakeWhile.make;