From ed5795e0d23f3f47f5b8ebdc4d22c6b2068d3a07 Mon Sep 17 00:00:00 2001 From: Sunita Prajapati Date: Mon, 2 Nov 2026 12:01:27 +0530 Subject: [PATCH] fix(core): build chunk() batches by appending and reset size per chunk chunk() assigned into an index, so an oversized first item left a hole at chunks[0] that made aggregateErrors throw on every flush. The rolling KB accumulator was also never reset, so once exceeded every remaining item became its own batch. Co-Authored-By: Claude Sonnet 5.5 --- packages/core/src/__tests__/util.test.ts | 16 +++++++++ packages/core/src/util.ts | 43 ++++++++++-------------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/packages/core/src/__tests__/util.test.ts b/packages/core/src/__tests__/util.test.ts index bdc03e049..ea79a3181 100644 --- a/packages/core/src/__tests__/util.test.ts +++ b/packages/core/src/__tests__/util.test.ts @@ -42,6 +42,22 @@ describe('#chunk', () => { chunk([about500bString, about500bString, about500bString], 2, 1) ).toEqual([[about500bString, about500bString], [about500bString]]); }); + + it('returns a dense array when the first item alone exceeds max kb', () => { + const big = { blob: 'x'.repeat(2 * 1024) }; + const result = chunk([big, { id: 'b' }, { id: 'c' }], 100, 1); + + expect(Object.keys(result)).toHaveLength(result.length); + expect(result[0]).toEqual([big]); + result.forEach((batch) => expect(batch.length).toBeGreaterThan(0)); + }); + + it('resets the rolling size per chunk so the count limit is reachable', () => { + const items = Array.from({ length: 12 }, (_, i) => ({ id: i })); + const result = chunk(items, 5, 500); + + expect(result.map((batch) => batch.length)).toEqual([5, 5, 2]); + }); }); describe('allSettled', () => { diff --git a/packages/core/src/util.ts b/packages/core/src/util.ts index 8a89a8a78..9c56813fa 100644 --- a/packages/core/src/util.ts +++ b/packages/core/src/util.ts @@ -29,34 +29,27 @@ export const chunk = (array: T[], count: number, maxKB?: number): T[][] => { return []; } - let currentChunk = 0; let rollingKBSize = 0; - const result: T[][] = array.reduce( - (chunks: T[][], item: T, index: number) => { - if (maxKB !== undefined) { - rollingKBSize += sizeOf(item); - // If we overflow chunk until the previous index, else keep going - if (rollingKBSize >= maxKB) { - chunks[++currentChunk] = [item]; - return chunks; - } - } - - if (index !== 0 && index % count === 0) { - chunks[++currentChunk] = [item]; - } else { - if (chunks[currentChunk] === undefined) { - chunks[currentChunk] = []; - } - chunks[currentChunk].push(item); - } - + return array.reduce((chunks: T[][], item: T) => { + const itemKBSize = maxKB === undefined ? 0 : sizeOf(item); + const currentChunk = chunks[chunks.length - 1]; + const isOverMaxKB = + maxKB !== undefined && rollingKBSize + itemKBSize >= maxKB; + + if ( + currentChunk === undefined || + currentChunk.length >= count || + isOverMaxKB + ) { + rollingKBSize = itemKBSize; + chunks.push([item]); return chunks; - }, - [] - ); + } - return result; + rollingKBSize += itemKBSize; + currentChunk.push(item); + return chunks; + }, []); }; export const getAllPlugins = (timeline: Timeline) => {