UNPKG

@shutterstock/chunker

Version:

Calls a callback before size would be exceeded or when count limit is reached

124 lines 4.48 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Chunker = void 0; const p_map_iterable_1 = require("@shutterstock/p-map-iterable"); /** * Collects items up to `countLimit`, calling `writer` before `sizeLimit` would be exceeded. * * @remarks * * Always call {@link onIdle} when done to ensure that the last `writer` call is made. * * @template T Type of items to be chunked */ class Chunker { /** * Collects items up to `countLimit`, calling `writer` before `sizeLimit` would be exceeded. * * @remarks * * Always call {@link onIdle} when done to ensure that the last `writer` call is made. * * @template T Type of items to be chunked * @param options Chunker options * @param options.sizeLimit User-defined size before (not to exceed) which `writer` should be called * @param options.countLimit User-defined size at which `writer` should be called * @param options.sizer Function that returns user-defined size of an item * @param options.writer Function that writes the pending items when `sizeLimit` or `countLimit` would be exceeded. * This is not a `mapper` as it does not return a result at all. * For example, this may write the items to a file or send them to a service. * If the results need to be passed along, add them to an `IterableQueueMapper` for example. */ constructor(options) { this._iterableQueue = new p_map_iterable_1.IterableQueue({ maxUnread: 0 }); this._errors = []; this._pendingSize = 0; this._pendingItems = []; const { sizeLimit, countLimit, sizer, writer } = options; this._writer = writer; // Start the background flushing process this._backgroundWriter = (async () => { for await (const item of this._iterableQueue) { // Get size of new item const size = await sizer(item); // Check if we need to flush before adding if (this._pendingItems.length === countLimit || (this._pendingSize > 0 && this._pendingSize + size > sizeLimit)) { await this.flush(); } // Add the new item to the queue this._pendingItems.push(item); this._pendingSize += size; // Check if we need to flush after adding if (this._pendingItems.length === countLimit) { await this.flush(); } } // Final flush if any items are left if (this._pendingItems.length > 0) { await this.flush(); } })(); } /** * Sum of the user-defined size of all pending items */ get pendingSize() { return this._pendingSize; } /** * Number of items pending */ get pendingCount() { return this._pendingItems.length; } /** * Accumulated errors from background flushes */ get errors() { return this._errors; } /** * Wait for all background writes to finish. * MUST be called before exit to ensure no lost writes. */ async onIdle() { // Indicate that we're done writing requests this._iterableQueue.done(); // Wait for the background writer to exit await this._backgroundWriter; } /** * Calls `writer` for any pending items and clears pending items queue. * @returns Result from `writer` function or `undefined` if no items pending */ async flush() { const itemsToWrite = this._pendingItems; this._pendingItems = []; this._pendingSize = 0; if (itemsToWrite.length === 0) { return undefined; } else { try { await this._writer(itemsToWrite); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { this._errors.push(error); } } } /** * Adds an item to the pending queue, flushing the queue before * adding the item if the new item would cause the item limit * or size limit to be exceeded. * * @param item Item to be added to the queue */ async enqueue(item) { await this._iterableQueue.enqueue(item); } } exports.Chunker = Chunker; //# sourceMappingURL=chunker.js.map