krisk/Fuse · error · Error

Invalid field query for key '${key}': ${reason}

Error message

Invalid field query for key '${key}': ${reason}

What it means

compileClause compiles an extended-search field clause into matchers and requires the clause to be a plain operator object (no top-level $and/$or). If the clause is not a plain object (string, array, number, null), it throws INVALID_FIELD_QUERY with the key path and reason 'each clause must be an operator object'.

Source

Thrown at src/search/extended/objectQuery.ts:41

// Anything deeper, structural siblings, empty objects/arrays, unknown operators,
// non-string or empty (post-normalization) values throw.
//
// Negation is `$not` wrapping exactly one operator, e.g. { $not: { $contains } }.
// It sits alongside other operators in a clause (they AND together) and resolves
// to the corresponding inverse matcher.

const AND = '$and'
const OR = '$or'
const NOT = '$not'

function isPlainObject(value: unknown): boolean {
  return isObjectLike(value) && !isArray(value)
}

// Compile an operator-only object (no `$and`/`$or`) into one AND group.
function compileClause(clause: any, keyPath: string, options: any): Matcher[] {
  if (!isPlainObject(clause)) {
    throw new Error(
      ErrorMsg.INVALID_FIELD_QUERY(
        keyPath,
        'each clause must be an operator object'
      )
    )
  }

  const ops = Object.keys(clause)
  if (!ops.length) {
    throw new Error(ErrorMsg.INVALID_FIELD_QUERY(keyPath, 'empty query'))
  }

  const group: Matcher[] = []
  for (let i = 0; i < ops.length; i += 1) {
    const op = ops[i]

    if (op === AND || op === OR) {
      throw new Error(

View on GitHub (pinned to edf2fb608e)

Solutions

  1. Wrap each field value in an operator object, e.g. { title: { $contains: 'world' } } instead of a bare string or array.
  2. Validate the query shape (plain-object clauses only) before calling search.
  3. For multiple values, use one clause per element combined with $or (or $in where supported).

Example fix

// before
fuse.search({ title: ['world'] })
// after
fuse.search({ title: { $contains: 'world' } })
Defensive patterns

Strategy: validation

Validate before calling

const isOperatorObject = (v) =>
  v !== null && typeof v === 'object' && !Array.isArray(v)
for (const [key, value] of Object.entries(query)) {
  if (!isOperatorObject(value)) {
    throw new TypeError(`Field '${key}' must be an operator object like { $eq: value }`)
  }
}
fuse.search(query)

Type guard

const isOperatorObject = (v) =>
  v !== null && typeof v === 'object' && !Array.isArray(v)

Try / catch

try {
  return fuse.search(query)
} catch (e) {
  if (e.message.startsWith('Invalid field query')) {
    console.warn('Malformed extended-search clause:', e.message)
    return []
  }
  throw e
}

Prevention

When it happens

Trigger: An extended-search object query like { title: 'plain string' }, { title: ['a','b'] }, or { title: null } — any non-plain-object clause routed through the object-query compiler.

Common situations: Mixing plain-string query values with object syntax in extended search; queries built from JSON where values arrive as arrays; forgetting to wrap values in a $-operator object.

Related errors


AI-assisted analysis of krisk/Fuse@edf2fb608e (2026-09-02). Data as JSON: /api/errors/a2dd70e63215a87d. Report an issue: GitHub.