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.