new changes

This commit is contained in:
Niranjan
2026-04-07 05:05:28 +05:30
parent 7c070224bd
commit a18bba15f2
29975 changed files with 3247495 additions and 2761 deletions

View File

@@ -0,0 +1,100 @@
# [4.0.0](https://github.com/nfroidure/BufferStreams/compare/v3.0.0...v4.0.0) (2024-07-17)
### Code Refactoring
* **api:** typescript rewrite ([9bdffa4](https://github.com/nfroidure/BufferStreams/commit/9bdffa413890add00130eb0be6ea18a735614434)), closes [#5](https://github.com/nfroidure/BufferStreams/issues/5)
### BREAKING CHANGES
* **api:** Require Node20+
# [3.0.0](https://github.com/nfroidure/BufferStreams/compare/v2.0.1...v3.0.0) (2019-11-15)
<a name="2.0.1"></a>
## [2.0.1](https://github.com/nfroidure/BufferStreams/compare/v2.0.0...v2.0.1) (2018-04-23)
### Bug Fixes
* Remove debug dependency ([2719573](https://github.com/nfroidure/BufferStreams/commit/2719573))
<a name="2.0.0"></a>
# [2.0.0](https://github.com/nfroidure/BufferStreams/compare/v1.1.1...v2.0.0) (2017-12-06)
### build
* **metapak-nfroidure:** Add metapak-nfroidure ([ed104f6](https://github.com/nfroidure/BufferStreams/commit/ed104f6))
### BREAKING CHANGES
* **metapak-nfroidure:** Removing support for versions minors to the last LTS
## Change Log
### upcoming (2017/03/05 07:36 +00:00)
- [f1bea6b](https://github.com/nfroidure/bufferstreams/commit/f1bea6bc9ad1c013457b9b9137f6dae70e9cab6b) Add Node 4-6-7 (@nfroidure)
### v1.1.1 (2016/06/28 11:20 +00:00)
- [acd1875](https://github.com/nfroidure/bufferstreams/commit/acd18756a3066c72b4cff8aa79aa44a3dcb4f85f) 1.1.1 (@shinnn)
- [4fc64b9](https://github.com/nfroidure/bufferstreams/commit/4fc64b9be16693e59920b976d4822adf52ec6f3d) Use wider badges to fit modern Github design (@shinnn)
- [2b28391](https://github.com/nfroidure/bufferstreams/commit/2b2839191eec654af9361fc654d430f69bc6d622) Fix invalid license statement in package.json (@shinnn)
- [e330151](https://github.com/nfroidure/bufferstreams/commit/e330151fd994b90d4383e2e189f18f628c3c1157) Fix file mode (@shinnn)
- [#4](https://github.com/nfroidure/bufferstreams/pull/4) Use files field instead of .npmignore (@shinnn)
- [d7435e5](https://github.com/nfroidure/bufferstreams/commit/d7435e5729b4684492e746d015bbfee39ac564dc) Use files field instead of .npmignore (@shinnn)
### v1.1.0 (2015/08/04 09:48 +00:00)
- [8b3f905](https://github.com/nfroidure/bufferstreams/commit/8b3f9058764b4ff8095df90eea55ea7683d3ee33) 1.1.0 (@nfroidure)
- [e7a4854](https://github.com/nfroidure/bufferstreams/commit/e7a4854d032682593851e20c20ac0b63587594d7) Better doc (@nfroidure)
- [0567b10](https://github.com/nfroidure/bufferstreams/commit/0567b10f2a8cc6ddb1eada72a9178533b1fa1dd2) Also build on Node 0.12 (@nfroidure)
- [92cd9de](https://github.com/nfroidure/bufferstreams/commit/92cd9de1752cbb70d403c4fbbee0532e762bf3e1) Dependencies update (@nfroidure)
- [5bc3649](https://github.com/nfroidure/bufferstreams/commit/5bc3649e1c8f58505cad3393cbf67c47581dac13) Better handling of errors (@nfroidure)
- [1ca6222](https://github.com/nfroidure/bufferstreams/commit/1ca6222b488072004305156c94a388c100df6fc7) Code cleanup and backward compatibility (@nfroidure)
- [999b805](https://github.com/nfroidure/bufferstreams/commit/999b805be2007b5a1100f9cdbf0f911aa667afab) Using Duplex stream instead of Transform one (@nfroidure)
- [77051e3](https://github.com/nfroidure/bufferstreams/commit/77051e330e82931acf3fb4c4b0b4f24aa4ae13de) Add tests for callback error emitting (@nfroidure)
- [6475b82](https://github.com/nfroidure/bufferstreams/commit/6475b820cbc5a80f4e98bea86cdda3acc6ec7743) Adding linter and suppress warnings (@nfroidure)
- [8d24a90](https://github.com/nfroidure/bufferstreams/commit/8d24a90bf5b91c1185f5c5f00cc32a4b65fc9fc5) Improving the README file (@nfroidure)
- [d5ab44e](https://github.com/nfroidure/bufferstreams/commit/d5ab44e5ddeaddd73744158d046eb71b2fb29fb6) Add stats (@nfroidure)
### v1.0.2 (2015/06/21 07:11 +00:00)
- [8aaf7a5](https://github.com/nfroidure/bufferstreams/commit/8aaf7a576177d3c2c1e626d8bfba091d47d7cea3) 1.0.2 (@nfroidure)
- [fe52a06](https://github.com/nfroidure/bufferstreams/commit/fe52a06f252b161676f722624e74e9290b845320) Fix trinity script (@nfroidure)
- [d512161](https://github.com/nfroidure/bufferstreams/commit/d5121614d055a63fca0072ebfc360fc17f8e04aa) Dependencies update (@nfroidure)
- [48687ed](https://github.com/nfroidure/bufferstreams/commit/48687ed86eea5262693bb9b11097003bc0985b19) Merge branch 'master' of github.com:nfroidure/bufferstreams (@nfroidure)
- [#3](https://github.com/nfroidure/bufferstreams/pull/3) Use SVG-based badges (@shinnn)
- [88eccc5](https://github.com/nfroidure/bufferstreams/commit/88eccc50275e9317cba820a72f69f611c69cf3ab) Use SVG-based badges (@shinnn)
- [a2875b3](https://github.com/nfroidure/bufferstreams/commit/a2875b323ec90b0d4d7afb60186a07c9f60f9350) Simplify test scripts (@shinnn)
### v1.0.1 (2015/02/09 17:57 +00:00)
- [7d4c975](https://github.com/nfroidure/bufferstreams/commit/7d4c975accd17ea382845d93e11761ad8c364534) 1.0.1 (@nfroidure)
- [6027553](https://github.com/nfroidure/bufferstreams/commit/602755373fd77d9ca34248b2ba106bcd57e49dce) Update deps (@nfroidure)
### v1.0.0 (2015/02/09 17:49 +00:00)
- [438812d](https://github.com/nfroidure/bufferstreams/commit/438812dc9e6ecfbc7bd184503a341b0627aa9bf6) 1.0.0 (@nfroidure)
- [01859b0](https://github.com/nfroidure/bufferstreams/commit/01859b0b32a8bb630d97aec3e01dac9148fd7b17) Documenting objectMode (@nfroidure)
- [d3cac8d](https://github.com/nfroidure/bufferstreams/commit/d3cac8dab9a8d6f8275e82eedfafe929b156c258) Fixing tests (@nfroidure)
- [d7ae39a](https://github.com/nfroidure/bufferstreams/commit/d7ae39a4bab7f77a93cde5993f8da703e21db0f0) Adding code climate metrics badge (@nfroidure)
- [677f394](https://github.com/nfroidure/bufferstreams/commit/677f394d68a49afd6a24865a111c061985f8739f) Allowing objectMode, adding a better test lib and 100% coveraging (@nfroidure)
- [1bf9b40](https://github.com/nfroidure/bufferstreams/commit/1bf9b404b47c801387867b9a3614041c5285cfe2) New version 0.0.2 (@nfroidure)
- [17723e2](https://github.com/nfroidure/bufferstreams/commit/17723e212599d3a6dd582980b98331c0651a93ea) Added readable-stream closes#1 (@nfroidure)
- [51545c3](https://github.com/nfroidure/bufferstreams/commit/51545c3e26b26315c17b17bb9a09986b39a538bf) Accept forgiving new (@nfroidure)
- [8d87f57](https://github.com/nfroidure/bufferstreams/commit/8d87f576047d65b2a86680df4d4a6469202d96e1) Dependencies update (@nfroidure)
- [3a1408f](https://github.com/nfroidure/bufferstreams/commit/3a1408fd7886a0dd5f1edf357d4c2373521ae032) Added deps (@nfroidure)
- [38f0690](https://github.com/nfroidure/bufferstreams/commit/38f06902efe745a9cbf648fe6634a2c78c18a544) Added coverage tests (@nfroidure)
- [2a631d6](https://github.com/nfroidure/bufferstreams/commit/2a631d66a41547e32f79dcba2ffe164d3f890bf9) Typo fix (@nfroidure)
- [7ce57c9](https://github.com/nfroidure/bufferstreams/commit/7ce57c96aa95a24038442026b318f98ee5e9318e) Fixing npm badge (@nfroidure)
- [c6d7b48](https://github.com/nfroidure/bufferstreams/commit/c6d7b486bc537b57997dd329966d53a216f30bff) Adding npm badge (@nfroidure)
- [bfbca40](https://github.com/nfroidure/bufferstreams/commit/bfbca40112d3341989961a7ddbed838d2620b8e2) Adding instructions, fixing naming (@nfroidure)
- [1a531c7](https://github.com/nfroidure/bufferstreams/commit/1a531c78b79642c76bcdd09aa0262342833e6203) Readme updated (@nfroidure)
- [b0e1be8](https://github.com/nfroidure/bufferstreams/commit/b0e1be875aa9583ed44b2f1e9c74d050ef777ebb) First commit (@nfroidure)

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright © 2017 Nicolas Froidure
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,161 @@
[//]: # ( )
[//]: # (This file is automatically generated by a `metapak`)
[//]: # (module. Do not change it except between the)
[//]: # (`content:start/end` flags, your changes would)
[//]: # (be overridden.)
[//]: # ( )
# bufferstreams
> Abstract streams to deal with the whole buffered contents.
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nfroidure/bufferstreams/blob/main/LICENSE)
[![Coverage Status](https://coveralls.io/repos/github/git://github.com/nfroidure/BufferStreams/badge.svg?branch=main)](https://coveralls.io/github/git://github.com/nfroidure/BufferStreams?branch=main)
[//]: # (::contents:start)
`bufferstreams` abstracts streams to allow you to deal with their whole content
in a single buffer when it becomes necessary (by example: a legacy library that
do not support streams).
It is not a good practice (dealing with the whole stream content means you need
to keep the whole stream content in memory which is probably not what you intent
by using streams), just some glue. Using `bufferstreams` means:
- there is no library dealing with streams for your needs
- you filled an issue to the wrapped library to support streams
`bufferstreams` can also be used to control the whole stream content in a single
point of a streaming pipeline for testing purposes.
## Usage
Install the [npm module](https://npmjs.org/package/bufferstreams):
```sh
npm install bufferstreams --save
```
Then, in your scripts:
```js
import fs from 'fs';
import { BufferStream } from 'bufferstreams';
fs.createReadStream('input.txt')
.pipe(
new BufferStream((err, buf, cb) => {
// err will be filled with an error if the piped in stream emits one.
if (err) {
throw err;
}
// buf will contain the whole piped in stream contents
buf = Buffer.from(buf.toString('utf-8').replace('foo', 'bar'));
// cb is a callback to pass the result back to the piped out stream
// first argument is an error that will be emitted if any
// the second argument is the modified buffer
cb(null, buf);
}),
)
.pipe(fs.createWriteStream('output.txt'));
```
Note that you can use `bufferstreams` with the objectMode option. In this case,
the given buffer will be an array containing the streamed objects:
```js
new BufferStreams(myCallback, { objectMode: true });
```
`bufferstreams` exposes a utility function for functional programming:
```js
import { streamBuffer } from 'bufferstreams';
process.stdin.pipe(streamBuffer(myCallback)).pipe(process.stdout);
```
Finally `bufferstreams` exposes another function for objects mode buffering:
```js
import { bufferObjects } from 'bufferstreams';
process.stdin.pipe(bufferObjects(myCallback)).pipe(process.stdout);
```
## Contributing
Feel free to contribute with your code if you agree with publishing it under the
MIT license.
[//]: # (::contents:end)
# API
## Classes
<dl>
<dt><a href="#BufferStream">BufferStream</a></dt>
<dd><p>Buffer the stream content and bring it into the provided callback</p>
</dd>
</dl>
## Functions
<dl>
<dt><a href="#bufferStream">bufferStream(bufferCallback, options)</a> ⇒</dt>
<dd><p>Utility function if you prefer a functional way of using this lib</p>
</dd>
<dt><a href="#bufferObjects">bufferObjects(bufferCallback, options)</a> ⇒</dt>
<dd><p>Utility function to buffer objet mode streams</p>
</dd>
</dl>
<a name="BufferStream"></a>
## BufferStream
Buffer the stream content and bring it into the provided callback
**Kind**: global class
<a name="new_BufferStream_new"></a>
### new BufferStream(bufferCallback, options)
| Param | Type | Description |
| --- | --- | --- |
| bufferCallback | <code>function</code> | A function to handle the buffered content. |
| options | <code>Object</code> | inherits of Stream.Duplex, the options are passed to the parent constructor so you can use it's options too. |
| options.objectMode | <code>boolean</code> | Use if piped in streams are in object mode. In this case, an array of the buffered will be transmitted to the callback function. |
<a name="bufferStream"></a>
## bufferStream(bufferCallback, options) ⇒
Utility function if you prefer a functional way of using this lib
**Kind**: global function
**Returns**: Stream
| Param |
| --- |
| bufferCallback |
| options |
<a name="bufferObjects"></a>
## bufferObjects(bufferCallback, options) ⇒
Utility function to buffer objet mode streams
**Kind**: global function
**Returns**: Stream
| Param |
| --- |
| bufferCallback |
| options |
# Authors
- [Nicolas Froidure](http://insertafter.com/en/index.html)
# License
[MIT](https://github.com/nfroidure/bufferstreams/blob/main/LICENSE)

View File

@@ -0,0 +1,50 @@
import { Duplex, type Writable } from 'stream';
export type BufferStreamOptions = {
objectMode: boolean;
};
export type BufferStreamItem<O extends Partial<BufferStreamOptions>, T> = O extends {
objectMode: true;
} ? T : Buffer;
export type BufferStreamPayload<O extends Partial<BufferStreamOptions>, T> = O extends {
objectMode: true;
} ? T[] : Buffer;
export type BufferStreamHandler<O extends Partial<BufferStreamOptions>, T> = (payload: BufferStreamPayload<O, T>) => Promise<BufferStreamPayload<O, T>>;
export type BufferStreamCallback<O extends Partial<BufferStreamOptions>, T> = (err: Error | null, payload: BufferStreamPayload<O, T>, cb: (err: Error | null, payload?: null | BufferStreamPayload<O, T>) => void) => void;
/**
* Buffer the stream content and bring it into the provided callback
*/
declare class BufferStream<T, O extends Partial<BufferStreamOptions>> extends Duplex {
private _options;
private _bufferCallback;
private _finished;
private _buffer;
/**
* @param bufferCallback {Function} A function to handle the buffered content.
* @param options {Object} inherits of Stream.Duplex, the options are passed to the parent constructor so you can use it's options too.
* @param options.objectMode {boolean} Use if piped in streams are in object mode. In this case, an array of the buffered will be transmitted to the callback function.
*/
constructor(bufferCallback: BufferStreamCallback<O, T> | BufferStreamHandler<O, T>, options?: O);
_write(chunk: BufferStreamItem<O, T>, encoding: Parameters<Writable['write']>[1], done: () => void): void;
_read(): void;
_bufferStreamCallbackWrapper(err: Error): void;
_bufferStreamError(err: Error): void;
}
/**
* Utility function if you prefer a functional way of using this lib
* @param bufferCallback
* @param options
* @returns Stream
*/
export declare function bufferStream<T, O extends Partial<BufferStreamOptions>>(bufferCallback: BufferStreamCallback<O, T>, options?: O): BufferStream<T, O>;
/**
* Utility function to buffer objet mode streams
* @param bufferCallback
* @param options
* @returns Stream
*/
export declare function bufferObjects<T>(bufferCallback: BufferStreamCallback<{
objectMode: true;
}, T>, options: Omit<BufferStreamOptions, 'objectMode'>): BufferStream<T, {
objectMode: true;
}>;
export { BufferStream };

View File

@@ -0,0 +1,105 @@
import { Duplex } from 'stream';
import { YError } from 'yerror';
const DEFAULT_BUFFER_STREAM_OPTIONS = {
objectMode: false,
};
/**
* Buffer the stream content and bring it into the provided callback
*/
class BufferStream extends Duplex {
_options = DEFAULT_BUFFER_STREAM_OPTIONS;
_bufferCallback;
_finished = false;
_buffer = [];
/**
* @param bufferCallback {Function} A function to handle the buffered content.
* @param options {Object} inherits of Stream.Duplex, the options are passed to the parent constructor so you can use it's options too.
* @param options.objectMode {boolean} Use if piped in streams are in object mode. In this case, an array of the buffered will be transmitted to the callback function.
*/
constructor(bufferCallback, options) {
super(options);
if (!(bufferCallback instanceof Function)) {
throw new YError('E_BAD_CALLBACK');
}
this._options = {
...DEFAULT_BUFFER_STREAM_OPTIONS,
...options,
};
this._bufferCallback =
bufferCallback.length === 1
? ((err, payload, cb) => {
bufferCallback(payload)
.then((result) => {
cb(err, result);
})
.catch((err) => {
cb(err);
});
})
: bufferCallback;
this.once('finish', this._bufferStreamCallbackWrapper);
this.on('error', this._bufferStreamError);
}
_write(chunk, encoding, done) {
this._buffer.push(chunk);
done();
}
_read() {
if (this._finished) {
while (this._buffer.length) {
if (!this.push(this._buffer.shift())) {
break;
}
}
if (0 === this._buffer.length) {
this.push(null);
}
}
}
_bufferStreamCallbackWrapper(err) {
const buffer = (this._options.objectMode
? this._buffer
: Buffer.concat(this._buffer));
err = err || null;
this._bufferCallback(err, buffer, (err2, buf) => {
setImmediate(() => {
this.removeListener('error', this._bufferStreamError);
if (err2) {
this.emit('error', err2);
}
this._buffer = (buf == null ? [] : buf instanceof Buffer ? [buf] : buf);
this._finished = true;
this._read();
});
});
}
_bufferStreamError(err) {
if (this._finished) {
return;
}
this._bufferStreamCallbackWrapper(err);
}
}
/**
* Utility function if you prefer a functional way of using this lib
* @param bufferCallback
* @param options
* @returns Stream
*/
export function bufferStream(bufferCallback, options = DEFAULT_BUFFER_STREAM_OPTIONS) {
return new BufferStream(bufferCallback, options);
}
/**
* Utility function to buffer objet mode streams
* @param bufferCallback
* @param options
* @returns Stream
*/
export function bufferObjects(bufferCallback, options) {
return new BufferStream(bufferCallback, {
...options,
objectMode: true,
});
}
export { BufferStream };
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAiB,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAsBhC,MAAM,6BAA6B,GAAG;IACpC,UAAU,EAAE,KAAK;CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,YAAwD,SAAQ,MAAM;IAClE,QAAQ,GAAwB,6BAA6B,CAAC;IAC9D,eAAe,CAA6B;IAC5C,SAAS,GAAY,KAAK,CAAC;IAC3B,OAAO,GAA6B,EAAE,CAAC;IAE/C;;;;OAIG;IACH,YACE,cAAsE,EACtE,OAAW;QAEX,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,IAAI,CAAC,CAAC,cAAc,YAAY,QAAQ,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAAC;QACrC,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG;YACd,GAAG,6BAA6B;YAChC,GAAG,OAAO;SACX,CAAC;QACF,IAAI,CAAC,eAAe;YAClB,cAAc,CAAC,MAAM,KAAK,CAAC;gBACzB,CAAC,CAAE,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE;oBACpB,cAA4C,CAAC,OAAO,CAAC;yBACnD,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;wBACf,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;oBAClB,CAAC,CAAC;yBACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;wBACb,EAAE,CAAC,GAAG,CAAC,CAAC;oBACV,CAAC,CAAC,CAAC;gBACP,CAAC,CAAgC;gBACnC,CAAC,CAAE,cAA6C,CAAC;QAErD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,4BAA4B,CAAC,CAAC;QACvD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,CACJ,KAA6B,EAC7B,QAA0C,EAC1C,IAAgB;QAEhB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,EAAE,CAAC;IACT,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC;oBACrC,MAAM;gBACR,CAAC;YACH,CAAC;YACD,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC9B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED,4BAA4B,CAAC,GAAU;QACrC,MAAM,MAAM,GAAG,CACb,IAAI,CAAC,QAAQ,CAAC,UAAU;YACtB,CAAC,CAAE,IAAI,CAAC,OAAe;YACvB,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAmB,CAAC,CAKnC,CAAC;QAEX,GAAG,GAAG,GAAG,IAAI,IAAI,CAAC;QAElB,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;YAC9C,YAAY,CAAC,GAAG,EAAE;gBAChB,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;gBACtD,IAAI,IAAI,EAAE,CAAC;oBACT,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;gBAC3B,CAAC;gBACD,IAAI,CAAC,OAAO,GAAG,CACb,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,YAAY,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAC3B,CAAC;gBAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;gBACtB,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,kBAAkB,CAAC,GAAU;QAC3B,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,4BAA4B,CAAC,GAAG,CAAC,CAAC;IACzC,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,cAA0C,EAC1C,UAAa,6BAAkC;IAE/C,OAAO,IAAI,YAAY,CAAO,cAAc,EAAE,OAAO,CAAC,CAAC;AACzD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,cAA6D,EAC7D,OAAgD;IAEhD,OAAO,IAAI,YAAY,CAA0B,cAAc,EAAE;QAC/D,GAAG,OAAO;QACV,UAAU,EAAE,IAAI;KACjB,CAAC,CAAC;AACL,CAAC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,329 @@
import { describe, expect, test } from '@jest/globals';
import { YError } from 'yerror';
import StreamTest from 'streamtest';
import { BufferStream } from './index.js';
// Helpers
function syncBufferPrefixer(headerText) {
return new BufferStream((err, buf, cb) => {
expect(err).toBeNull();
if (null === buf) {
cb(null, Buffer.from(headerText));
return;
}
cb(null, Buffer.concat([Buffer.from(headerText), buf]));
}, {
objectMode: false,
});
}
function syncObjectsPrefixer(prefixObject) {
return new BufferStream((err, objs, cb) => {
expect(err).toBeNull();
if (null === objs) {
cb(null, [prefixObject]);
return;
}
cb(null, [prefixObject, ...objs]);
}, {
objectMode: true,
});
}
function asyncBufferPrefixer(headerText) {
return new BufferStream((err, buf, cb) => {
expect(err).toBeNull();
if (null === buf) {
setTimeout(() => {
cb(null, Buffer.from(headerText));
}, 0);
}
else {
setTimeout(() => {
cb(null, Buffer.concat([Buffer.from(headerText), buf]));
}, 0);
}
}, {
objectMode: false,
});
}
describe('bufferstreams', () => {
test('should fail when callback is not a function', () => {
try {
new BufferStream(undefined);
throw new YError('E_UNEXPECTED_SUCCESS');
}
catch (err) {
expect(err.code).toEqual('E_BAD_CALLBACK');
}
});
describe('in buffer mode', () => {
describe('synchonously', () => {
test('should work with one pipe', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(syncBufferPrefixer('plop'))
.pipe(stream);
expect(await result).toEqual('ploptest');
});
test('should work when returning a null buffer', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(new BufferStream((err, buf, cb) => {
if (err) {
cb(err);
return;
}
cb(null, null);
}))
.pipe(stream);
expect(await result).toEqual('');
});
test('should work with an async handler', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(new BufferStream(async (buf) => {
return buf;
}))
.pipe(stream);
expect(await result).toEqual('test');
});
test('should work with multiple pipes', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(syncBufferPrefixer('plop'))
.pipe(syncBufferPrefixer('plip'))
.pipe(syncBufferPrefixer('plap'))
.pipe(stream);
expect(await result).toEqual('plapplipploptest');
});
});
describe('asynchonously', () => {
test('should work with one pipe', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(asyncBufferPrefixer('plop'))
.pipe(stream);
expect(await result).toEqual('ploptest');
});
test('should work when returning a null buffer', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(new BufferStream((err, _buf, cb) => {
if (err) {
cb(err);
return;
}
cb(null, null);
}))
.pipe(stream);
expect(await result).toEqual('');
});
test('should work with multiple pipes', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(asyncBufferPrefixer('plop'))
.pipe(asyncBufferPrefixer('plip'))
.pipe(asyncBufferPrefixer('plap'))
.pipe(stream);
expect(await result).toEqual('plapplipploptest');
});
test('should report stream errors', async () => {
const [stream, result] = StreamTest.toText();
const bufferStream = new BufferStream((err, _objs, cb) => {
expect(err.code).toEqual('E_ERROR');
cb(null, []);
}, {
objectMode: true,
});
StreamTest.fromErroredChunks(new YError('E_ERROR'), [
Buffer.from('ou'),
Buffer.from('de'),
Buffer.from('la'),
Buffer.from('li'),
])
.on('error', (err) => {
bufferStream.emit('error', err);
})
.pipe(bufferStream)
.pipe(stream);
expect(await result).toEqual('');
});
test('should emit callback errors', async () => {
const [stream, result] = StreamTest.toText();
let caughtError = new YError('E_UNEXPECTED_SUCCESS');
StreamTest.fromChunks([
Buffer.from('ou'),
Buffer.from('de'),
Buffer.from('la'),
Buffer.from('li'),
])
.pipe(new BufferStream((err, _objs, cb) => {
if (err) {
cb(err);
return;
}
cb(new YError('E_ERROR'), Buffer.from(''));
}))
.on('error', (err) => {
caughtError = err;
})
.pipe(stream);
expect(await result).toEqual('');
expect(caughtError.code).toEqual('E_ERROR');
});
});
});
describe('in object mode', () => {
const object1 = { txt: 'te' };
const object2 = { txt: 'st' };
const object3 = { txt: 'e' };
const object4 = { txt: 'd' };
const object5 = { txt: 'u' };
const object6 = { txt: 'ni' };
const object7 = { txt: 't' };
describe('synchonously', () => {
test('should work with one pipe', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(syncObjectsPrefixer(object4))
.pipe(stream);
expect(await result).toEqual([object4, object1, object2]);
});
test('should work when returning an empty array', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(new BufferStream((err, _objs, cb) => {
if (err) {
cb(err);
return;
}
cb(null, []);
}, {
objectMode: true,
}))
.pipe(stream);
expect((await result).length).toEqual(0);
});
test('should work with multiple pipes', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(syncObjectsPrefixer(object4))
.pipe(syncObjectsPrefixer(object5))
.pipe(syncObjectsPrefixer(object6))
.pipe(stream);
expect(await result).toEqual([
object6,
object5,
object4,
object1,
object2,
]);
});
});
describe('asynchonously', () => {
test('should work with one pipe', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(syncObjectsPrefixer(object4))
.pipe(stream);
expect(await result).toEqual([object4, object1, object2]);
});
test('should work when returning an empty array', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(new BufferStream((err, _objs, cb) => {
if (err) {
cb(err);
return;
}
cb(null, []);
}, {
objectMode: true,
}))
.pipe(stream);
expect((await result).length).toEqual(0);
});
test('should work when returning legacy null', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(new BufferStream((err, _objs, cb) => {
if (err) {
cb(err);
return;
}
cb(null, null);
}, {
objectMode: true,
}))
.pipe(stream);
expect((await result).length).toEqual(0);
});
test('should work with multiple pipes', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(syncObjectsPrefixer(object4))
.pipe(syncObjectsPrefixer(object5))
.pipe(syncObjectsPrefixer(object6))
.pipe(stream);
expect(await result).toEqual([
object6,
object5,
object4,
object1,
object2,
]);
});
test('should report stream errors', async () => {
const [stream, result] = StreamTest.toObjects();
const bufferStream = new BufferStream((err, _objs, cb) => {
expect(err.code).toEqual('E_ERROR');
cb(null, []);
}, {
objectMode: true,
});
StreamTest.fromErroredObjects(new YError('E_ERROR'), [
object1,
object2,
object3,
object4,
object5,
object6,
object7,
])
.on('error', (err) => {
bufferStream.emit('error', err);
})
.pipe(bufferStream)
.pipe(stream);
expect(await result).toEqual([]);
});
test('should emit callback errors', async () => {
const [stream, result] = StreamTest.toObjects();
let caughtError = new YError('E_UNEXPECTED_SUCCESS');
StreamTest.fromObjects([
object1,
object2,
object3,
object4,
object5,
object6,
object7,
])
.pipe(new BufferStream((err, _objs, cb) => {
if (err) {
cb(err, []);
return;
}
cb(new YError('E_ERROR'), []);
}, {
objectMode: true,
}))
.on('error', (err) => {
caughtError = err;
})
.pipe(stream);
expect(await result).toEqual([]);
expect(caughtError.code).toEqual('E_ERROR');
});
});
});
});
//# sourceMappingURL=index.test.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,171 @@
{
"metapak": {
"configs": [
"main",
"readme",
"eslint",
"tsesm",
"jest",
"jsdocs",
"codeclimate",
"coveralls",
"ghactions"
],
"data": {
"files": "'src/**/*.ts'",
"testsFiles": "'src/**/*.tests.ts'",
"distFiles": "'dist/**/*.js'",
"ignore": [
"dist"
],
"bundleFiles": [
"dist",
"src"
]
}
},
"name": "bufferstreams",
"version": "4.0.0",
"description": "Abstract streams to deal with the whole buffered contents.",
"homepage": "https://github.com/nfroidure/BufferStreams",
"main": "dist/index.js",
"files": [
"dist",
"src",
"LICENSE",
"README.md",
"CHANGELOG.md"
],
"scripts": {
"build": "rimraf 'dist' && tsc --outDir dist",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md",
"cli": "env NODE_ENV=${NODE_ENV:-cli}",
"cover": "npm run jest -- --coverage",
"cz": "env NODE_ENV=${NODE_ENV:-cli} git cz",
"doc": "echo \"# API\" > API.md; jsdoc2md 'dist/**/*.js' >> API.md && git add API.md",
"format": "npm run prettier",
"jest": "NODE_OPTIONS=--experimental-vm-modules NODE_ENV=test jest",
"lint": "eslint 'src/**/*.ts'",
"metapak": "metapak",
"precz": "npm t && npm run lint && npm run build && npm run doc && npm run metapak -- -s",
"prettier": "prettier --write 'src/**/*.ts'",
"preversion": "npm t && npm run lint && npm run build && npm run doc && npm run metapak -- -s",
"rebuild": "swc ./src -s -d dist -C jsc.target=es2022",
"test": "npm run jest",
"type-check": "tsc --pretty --noEmit",
"version": "npm run changelog"
},
"repository": {
"type": "git",
"url": "git://github.com/nfroidure/BufferStreams"
},
"keywords": [
"buffer",
"streaming",
"stream",
"async",
"abstract"
],
"dependencies": {
"readable-stream": "^3.4.0",
"yerror": "^8.0.0"
},
"devDependencies": {
"@eslint/js": "^9.7.0",
"@swc/cli": "^0.4.0",
"@swc/core": "^1.6.13",
"@swc/helpers": "^0.5.12",
"@swc/jest": "^0.2.36",
"commitizen": "^4.3.0",
"conventional-changelog-cli": "^5.0.0",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.6.0",
"eslint-plugin-prettier": "^5.1.3",
"jest": "^29.7.0",
"jsdoc-to-markdown": "^8.0.0",
"metapak": "^6.0.1",
"metapak-nfroidure": "^18.2.0",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"streamtest": "^3.0.0",
"typescript": "^5.5.3",
"typescript-eslint": "^7.16.0"
},
"author": {
"name": "Nicolas Froidure",
"email": "nicolas.froidure@insertafter.com",
"url": "http://insertafter.com/en/index.html"
},
"engines": {
"node": ">=20.11.1"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/nfroidure/BufferStreams/issues"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"greenkeeper": {
"ignore": [
"commitizen",
"cz-conventional-changelog",
"conventional-changelog-cli",
"eslint",
"prettier",
"eslint-config-prettier",
"eslint-plugin-prettier",
"typescript-eslint",
"typescript",
"rimraf",
"@swc/cli",
"@swc/core",
"@swc/helpers",
"jest",
"@swc/jest",
"jsdoc-to-markdown"
]
},
"prettier": {
"semi": true,
"printWidth": 80,
"singleQuote": true,
"trailingComma": "all",
"proseWrap": "always"
},
"contributors": [],
"jest": {
"coverageReporters": [
"lcov"
],
"testPathIgnorePatterns": [
"/node_modules/"
],
"roots": [
"<rootDir>/src"
],
"transform": {
"^.+\\.tsx?$": [
"@swc/jest",
{}
]
},
"testEnvironment": "node",
"moduleNameMapper": {
"(.+)\\.js": "$1"
},
"extensionsToTreatAsEsm": [
".ts"
],
"prettierPath": null
},
"overrides": {
"eslint": "^9.7.0"
},
"type": "module",
"types": "dist/index.d.ts"
}

View File

@@ -0,0 +1,421 @@
import { describe, expect, test } from '@jest/globals';
import { YError } from 'yerror';
import StreamTest from 'streamtest';
import { BufferStream } from './index.js';
// Helpers
function syncBufferPrefixer(headerText: string) {
return new BufferStream(
(err, buf, cb) => {
expect(err).toBeNull();
if (null === buf) {
cb(null, Buffer.from(headerText));
return;
}
cb(null, Buffer.concat([Buffer.from(headerText), buf]));
},
{
objectMode: false,
},
);
}
function syncObjectsPrefixer<T>(prefixObject: T) {
return new BufferStream(
(err, objs, cb) => {
expect(err).toBeNull();
if (null === objs) {
cb(null, [prefixObject]);
return;
}
cb(null, [prefixObject, ...objs]);
},
{
objectMode: true,
},
);
}
function asyncBufferPrefixer(headerText: string) {
return new BufferStream(
(err, buf, cb) => {
expect(err).toBeNull();
if (null === buf) {
setTimeout(() => {
cb(null, Buffer.from(headerText));
}, 0);
} else {
setTimeout(() => {
cb(null, Buffer.concat([Buffer.from(headerText), buf]));
}, 0);
}
},
{
objectMode: false,
},
);
}
describe('bufferstreams', () => {
test('should fail when callback is not a function', () => {
try {
new BufferStream(undefined as unknown as <T>(t: T) => Promise<T>);
throw new YError('E_UNEXPECTED_SUCCESS');
} catch (err) {
expect((err as YError).code).toEqual('E_BAD_CALLBACK');
}
});
describe('in buffer mode', () => {
describe('synchonously', () => {
test('should work with one pipe', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(syncBufferPrefixer('plop'))
.pipe(stream);
expect(await result).toEqual('ploptest');
});
test('should work when returning a null buffer', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(
new BufferStream((err, buf, cb) => {
if (err) {
cb(err);
return;
}
cb(null, null);
}),
)
.pipe(stream);
expect(await result).toEqual('');
});
test('should work with an async handler', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(
new BufferStream(async (buf) => {
return buf;
}),
)
.pipe(stream);
expect(await result).toEqual('test');
});
test('should work with multiple pipes', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(syncBufferPrefixer('plop'))
.pipe(syncBufferPrefixer('plip'))
.pipe(syncBufferPrefixer('plap'))
.pipe(stream);
expect(await result).toEqual('plapplipploptest');
});
});
describe('asynchonously', () => {
test('should work with one pipe', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(asyncBufferPrefixer('plop'))
.pipe(stream);
expect(await result).toEqual('ploptest');
});
test('should work when returning a null buffer', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(
new BufferStream((err, _buf, cb) => {
if (err) {
cb(err);
return;
}
cb(null, null);
}),
)
.pipe(stream);
expect(await result).toEqual('');
});
test('should work with multiple pipes', async () => {
const [stream, result] = StreamTest.toText();
StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')])
.pipe(asyncBufferPrefixer('plop'))
.pipe(asyncBufferPrefixer('plip'))
.pipe(asyncBufferPrefixer('plap'))
.pipe(stream);
expect(await result).toEqual('plapplipploptest');
});
test('should report stream errors', async () => {
const [stream, result] = StreamTest.toText();
const bufferStream = new BufferStream(
(err, _objs, cb) => {
expect((err as YError).code).toEqual('E_ERROR');
cb(null, []);
},
{
objectMode: true,
},
);
StreamTest.fromErroredChunks(new YError('E_ERROR'), [
Buffer.from('ou'),
Buffer.from('de'),
Buffer.from('la'),
Buffer.from('li'),
])
.on('error', (err) => {
bufferStream.emit('error', err);
})
.pipe(bufferStream)
.pipe(stream);
expect(await result).toEqual('');
});
test('should emit callback errors', async () => {
const [stream, result] = StreamTest.toText();
let caughtError: YError = new YError('E_UNEXPECTED_SUCCESS');
StreamTest.fromChunks([
Buffer.from('ou'),
Buffer.from('de'),
Buffer.from('la'),
Buffer.from('li'),
])
.pipe(
new BufferStream((err, _objs, cb) => {
if (err) {
cb(err);
return;
}
cb(new YError('E_ERROR'), Buffer.from(''));
}),
)
.on('error', (err) => {
caughtError = err as YError;
})
.pipe(stream);
expect(await result).toEqual('');
expect(caughtError.code).toEqual('E_ERROR');
});
});
});
describe('in object mode', () => {
const object1 = { txt: 'te' };
const object2 = { txt: 'st' };
const object3 = { txt: 'e' };
const object4 = { txt: 'd' };
const object5 = { txt: 'u' };
const object6 = { txt: 'ni' };
const object7 = { txt: 't' };
describe('synchonously', () => {
test('should work with one pipe', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(syncObjectsPrefixer(object4))
.pipe(stream);
expect(await result).toEqual([object4, object1, object2]);
});
test('should work when returning an empty array', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(
new BufferStream(
(err, _objs, cb) => {
if (err) {
cb(err);
return;
}
cb(null, []);
},
{
objectMode: true,
},
),
)
.pipe(stream);
expect((await result).length).toEqual(0);
});
test('should work with multiple pipes', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(syncObjectsPrefixer(object4))
.pipe(syncObjectsPrefixer(object5))
.pipe(syncObjectsPrefixer(object6))
.pipe(stream);
expect(await result).toEqual([
object6,
object5,
object4,
object1,
object2,
]);
});
});
describe('asynchonously', () => {
test('should work with one pipe', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(syncObjectsPrefixer(object4))
.pipe(stream);
expect(await result).toEqual([object4, object1, object2]);
});
test('should work when returning an empty array', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(
new BufferStream(
(err, _objs, cb) => {
if (err) {
cb(err);
return;
}
cb(null, []);
},
{
objectMode: true,
},
),
)
.pipe(stream);
expect((await result).length).toEqual(0);
});
test('should work when returning legacy null', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(
new BufferStream(
(err, _objs, cb) => {
if (err) {
cb(err);
return;
}
cb(null, null);
},
{
objectMode: true,
},
),
)
.pipe(stream);
expect((await result).length).toEqual(0);
});
test('should work with multiple pipes', async () => {
const [stream, result] = StreamTest.toObjects();
StreamTest.fromObjects([object1, object2])
.pipe(syncObjectsPrefixer(object4))
.pipe(syncObjectsPrefixer(object5))
.pipe(syncObjectsPrefixer(object6))
.pipe(stream);
expect(await result).toEqual([
object6,
object5,
object4,
object1,
object2,
]);
});
test('should report stream errors', async () => {
const [stream, result] = StreamTest.toObjects();
const bufferStream = new BufferStream(
(err, _objs, cb) => {
expect((err as YError).code).toEqual('E_ERROR');
cb(null, []);
},
{
objectMode: true,
},
);
StreamTest.fromErroredObjects(new YError('E_ERROR'), [
object1,
object2,
object3,
object4,
object5,
object6,
object7,
])
.on('error', (err) => {
bufferStream.emit('error', err);
})
.pipe(bufferStream)
.pipe(stream);
expect(await result).toEqual([]);
});
test('should emit callback errors', async () => {
const [stream, result] = StreamTest.toObjects();
let caughtError: YError = new YError('E_UNEXPECTED_SUCCESS');
StreamTest.fromObjects([
object1,
object2,
object3,
object4,
object5,
object6,
object7,
])
.pipe(
new BufferStream(
(err, _objs, cb) => {
if (err) {
cb(err, []);
return;
}
cb(new YError('E_ERROR'), []);
},
{
objectMode: true,
},
),
)
.on('error', (err) => {
caughtError = err as YError;
})
.pipe(stream);
expect(await result).toEqual([]);
expect(caughtError.code).toEqual('E_ERROR');
});
});
});
});

View File

@@ -0,0 +1,160 @@
import { Duplex, type Writable } from 'stream';
import { YError } from 'yerror';
export type BufferStreamOptions = {
objectMode: boolean;
};
export type BufferStreamItem<
O extends Partial<BufferStreamOptions>,
T,
> = O extends { objectMode: true } ? T : Buffer;
export type BufferStreamPayload<
O extends Partial<BufferStreamOptions>,
T,
> = O extends { objectMode: true } ? T[] : Buffer;
export type BufferStreamHandler<O extends Partial<BufferStreamOptions>, T> = (
payload: BufferStreamPayload<O, T>,
) => Promise<BufferStreamPayload<O, T>>;
export type BufferStreamCallback<O extends Partial<BufferStreamOptions>, T> = (
err: Error | null,
payload: BufferStreamPayload<O, T>,
cb: (err: Error | null, payload?: null | BufferStreamPayload<O, T>) => void,
) => void;
const DEFAULT_BUFFER_STREAM_OPTIONS = {
objectMode: false,
};
/**
* Buffer the stream content and bring it into the provided callback
*/
class BufferStream<T, O extends Partial<BufferStreamOptions>> extends Duplex {
private _options: BufferStreamOptions = DEFAULT_BUFFER_STREAM_OPTIONS;
private _bufferCallback: BufferStreamCallback<O, T>;
private _finished: boolean = false;
private _buffer: BufferStreamItem<O, T>[] = [];
/**
* @param bufferCallback {Function} A function to handle the buffered content.
* @param options {Object} inherits of Stream.Duplex, the options are passed to the parent constructor so you can use it's options too.
* @param options.objectMode {boolean} Use if piped in streams are in object mode. In this case, an array of the buffered will be transmitted to the callback function.
*/
constructor(
bufferCallback: BufferStreamCallback<O, T> | BufferStreamHandler<O, T>,
options?: O,
) {
super(options);
if (!(bufferCallback instanceof Function)) {
throw new YError('E_BAD_CALLBACK');
}
this._options = {
...DEFAULT_BUFFER_STREAM_OPTIONS,
...options,
};
this._bufferCallback =
bufferCallback.length === 1
? (((err, payload, cb) => {
(bufferCallback as BufferStreamHandler<O, T>)(payload)
.then((result) => {
cb(err, result);
})
.catch((err) => {
cb(err);
});
}) as BufferStreamCallback<O, T>)
: (bufferCallback as BufferStreamCallback<O, T>);
this.once('finish', this._bufferStreamCallbackWrapper);
this.on('error', this._bufferStreamError);
}
_write(
chunk: BufferStreamItem<O, T>,
encoding: Parameters<Writable['write']>[1],
done: () => void,
) {
this._buffer.push(chunk);
done();
}
_read() {
if (this._finished) {
while (this._buffer.length) {
if (!this.push(this._buffer.shift())) {
break;
}
}
if (0 === this._buffer.length) {
this.push(null);
}
}
}
_bufferStreamCallbackWrapper(err: Error) {
const buffer = (
this._options.objectMode
? (this._buffer as T[])
: Buffer.concat(this._buffer as Buffer[])
) as O extends {
objectMode: true;
}
? T[]
: Buffer;
err = err || null;
this._bufferCallback(err, buffer, (err2, buf) => {
setImmediate(() => {
this.removeListener('error', this._bufferStreamError);
if (err2) {
this.emit('error', err2);
}
this._buffer = (
buf == null ? [] : buf instanceof Buffer ? [buf] : buf
) as BufferStreamItem<O, T>[];
this._finished = true;
this._read();
});
});
}
_bufferStreamError(err: Error) {
if (this._finished) {
return;
}
this._bufferStreamCallbackWrapper(err);
}
}
/**
* Utility function if you prefer a functional way of using this lib
* @param bufferCallback
* @param options
* @returns Stream
*/
export function bufferStream<T, O extends Partial<BufferStreamOptions>>(
bufferCallback: BufferStreamCallback<O, T>,
options: O = DEFAULT_BUFFER_STREAM_OPTIONS as O,
) {
return new BufferStream<T, O>(bufferCallback, options);
}
/**
* Utility function to buffer objet mode streams
* @param bufferCallback
* @param options
* @returns Stream
*/
export function bufferObjects<T>(
bufferCallback: BufferStreamCallback<{ objectMode: true }, T>,
options: Omit<BufferStreamOptions, 'objectMode'>,
) {
return new BufferStream<T, { objectMode: true }>(bufferCallback, {
...options,
objectMode: true,
});
}
export { BufferStream };