meteor/meteor · error · MiniMongoQueryError

unknown operator: ${unprefixedKeys[0]}

Error message

unknown operator: ${unprefixedKeys[0]}

What it means

During upsert document inference, populateDocumentWithObject treats an object as a literal sub-document only if it has unprefixed keys or is empty. If it mixes unprefixed (literal) keys with '$'-prefixed operator keys, the first unprefixed key is reported as 'unknown operator' because operators are not allowed to coexist with literal fields in the inferred insert document.

Source

Thrown at packages/minimongo/common.js:1240

function populateDocumentWithKeyValue(document, key, value) {
  if (value && Object.getPrototypeOf(value) === Object.prototype) {
    populateDocumentWithObject(document, key, value);
  } else if (!(value instanceof RegExp)) {
    insertIntoDocument(document, key, value);
  }
}

// Handles a key, value pair to put in the selector document
// if the value is an object
function populateDocumentWithObject(document, key, value) {
  const keys = Object.keys(value);
  const unprefixedKeys = keys.filter(op => op[0] !== '$');

  if (unprefixedKeys.length > 0 || !keys.length) {
    // Literal (possibly empty) object ( or empty object )
    // Don't allow mixing '$'-prefixed with non-'$'-prefixed fields
    if (keys.length !== unprefixedKeys.length) {
      throw new MiniMongoQueryError(`unknown operator: ${unprefixedKeys[0]}`);
    }

    validateObject(value, key);
    insertIntoDocument(document, key, value);
  } else {
    Object.keys(value).forEach(op => {
      const object = value[op];

      if (op === '$eq') {
        populateDocumentWithKeyValue(document, key, object);
      } else if (op === '$all') {
        // every value for $all should be dealt with as separate $eq-s
        object.forEach(element =>
          populateDocumentWithKeyValue(document, key, element)
        );
      }
    });
  }

View on GitHub (pinned to 5076d2f818)

Solutions

  1. Do not mix literal and operator keys in a single equality value; split across fields or use $and.
  2. Provide an explicit upsert insert document.
  3. Restructure the selector so equality values are either fully literal or fully operator objects.

Example fix

// before
coll.update({ a: { b: 1, $gt: 0 } }, { $set: { x: 1 } }, { upsert: true });
// after
coll.update({ $and: [{ 'a.b': 1 }, { a: { $gt: 0 } }] }, { $set: { x: 1 } }, { upsert: true });
Defensive patterns

Strategy: validation

Validate before calling

function isPureObjectValue(obj) {
  if (!obj || typeof obj !== 'object') return true;
  const keys = Object.keys(obj);
  const ops = keys.filter(k => k[0] === '$');
  const literals = keys.filter(k => k[0] !== '$');
  return ops.length === 0 || literals.length === 0;
}

Type guard

function isMixedKeyObject(obj) {
  if (!obj || typeof obj !== 'object') return false;
  const keys = Object.keys(obj);
  return keys.some(k => k[0] === '$') && keys.some(k => k[0] !== '$');
}

Prevention

When it happens

Trigger: An upsert equality clause whose value object mixes literal and operator keys, e.g. update({a: {b: 1, $gt: 0}}, ..., {upsert: true}). The upsert path tries to store 'a' as a literal {b:1,$gt:0} which is illegal.

Common situations: Building equality values by merging a literal object and an operator object into one, then upserting. Less common than 227 because this specifically concerns upsert insert-document inference.

Related errors


AI-assisted analysis of meteor/meteor@5076d2f818 (2026-08-13). Data as JSON: /api/errors/b89863b633958f76. Report an issue: GitHub.