UNPKG

batched-items-accumulator

Version:

A lightweight utility for Node.js projects that accumulates items into fixed-size batches (number-of-items wise), preserving insertion order. Streams items directly into batches at runtime, eliminating the overhead of post-processing a 1D array - a common

152 lines 6.49 kB
"use strict"; /** * Copyright 2025 Ori Cohen https://github.com/ori88c * https://github.com/ori88c/batched-items-accumulator * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.BatchedAccumulator = void 0; /** * The `BatchedAccumulator` class facilitates accumulating items into fixed-size * batches, commonly used for delayed post-processing tasks such as bulk-write * operations to a database or blob storage. Each batch is an array of `ItemType` * items, preserving the original insertion order. * * ### Example * Given a `BatchedAccumulator` instance with a batch size of 4, inserting the * items 1, 2, 3, 4, 5, 6, 7 results in the following batches: * - [1, 2, 3, 4] (a full batch) * - [5, 6, 7] (a partial batch with the remaining items) * * ### Purpose * While simple in design, this class serves as a **building block** for more complex * solutions. It abstracts batch management, allowing users to focus on their application * logic while leveraging a well-tested, efficient batching mechanism. * * ### Typical Use Case * Applications often accumulate data from user interactions or message queues before * persisting them in bulk to storage solutions like Amazon S3, Azure Blob Storage, or * a database. * To reduce network overhead, items are temporarily stored in memory and written in * bulk, once a sufficient number has been collected or a timeout has been reached. * A corresponding example is available in this package's README. * * ### Design Decision: No Peeking (`extractAccumulatedBatches`) * To maintain integrity, the class **does not provide direct access** to accumulated * items or batches. Exposing internal references could allow unintended modifications, * such as appending items to a full batch. * Instead, the `extractAccumulatedBatches` method **transfers ownership** of all batches * to the caller while resetting the instance to a clean state. This ensures the component's * guarantees remain intact and prevents accidental modifications of extracted batches. * However, while direct peeking is not possible, users can utilize the getter methods * `batchesCount`, `isEmpty`, and `accumulatedItemsCount` to assess whether extraction is * needed. */ class BatchedAccumulator { constructor(_batchSize) { this._batchSize = _batchSize; /** * Stores accumulated items as an array of batches, * where each batch is an array of `ItemType` items. */ this._batches = []; if (!isNaturalNumber(this._batchSize)) { // prettier-ignore throw new Error(`${BatchedAccumulator.name} expects a natural number for ` + `batch size, received ${this._batchSize}`); } } /** * Returns the number of batches currently held by this instance. * Each batch contains exactly `batchSize` items, except for the * last batch, which may contain fewer items. * * @returns The number of batches held by this instance. */ get batchesCount() { return this._batches.length; } /** * Indicates whether this instance has accumulated any items. * * @returns `true` if no items have been accumulated, `false` otherwise. */ get isEmpty() { return this.batchesCount === 0; } /** * Returns the total number of accumulated items across all batches. * For example, if there are 5 full batches and the batch size is 100, * the output will be 500. * * ### Use Case: Conditional Extraction * This method is useful for determining whether a minimum threshold of * accumulated items has been reached before extracting batches, helping * to avoid excessively small bulk operations. * * @returns The total number of accumulated items across all batches. */ get accumulatedItemsCount() { const lastBatch = this._batches.at(-1); if (lastBatch === undefined) { return 0; } return lastBatch.length + (this.batchesCount - 1) * this._batchSize; } /** * Adds an item to the accumulator, grouping it into a batch of fixed size. * If the last batch is full or no batch exists, a new batch is created. * * @param item The item to accumulate. */ push(item) { const lastBatch = this._batches.at(-1); if (lastBatch === undefined || lastBatch.length === this._batchSize) { // No batch exists yet in the current cycle (after the last extraction), // or the last batch is full. Create a new batch. const newBatch = [item]; this._batches.push(newBatch); } else { // Append the item to the existing last batch. lastBatch.push(item); } } /** * Extracts and returns the accumulated batches as a 2D array, where each batch * is a fixed-size array of `ItemType` items. The last batch may contain fewer * elements if the total count is not a multiple of the batch size. * * Calling this method **transfers ownership** of the extracted batches to the * caller, meaning the instance will **no longer retain them**. The accumulator * is reset, clearing its internal storage to begin a new accumulation cycle. * * @returns A 2D array containing the extracted batches, each represented as a * fixed-size array of `ItemType` items. */ extractAccumulatedBatches() { const takenBatches = this._batches; this._batches = []; // Resets with a new reference. return takenBatches; } } exports.BatchedAccumulator = BatchedAccumulator; function isNaturalNumber(num) { if (typeof num !== 'number') { return false; } const floored = Math.floor(num); return floored >= 1 && floored === num; } //# sourceMappingURL=batched-items-accumulator.js.map