Skip to content

chunk ​

Process matching rows in fixed-size pages. Use when you want to walk a large result set without holding it all in memory.

Signature ​

ts
FedacoBuilder<T>.chunk(
  count: number,
  callback: (models: T[], page: number) => Promise<boolean | void> | boolean | void,
): Promise<boolean>

Parameters ​

NameDescription
countPage size (rows per chunk).
callbackCalled once per chunk with the page's models and the 1-indexed page number. Returning false stops iteration.

Returns ​

Promise<boolean> — true if iteration completed, false if a callback short-circuited.

Real-World Use Cases ​

1. Server-side batch processing ​

ts
await User.createQuery()
  .where('active', true)
  .orderBy('id')
  .chunk(500, async (users) => {
    for (const u of users) {
      await sendWeeklyDigest(u);
    }
  });

Each chunk is a separate SELECT ... LIMIT 500 OFFSET .... Memory stays bounded — only 500 rows are hydrated at a time.

2. Stop early ​

ts
let processed = 0;

await Order.createQuery()
  .where('status', 'pending')
  .orderBy('id')
  .chunk(100, (orders) => {
    for (const o of orders) {
      processOrder(o);
      processed++;
    }
    if (processed >= 1000) return false; // stop iteration
  });

3. Async work per chunk ​

ts
await User.createQuery()
  .orderBy('id')
  .chunk(200, async (batch) => {
    await Promise.all(batch.map((u) => syncToCrm(u)));
  });

Fedaco awaits your callback before fetching the next chunk, so back-pressure is automatic.

4. Filtered + ordered chunking ​

ts
await Post.createQuery()
  .where('archived', false)
  .where('updated_at', '<', oneYearAgo)
  .orderBy('updated_at')
  .chunk(1000, async (posts) => {
    await Post.createQuery().whereIn('id', posts.map((p) => p.id)).update({ archived: true });
  });

chunk vs chunkById vs each ​

MethodStrategyWhen to use
chunkLIMIT + OFFSETStatic result set; rows aren't being inserted/deleted while you iterate.
chunkByIdWHERE id > lastDefault for safety — stable when rows shift, primary-key-ordered.
eachIteration on top of chunkWhen you want a row-at-a-time API.
eachByIdIteration on top of chunkByIdSame as each, but with the safer paging strategy.

WARNING

chunk uses OFFSET. If your callback inserts or deletes rows in the same scan range, the offsets shift and you can skip or re-visit rows. Switch to chunkById whenever in doubt.

Common Pitfalls ​

  • Always order, otherwise the LIMIT/OFFSET pages have no defined contents.
  • Awaitable callbacks must be async or return a Promise. A synchronous callback that internally fires off setTimeout-style work won't be awaited.
  • Memory is bounded per chunk, not total. If you collect all rows into an outer array, you've defeated the purpose.
  • Returning false stops the loop; returning anything else (or undefined) continues.

See Also ​

  • chunkById — keyset chunking; safer when the table is being written to.
  • each / eachById — per-row iteration.
  • forPageAfterId — single keyset page (the building block for chunkById).
  • paginate — UI pagination instead of server-side iteration.

Released under the MIT License.