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,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 };