nodejs/node · error · InvalidArgumentError

UND_ERR_INVALID_ARG

UND_ERR_INVALID_ARG

Error message

factory must be a function.

What it means

Thrown by the Agent constructor. The factory option selects which Dispatcher (Client or Pool) the Agent creates per origin; the default returns a Pool (or a single-connection Client when connections === 1). Because the Agent invokes factory(origin, opts) as a function on every new origin, a non-function value cannot be called, so it is rejected at construction.

Source

Thrown at deps/undici/src/lib/dispatcher/agent.js:27

const kOnConnect = Symbol('onConnect')
const kOnDisconnect = Symbol('onDisconnect')
const kOnConnectionError = Symbol('onConnectionError')
const kOnDrain = Symbol('onDrain')
const kFactory = Symbol('factory')
const kOptions = Symbol('options')
const kOrigins = Symbol('origins')

function defaultFactory (origin, opts) {
  return opts && opts.connections === 1
    ? new Client(origin, opts)
    : new Pool(origin, opts)
}

class Agent extends DispatcherBase {
  constructor ({ factory = defaultFactory, maxOrigins = Infinity, connect, ...options } = {}) {
    if (typeof factory !== 'function') {
      throw new InvalidArgumentError('factory must be a function.')
    }

    if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') {
      throw new InvalidArgumentError('connect must be a function or an object')
    }

    if (typeof maxOrigins !== 'number' || Number.isNaN(maxOrigins) || maxOrigins <= 0) {
      throw new InvalidArgumentError('maxOrigins must be a number greater than 0')
    }

    super(options)

    if (connect && typeof connect !== 'function') {
      connect = { ...connect }
    }

    this[kOptions] = { ...util.deepClone(options), maxOrigins, connect }
    this[kFactory] = factory

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Provide factory as a function: new Agent({ factory: (origin, opts) => new Pool(origin, opts) }).
  2. If you want the default behavior, omit the factory option entirely.
  3. Check that any imported Dispatcher class is actually defined (not undefined) before referencing it.

Example fix

// before
const agent = new Agent({ factory: Pool })
// after
const agent = new Agent({ factory: (origin, opts) => new Pool(origin, opts) })
Defensive patterns

Strategy: type-guard

Validate before calling

function buildAgent(options = {}) {
  if (options.factory !== undefined && typeof options.factory !== 'function') {
    throw new TypeError('Agent options.factory must be a function')
  }
  return new Agent(options)
}

Type guard

const isFactory = (f) => typeof f === 'function'

Prevention

When it happens

Trigger: new Agent({ factory: <not a function> }) — e.g. factory: Pool, factory: {}, factory: 'pool', or a destructured import that resolved to undefined.

Common situations: Passing a class reference without wrapping it (factory: Pool instead of (origin, opts) => new Pool(origin, opts)); a default import that returned undefined due to an ESM/CJS interop issue; a config object where factory was overwritten by a merge.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/f4aa8515eb46d880. Report an issue: GitHub.