UNPKG

fluent-object-stream

Version:
258 lines (257 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = require("stream"); const promises_1 = require("stream/promises"); const _1 = require("."); /** * This is a class to facilitate the transformation of data in a stream. The generic type represent the type of the data in the stream. * All methods allow to keep a strongly typed system. */ class ObjectStream { constructor(stream, intermediateOperations = [], options) { this.stream = stream; this.intermediateOperations = intermediateOperations; this.options = options; } static ofReadable(readable) { return new ObjectStream(readable); } /** * Returns an {@link ObjectStream} consisting of the results of applying the given function to each element of this stream. * To use an async function, see {@link mapAsync} instead. * <p> * This is an <strong>intermediate operation</strong>. * <p/> * @param mapFn Function that is called for every element of the stream. * The mapFn function accepts one argument which is the current element processed in the stream. * @return the new {@link ObjectStream}. */ map(mapFn) { return this.transformWith({ transformElement: (value, pushData) => { const mappedValue = mapFn(value); pushData(mappedValue); }, }); } /** * Returns an {@link ObjectStream} consisting of the results of resolving the given function to each element of this stream. * To use a sync operation, see {@link map} instead. * <p> * This is an <strong>intermediate operation</strong>. * <p/> * @param mapFn Function that is called for every element of the stream to transform each element with the resolve value af this function. * The mapFn function accepts one argument which is the current element processed in the stream. * @return the new {@link ObjectStream}. */ mapAsync(mapFn) { return this.transformWith({ transformElement: async (value, pushData) => { const mappedValue = await mapFn(value); pushData(mappedValue); }, }); } /** * Returns an {@link ObjectStream} consisting of replacing each element by all the elements of the array returned by the mapFn. * <p> * This is an <strong>intermediate operation</strong>. * <p/> * @param mapFn Function that is called for every element of the stream. Each time mapFn executes, all the element in the returned array are pushed to the result stream. * The mapFn function accepts one argument which is the current element processed in the stream. * @return the new {@link ObjectStream}. */ flatMap(mapFn) { return this.transformWith({ transformElement: (value, pushData) => { const mappedArrayValue = mapFn(value); mappedArrayValue.forEach(pushData); }, }); } /** * Returns an {@link ObjectStream} consisting of replacing each element by all the elements of the array resolved by the mapFn. * <p> * This is an <strong>intermediate operation</strong>. * <p/> * @param mapFn Function that is called for every element of the stream. Each time mapFn executes, all the element in the resolved array are pushed to the result stream. * The mapFn function accepts one argument which is the current element processed in the stream. * @return the new {@link ObjectStream}. */ flatMapAsync(mapFn) { return this.transformWith({ transformElement: async (value, pushData) => { const mappedArrayValue = await mapFn(value); mappedArrayValue.forEach(pushData); }, }); } /** * Returns an {@link ObjectStream} consisting of the elements that pass the test implemented by the provided filterFn. * <p> * This is an <strong>intermediate operation</strong>. * <p/> * @param filterFn Function that is a predicate, to test each element of the stream. Return a value that coerces to true to keep the element, or to false otherwise. * It accepts one argument which is the element processed in the stream. * @return the new {@link ObjectStream}. */ filter(filterFn) { return this.transformWith({ transformElement: (value, pushData) => { if (filterFn(value)) { pushData(value); } }, }); } /** * Returns an {@link ObjectStream} consisting of elements grouped by the result of getKeyFn. * Each time the getKeyFn returns a different key, it considers the group is complete and push it to the result stream. * This means for the result to be accurate your stream needs to be ordered by your key first or else you will have multiple {@link GroupingByKey} for the same key. * This behaviour is to avoid loading all data into memory. * * <strong>NOTE</strong> : If a lot of elements are in the same group, that means that all these elements will be into memory. * <p> * This is an <strong>intermediate operation</strong>. * <p/> * @param getKeyFn Function that is called for each element to know if current element should be grouped with the previous one. * @return the new {@link ObjectStream}. */ groupByKey(getKeyFn) { let currentGroupingByKey; return this.transformWith({ transformElement: (value, pushData) => { const key = getKeyFn(value); if (key !== currentGroupingByKey?.key) { if (currentGroupingByKey) { pushData(currentGroupingByKey); } currentGroupingByKey = { key: key, groupedValues: [], }; } currentGroupingByKey.groupedValues.push(value); }, onEnd: (pushData) => { if (currentGroupingByKey) { pushData(currentGroupingByKey); } }, }, { highWaterMark: 1, }); } /** * Returns an {@link ObjectStream} consisting of an array of elements whose size depends on chunkSize. * <p> * This is an <strong>intermediate operation</strong>. * <p/> * @param chunkSize size of the chunks * @return the new {@link ObjectStream}. */ groupByChunk(chunkSize) { let chunkArray = []; return this.transformWith({ transformElement: (value, pushData) => { chunkArray.push(value); if (chunkArray.length >= chunkSize) { pushData(chunkArray); chunkArray = []; } }, onEnd: (pushData) => { if (chunkArray.length > 0) { pushData(chunkArray); } }, }, { highWaterMark: 1, }); } /** * Returns an {@link ObjectStream} consisting of the elements pushed in the given parameter. * This method is to add a generic operation in case none of the existing ones correspond to your needs. * <p> * This is an <strong>intermediate operation</strong>. * <p/> * @param objectTransform {@link ObjectTransform} which represents the transformation to apply. * @param options {@link ObjectStreamOptions} to override for current and next operation. * @return the new {@link ObjectStream}. */ transformWith(objectTransform, options) { const transform = (0, _1.createTransform)(objectTransform, { ...this.options, ...options }); return this.applyTransform(transform, options); } /** * Returns an {@link ObjectStream} consisting of the elements transformed by the given Transform parameter. * <p> * This is an <strong>intermediate operation</strong>. * <p/> * @param transform which is a {@link Transform}. * @param options {@link ObjectStreamOptions} to override for next operations * @return the new {@link ObjectStream}. */ applyTransform(transform, options = {}) { return new ObjectStream(this.stream, [...this.intermediateOperations, transform], { ...this.options, ...options }); } /** * Return all the elements of the stream in an array. * <p> * This is a <strong>terminal operation</strong>. * <p/> * @return a Promise which is * - resolved once all the stream is processed with an array containing all the elements of the stream * - or rejected with the error raised during the processing of the stream if there is one. */ async toArray() { const array = []; await this.forEach((value) => { array.push(value); }); return array; } /** * Apply the given function to each element of the stream before closing it. * <p> * This is a <strong>terminal operation</strong>. * <p/> * @param fn Function that is called for each element of the stream. It can be an async function. * @return a Promise which is * - resolved once all the stream is processed * - or rejected with the error raised during the processing of the stream if there is one. */ async forEach(fn) { const forEachStream = new stream_1.Writable({ objectMode: true, highWaterMark: this.options?.highWaterMark, write: async (value, encoding, callback) => { try { await fn(value); callback(); } catch (e) { if (e instanceof Error) callback(e); else callback(new _1.StreamError(e)); } }, }); await this.writeTo(forEachStream); } /** * Pass each element to the writable stream. * <p> * This is a <strong>terminal operation</strong>. * <p/> * @param writable Writable stream to write data to their destination. * @return a Promise which is * - resolved once all the stream is processed * - or rejected with the error raised during the processing of the stream if there is one. */ async writeTo(writable) { await (0, promises_1.pipeline)([this.stream, ...this.intermediateOperations, writable]); } } exports.default = ObjectStream;