0.3.0 Release
This commit is contained in:
parent
5074a991fc
commit
e1796d5896
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
.vscode
|
.vscode
|
||||||
node_modules
|
node_modules
|
||||||
**/*.js
|
dist
|
||||||
**/*.js.map
|
sample_output
|
@ -1,5 +0,0 @@
|
|||||||
.vscode
|
|
||||||
node_modules
|
|
||||||
**/*.ts
|
|
||||||
tsconfig.json
|
|
||||||
tslint.json
|
|
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
79
README.md
79
README.md
@ -1,6 +1,79 @@
|
|||||||
# mysah
|
# mysah
|
||||||
ES6 Promise library
|
|
||||||
|
|
||||||
## State of the project
|
Promise, Stream and EventEmitter utils for Node.js
|
||||||
|
|
||||||
**mysah** is in its infancy. More development to come!
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add mysah
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
const stream = require("mysah/stream");
|
||||||
|
const { once, sleep } = require("mysah");
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const collector = stream
|
||||||
|
.concat(
|
||||||
|
stream.fromArray(["a", "b", "c"]),
|
||||||
|
stream.fromArray(["d", "e"])
|
||||||
|
)
|
||||||
|
.pipe(stream.collect({ objectMode: true }));
|
||||||
|
|
||||||
|
const collected = await once(collector, "data");
|
||||||
|
console.log(collected); // [ 'a', 'b', 'c', 'd', 'e' ]
|
||||||
|
await sleep(1000); // Resolve after one second
|
||||||
|
}
|
||||||
|
main();
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### mysah/stream
|
||||||
|
|
||||||
|
```ts
|
||||||
|
/**
|
||||||
|
* Convert an array into a readable stream of its elements
|
||||||
|
* @param array The array of elements to stream
|
||||||
|
*/
|
||||||
|
export declare function fromArray(array: any[]): NodeJS.ReadableStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a ReadWrite stream that collects streamed objects or bytes into an array or buffer
|
||||||
|
* @param options
|
||||||
|
* @param options.objectMode Whether this stream should behave as a stream of objects
|
||||||
|
*/
|
||||||
|
export declare function collect({
|
||||||
|
objectMode,
|
||||||
|
}?: {
|
||||||
|
objectMode?: boolean | undefined;
|
||||||
|
}): NodeJS.ReadWriteStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a stream of readable streams concatenated together
|
||||||
|
* @param streams The readable streams to concatenate
|
||||||
|
*/
|
||||||
|
export declare function concat(
|
||||||
|
...streams: NodeJS.ReadableStream[]
|
||||||
|
): NodeJS.ReadableStream;
|
||||||
|
```
|
||||||
|
|
||||||
|
### mysah
|
||||||
|
|
||||||
|
```ts
|
||||||
|
/**
|
||||||
|
* Resolve after the given delay in milliseconds
|
||||||
|
*
|
||||||
|
* @param ms - The number of milliseconds to wait
|
||||||
|
*/
|
||||||
|
export declare function sleep(ms: number): Promise<{}>;
|
||||||
|
/**
|
||||||
|
* Resolve once the given event emitter emits the specified event
|
||||||
|
*
|
||||||
|
* @param emitter - The event emitter to watch
|
||||||
|
* @param event - The event to watch
|
||||||
|
*/
|
||||||
|
export declare function once<T>(emitter: NodeJS.EventEmitter, event: string): Promise<T>;
|
||||||
|
```
|
||||||
|
31
index.ts
31
index.ts
@ -1,31 +0,0 @@
|
|||||||
const es6Promisify = require('es6-promisify');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a promise that resolves once the given event emitter emits the specified event
|
|
||||||
*
|
|
||||||
* @param {NodeJS.EventEmitter} emitter - The event emitter to watch
|
|
||||||
* @param {string} event - The event to watch
|
|
||||||
* @returns {Promise<{}>} - The promise that resolves once the given emitter emits the specified evnet
|
|
||||||
*/
|
|
||||||
export function once(emitter: NodeJS.EventEmitter, event: string) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
emitter.once(event, result => {
|
|
||||||
resolve(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform callback-based function -- func(arg1, arg2 .. argN, callback) -- into
|
|
||||||
* an ES6-compatible Promise. Promisify provides a default callback of the form (error, result)
|
|
||||||
* and rejects when `error` is truthy. You can also supply settings object as the second argument.
|
|
||||||
*
|
|
||||||
* @param {function} original - The function to promisify
|
|
||||||
* @param {object} [settings] - Settings object
|
|
||||||
* @param {object} settings.thisArg - A `this` context to use. If not set, assume `settings` _is_ `thisArg`
|
|
||||||
* @param {bool} settings.multiArgs - Should multiple arguments be returned as an array?
|
|
||||||
* @returns {function} A promisified version of `original`
|
|
||||||
*/
|
|
||||||
export function promisify(original: Function, settings?: Object): Function {
|
|
||||||
return es6Promisify(original, settings);
|
|
||||||
};
|
|
68
package.json
68
package.json
@ -1,22 +1,60 @@
|
|||||||
{
|
{
|
||||||
"name": "mysah",
|
"name": "mysah",
|
||||||
"version": "0.1.0",
|
"version": "0.3.0",
|
||||||
"description": "ES6 Promise library",
|
"description": "Promise, Stream and EventEmitter utils for Node.js",
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ES6",
|
"promise",
|
||||||
"Promise",
|
"stream",
|
||||||
"library"
|
"event emitter",
|
||||||
|
"utils"
|
||||||
],
|
],
|
||||||
"author": "Sami Turcotte",
|
"author": {
|
||||||
"license": "MIT",
|
"name": "Sami Turcotte",
|
||||||
"devDependencies": {
|
"email": "samiturcotte@gmail.com"
|
||||||
"@types/node": "^7.0.8"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"license": "MIT",
|
||||||
"es6-promisify": "^5.0.0"
|
"main": "index.js",
|
||||||
|
"types": "dist/**/*.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"url": "git@github.com:Wenzil/mysah.git",
|
||||||
|
"type": "git"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "ava",
|
||||||
|
"lint": "tslint -p tsconfig.json",
|
||||||
|
"validate:tslint": "tslint-config-prettier-check ./tslint.json",
|
||||||
|
"prepublishOnly": "yarn lint && yarn test && yarn tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/chai": "^4.1.7",
|
||||||
|
"@types/node": "^10.12.10",
|
||||||
|
"ava": "^1.0.0-rc.2",
|
||||||
|
"chai": "^4.2.0",
|
||||||
|
"prettier": "^1.14.3",
|
||||||
|
"ts-node": "^7.0.1",
|
||||||
|
"tslint": "^5.11.0",
|
||||||
|
"tslint-config-prettier": "^1.16.0",
|
||||||
|
"tslint-plugin-prettier": "^2.0.1",
|
||||||
|
"typescript": "^3.1.6"
|
||||||
|
},
|
||||||
|
"ava": {
|
||||||
|
"files": [
|
||||||
|
"src/**/*.spec.ts"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"compileEnhancements": false,
|
||||||
|
"failWithoutAssertions": false,
|
||||||
|
"extensions": [
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"require": [
|
||||||
|
"ts-node/register/transpile-only"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
samples/concat_files.js
Normal file
21
samples/concat_files.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const stream = require("mysah/stream");
|
||||||
|
|
||||||
|
const sourceFile1 = path.join(process.cwd(), "package.json");
|
||||||
|
const sourceFile2 = path.join(process.cwd(), "README.md");
|
||||||
|
const outputDir = path.join(process.cwd(), "sample_output");
|
||||||
|
const outputFile = path.join(outputDir, "concat_files.txt");
|
||||||
|
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concat two source files together into one
|
||||||
|
stream
|
||||||
|
.concat(
|
||||||
|
fs.createReadStream(sourceFile1),
|
||||||
|
stream.fromArray(["\n"]),
|
||||||
|
fs.createReadStream(sourceFile2),
|
||||||
|
)
|
||||||
|
.pipe(fs.createWriteStream(outputFile));
|
29
src/index.spec.ts
Normal file
29
src/index.spec.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import test from "ava";
|
||||||
|
import { expect } from "chai";
|
||||||
|
import { once, sleep } from "./";
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
|
const TimingErrorMarginMs = 50;
|
||||||
|
|
||||||
|
test("sleep() resolves after the specified delay in milliseconds", async t => {
|
||||||
|
const before = Date.now();
|
||||||
|
await sleep(200);
|
||||||
|
const after = Date.now();
|
||||||
|
|
||||||
|
expect(after - before).gte(200);
|
||||||
|
expect(after - before).closeTo(200, TimingErrorMarginMs);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("once() resolves only after the specified event is emitted", async t => {
|
||||||
|
const emitter = new EventEmitter();
|
||||||
|
const before = Date.now();
|
||||||
|
emitter.emit("noise", "is ignored");
|
||||||
|
setTimeout(() => emitter.emit("done", "some-result"), 200);
|
||||||
|
|
||||||
|
const result = await once(emitter, "done");
|
||||||
|
const after = Date.now();
|
||||||
|
|
||||||
|
expect(result).to.equal("some-result");
|
||||||
|
expect(after - before).gte(200);
|
||||||
|
expect(after - before).closeTo(200, TimingErrorMarginMs);
|
||||||
|
});
|
27
src/index.ts
Normal file
27
src/index.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Resolve after the given delay in milliseconds
|
||||||
|
*
|
||||||
|
* @param ms - The number of milliseconds to wait
|
||||||
|
*/
|
||||||
|
export function sleep(ms: number) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve once the given event emitter emits the specified event
|
||||||
|
*
|
||||||
|
* @param emitter - The event emitter to watch
|
||||||
|
* @param event - The event to watch
|
||||||
|
*/
|
||||||
|
export function once<T>(
|
||||||
|
emitter: NodeJS.EventEmitter,
|
||||||
|
event: string,
|
||||||
|
): Promise<T> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
emitter.once(event, result => {
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
350
src/stream.spec.ts
Normal file
350
src/stream.spec.ts
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
import test from "ava";
|
||||||
|
import { expect } from "chai";
|
||||||
|
import { fromArray, collect, concat } from "./stream";
|
||||||
|
import { Readable } from "stream";
|
||||||
|
|
||||||
|
test.cb("fromArray() streams array elements in flowing mode", t => {
|
||||||
|
t.plan(3);
|
||||||
|
const elements = ["a", "b", "c"];
|
||||||
|
const stream = fromArray(elements);
|
||||||
|
let i = 0;
|
||||||
|
stream
|
||||||
|
.on("data", element => {
|
||||||
|
expect(element).to.equal(elements[i]);
|
||||||
|
t.pass();
|
||||||
|
i++;
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.cb("fromArray() streams array elements in paused mode", t => {
|
||||||
|
t.plan(3);
|
||||||
|
const elements = ["a", "b", "c"];
|
||||||
|
const stream = fromArray(elements);
|
||||||
|
let i = 0;
|
||||||
|
stream
|
||||||
|
.on("readable", () => {
|
||||||
|
let element = stream.read();
|
||||||
|
while (element !== null) {
|
||||||
|
expect(element).to.equal(elements[i]);
|
||||||
|
t.pass();
|
||||||
|
i++;
|
||||||
|
element = stream.read();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.cb("fromArray() ends immediately if there are no array elements", t => {
|
||||||
|
t.plan(0);
|
||||||
|
fromArray([])
|
||||||
|
.on("data", () => t.fail())
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"collect() collects streamed elements into an array (object, flowing mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(1);
|
||||||
|
const source = new Readable({ objectMode: true });
|
||||||
|
|
||||||
|
source
|
||||||
|
.pipe(collect({ objectMode: true }))
|
||||||
|
.on("data", collected => {
|
||||||
|
expect(collected).to.deep.equal(["a", "b", "c"]);
|
||||||
|
t.pass();
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source.push("a");
|
||||||
|
source.push("b");
|
||||||
|
source.push("c");
|
||||||
|
source.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"collect() collects streamed elements into an array (object, paused mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(1);
|
||||||
|
const source = new Readable({ objectMode: true });
|
||||||
|
const collector = source.pipe(collect({ objectMode: true }));
|
||||||
|
|
||||||
|
collector
|
||||||
|
.on("readable", () => {
|
||||||
|
let collected = collector.read();
|
||||||
|
while (collected !== null) {
|
||||||
|
expect(collected).to.deep.equal(["a", "b", "c"]);
|
||||||
|
t.pass();
|
||||||
|
collected = collector.read();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source.push("a");
|
||||||
|
source.push("b");
|
||||||
|
source.push("c");
|
||||||
|
source.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"collect() collects streamed bytes into a buffer (non-object, flowing mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(1);
|
||||||
|
const source = new Readable({ objectMode: false });
|
||||||
|
|
||||||
|
source
|
||||||
|
.pipe(collect())
|
||||||
|
.on("data", collected => {
|
||||||
|
expect(collected).to.deep.equal(Buffer.from("abc"));
|
||||||
|
t.pass();
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source.push("a");
|
||||||
|
source.push("b");
|
||||||
|
source.push("c");
|
||||||
|
source.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"collect() collects streamed bytes into a buffer (non-object, paused mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(1);
|
||||||
|
const source = new Readable({ objectMode: false });
|
||||||
|
const collector = source.pipe(collect({ objectMode: false }));
|
||||||
|
collector
|
||||||
|
.on("readable", () => {
|
||||||
|
let collected = collector.read();
|
||||||
|
while (collected !== null) {
|
||||||
|
expect(collected).to.deep.equal(Buffer.from("abc"));
|
||||||
|
t.pass();
|
||||||
|
collected = collector.read();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source.push("a");
|
||||||
|
source.push("b");
|
||||||
|
source.push("c");
|
||||||
|
source.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"collect() emits an empty array if the source was empty (object mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(1);
|
||||||
|
const source = new Readable({ objectMode: true });
|
||||||
|
const collector = source.pipe(collect({ objectMode: true }));
|
||||||
|
collector
|
||||||
|
.on("data", collected => {
|
||||||
|
expect(collected).to.deep.equal([]);
|
||||||
|
t.pass();
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"collect() emits nothing if the source was empty (non-object mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(0);
|
||||||
|
const source = new Readable({ objectMode: false });
|
||||||
|
const collector = source.pipe(collect({ objectMode: false }));
|
||||||
|
collector
|
||||||
|
.on("data", () => t.fail())
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"concat() concatenates multiple readable streams (object, flowing mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(6);
|
||||||
|
const source1 = new Readable({ objectMode: true });
|
||||||
|
const source2 = new Readable({ objectMode: true });
|
||||||
|
const expectedElements = ["a", "b", "c", "d", "e", "f"];
|
||||||
|
let i = 0;
|
||||||
|
concat(source1, source2)
|
||||||
|
.on("data", element => {
|
||||||
|
expect(element).to.equal(expectedElements[i]);
|
||||||
|
t.pass();
|
||||||
|
i++;
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source1.push("a");
|
||||||
|
source2.push("d");
|
||||||
|
source1.push("b");
|
||||||
|
source2.push("e");
|
||||||
|
source1.push("c");
|
||||||
|
source2.push("f");
|
||||||
|
source2.push(null);
|
||||||
|
source1.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"concat() concatenates multiple readable streams (object, paused mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(6);
|
||||||
|
const source1 = new Readable({ objectMode: true });
|
||||||
|
const source2 = new Readable({ objectMode: true });
|
||||||
|
const expectedElements = ["a", "b", "c", "d", "e", "f"];
|
||||||
|
let i = 0;
|
||||||
|
const concatenation = concat(source1, source2)
|
||||||
|
.on("readable", () => {
|
||||||
|
let element = concatenation.read();
|
||||||
|
while (element !== null) {
|
||||||
|
expect(element).to.equal(expectedElements[i]);
|
||||||
|
t.pass();
|
||||||
|
i++;
|
||||||
|
element = concatenation.read();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source1.push("a");
|
||||||
|
source2.push("d");
|
||||||
|
source1.push("b");
|
||||||
|
source2.push("e");
|
||||||
|
source1.push("c");
|
||||||
|
source2.push("f");
|
||||||
|
source2.push(null);
|
||||||
|
source1.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"concat() concatenates multiple readable streams (non-object, flowing mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(6);
|
||||||
|
const source1 = new Readable({ objectMode: false });
|
||||||
|
const source2 = new Readable({ objectMode: false });
|
||||||
|
const expectedElements = ["a", "b", "c", "d", "e", "f"];
|
||||||
|
let i = 0;
|
||||||
|
concat(source1, source2)
|
||||||
|
.on("data", element => {
|
||||||
|
expect(element).to.deep.equal(Buffer.from(expectedElements[i]));
|
||||||
|
t.pass();
|
||||||
|
i++;
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source1.push("a");
|
||||||
|
source2.push("d");
|
||||||
|
source1.push("b");
|
||||||
|
source2.push("e");
|
||||||
|
source1.push("c");
|
||||||
|
source2.push("f");
|
||||||
|
source2.push(null);
|
||||||
|
source1.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"concat() concatenates multiple readable streams (non-object, paused mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(6);
|
||||||
|
const source1 = new Readable({ objectMode: false });
|
||||||
|
const source2 = new Readable({ objectMode: false });
|
||||||
|
const expectedElements = ["a", "b", "c", "d", "e", "f"];
|
||||||
|
let i = 0;
|
||||||
|
const concatenation = concat(source1, source2)
|
||||||
|
.on("readable", () => {
|
||||||
|
let element = concatenation.read();
|
||||||
|
while (element !== null) {
|
||||||
|
expect(element).to.deep.equal(
|
||||||
|
Buffer.from(expectedElements[i]),
|
||||||
|
);
|
||||||
|
t.pass();
|
||||||
|
i++;
|
||||||
|
element = concatenation.read();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source1.push("a");
|
||||||
|
source2.push("d");
|
||||||
|
source1.push("b");
|
||||||
|
source2.push("e");
|
||||||
|
source1.push("c");
|
||||||
|
source2.push("f");
|
||||||
|
source2.push(null);
|
||||||
|
source1.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb("concat() concatenates a single readable stream (object mode)", t => {
|
||||||
|
t.plan(3);
|
||||||
|
const source = new Readable({ objectMode: true });
|
||||||
|
const expectedElements = ["a", "b", "c", "d", "e", "f"];
|
||||||
|
let i = 0;
|
||||||
|
concat(source)
|
||||||
|
.on("data", element => {
|
||||||
|
expect(element).to.equal(expectedElements[i]);
|
||||||
|
t.pass();
|
||||||
|
i++;
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source.push("a");
|
||||||
|
source.push("b");
|
||||||
|
source.push("c");
|
||||||
|
source.push(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.cb(
|
||||||
|
"concat() concatenates a single readable stream (non-object mode)",
|
||||||
|
t => {
|
||||||
|
t.plan(3);
|
||||||
|
const source = new Readable({ objectMode: false });
|
||||||
|
const expectedElements = ["a", "b", "c", "d", "e", "f"];
|
||||||
|
let i = 0;
|
||||||
|
concat(source)
|
||||||
|
.on("data", element => {
|
||||||
|
expect(element).to.deep.equal(Buffer.from(expectedElements[i]));
|
||||||
|
t.pass();
|
||||||
|
i++;
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
|
||||||
|
source.push("a");
|
||||||
|
source.push("b");
|
||||||
|
source.push("c");
|
||||||
|
source.push(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.cb("concat() concatenates empty list of readable streams", t => {
|
||||||
|
t.plan(0);
|
||||||
|
concat()
|
||||||
|
.pipe(collect())
|
||||||
|
.on("data", _ => {
|
||||||
|
t.fail();
|
||||||
|
})
|
||||||
|
.on("error", t.end)
|
||||||
|
.on("end", t.end);
|
||||||
|
});
|
83
src/stream.ts
Normal file
83
src/stream.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { Transform, Readable } from "stream";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an array into a readable stream of its elements
|
||||||
|
* @param array The array of elements to stream
|
||||||
|
*/
|
||||||
|
export function fromArray(array: any[]): NodeJS.ReadableStream {
|
||||||
|
let cursor = 0;
|
||||||
|
return new Readable({
|
||||||
|
objectMode: true,
|
||||||
|
read() {
|
||||||
|
if (cursor < array.length) {
|
||||||
|
this.push(array[cursor]);
|
||||||
|
cursor++;
|
||||||
|
} else {
|
||||||
|
this.push(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a ReadWrite stream that collects streamed objects or bytes into an array or buffer
|
||||||
|
* @param options
|
||||||
|
* @param options.objectMode Whether this stream should behave as a stream of objects
|
||||||
|
*/
|
||||||
|
export function collect({ objectMode = false } = {}): NodeJS.ReadWriteStream {
|
||||||
|
const collected: any[] = [];
|
||||||
|
return new Transform({
|
||||||
|
readableObjectMode: objectMode,
|
||||||
|
writableObjectMode: objectMode,
|
||||||
|
transform(data, encoding, callback) {
|
||||||
|
collected.push(data);
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
flush(callback) {
|
||||||
|
this.push(objectMode ? collected : Buffer.concat(collected));
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a stream of readable streams concatenated together
|
||||||
|
* @param streams The readable streams to concatenate
|
||||||
|
*/
|
||||||
|
export function concat(
|
||||||
|
...streams: NodeJS.ReadableStream[]
|
||||||
|
): NodeJS.ReadableStream {
|
||||||
|
let isStarted = false;
|
||||||
|
let currentStreamIndex = 0;
|
||||||
|
const startCurrentStream = () => {
|
||||||
|
if (currentStreamIndex >= streams.length) {
|
||||||
|
wrapper.push(null);
|
||||||
|
} else {
|
||||||
|
streams[currentStreamIndex]
|
||||||
|
.on("data", chunk => {
|
||||||
|
if (!wrapper.push(chunk)) {
|
||||||
|
streams[currentStreamIndex].pause();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("error", err => wrapper.emit("error", err))
|
||||||
|
.on("end", () => {
|
||||||
|
currentStreamIndex++;
|
||||||
|
startCurrentStream();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = new Readable({
|
||||||
|
objectMode: true,
|
||||||
|
read() {
|
||||||
|
if (!isStarted) {
|
||||||
|
isStarted = true;
|
||||||
|
startCurrentStream();
|
||||||
|
}
|
||||||
|
if (currentStreamIndex < streams.length) {
|
||||||
|
streams[currentStreamIndex].resume();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return wrapper;
|
||||||
|
}
|
@ -1,13 +1,18 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es6",
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
|
"outDir": "./dist",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["es2017"],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"watch": false,
|
"declaration": true
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noUnusedLocals": true
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*.ts"]
|
||||||
"**/*.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
24
tslint.json
24
tslint.json
@ -1,19 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"extends": [
|
||||||
|
"tslint:latest",
|
||||||
|
"tslint-plugin-prettier",
|
||||||
|
"tslint-config-prettier"
|
||||||
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-unused-expression": true,
|
"no-console": false,
|
||||||
"no-duplicate-variable": true,
|
"no-implicit-dependencies": [true, ["ava"]],
|
||||||
"curly": true,
|
"prettier": true,
|
||||||
"class-name": true,
|
"ordered-imports": false
|
||||||
"semicolon": [true, "always"],
|
|
||||||
"triple-equals": true,
|
|
||||||
"trailing-comma": [true, {"multiline": "never", "singleline": "never"}],
|
|
||||||
"only-arrow-functions": [true, "allow-declarations"],
|
|
||||||
"no-eval": true,
|
|
||||||
"no-invalid-this": true,
|
|
||||||
"switch-default": true,
|
|
||||||
"prefer-const": true,
|
|
||||||
"arrow-return-shorthand": [true],
|
|
||||||
"jsdoc-format": true,
|
|
||||||
"no-consecutive-blank-lines": [true]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user