Automattic/mongoose · error · TypeError

batchSize must be a number

Error message

batchSize must be a number

What it means

eachAsync on a query or aggregation cursor accepts an options object whose batchSize controls how many documents are fetched per batch. The helper validates batchSize eagerly: it must be a JavaScript number. Passing '100' (a string from an env var or query param) or any non-number throws TypeError before iteration starts; the check only runs when batchSize is not null/undefined.

Source

Thrown at lib/helpers/cursor/eachAsync.js:50

  const enqueue = asyncQueue();

  let aborted = false;

  return new Promise((resolve, reject) => {
    if (signal != null) {
      if (signal.aborted) {
        return resolve(null);
      }

      signal.addEventListener('abort', () => {
        aborted = true;
        return resolve(null);
      }, { once: true });
    }

    if (batchSize != null) {
      if (typeof batchSize !== 'number') {
        throw new TypeError('batchSize must be a number');
      } else if (!Number.isInteger(batchSize)) {
        throw new TypeError('batchSize must be an integer');
      } else if (batchSize < 1) {
        throw new TypeError('batchSize must be at least 1');
      }
    }

    iterate((err, res) => {
      if (err != null) {
        return reject(err);
      }
      resolve(res);
    });
  });

  function iterate(finalCallback) {
    let handleResultsInProgress = 0;
    let currentDocumentIndex = 0;

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Coerce before passing: batchSize: Number(process.env.BATCH_SIZE).
  2. Parse query params explicitly: const batchSize = parseInt(req.query.batchSize, 10).
  3. Omit batchSize entirely (pass undefined) when it is not configured - the validation skips null/undefined.

Example fix

// before
const batchSize = process.env.BATCH_SIZE; // string
await Model.find().cursor().eachAsync(fn, { batchSize }); // TypeError

// after
const batchSize = process.env.BATCH_SIZE != null ? Number(process.env.BATCH_SIZE) : undefined;
await Model.find().cursor().eachAsync(fn, { batchSize });
Defensive patterns

Strategy: validation

Validate before calling

function toBatchSize(v) {
  if (v == null) return undefined;
  const n = Number(v);
  if (!Number.isInteger(n) || n < 1) {
    throw new TypeError(`invalid batchSize: ${v}`);
  }
  return n;
}
await cursor.eachAsync(fn, { batchSize: toBatchSize(rawValue) });

Type guard

function isValidBatchSize(v) {
  return typeof v === 'number' && Number.isInteger(v) && v >= 1;
}

Prevention

When it happens

Trigger: cursor.eachAsync(fn, { batchSize: '100' }); batchSize read from process.env.BATCH_SIZE or a URL query parameter without conversion; a shared utility forwarding caller-supplied strings into eachAsync.

Common situations: Config-driven batch sizes in ETL/backfill scripts; API handlers forwarding query-string options directly into cursor options; defaults that accidentally set batchSize to a non-number.

Related errors


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/0bd12bab595f93bd. Report an issue: GitHub.