krisk/Fuse · error · Error

Invalid doc index: must be a non-negative integer within the

Error message

Invalid doc index: must be a non-negative integer within the bounds of the docs array

What it means

Fuse.removeAt(idx) validates its argument before any mutation: idx must be an integer, non-negative, and within bounds of the docs array. The check is atomic — an invalid call leaves the collection and inverted index completely untouched (the old implementation spliced first and threw after, leaving partial state).

Source

Thrown at src/core/index.ts:198

      }

      // Filter docs in a single pass instead of reverse-splicing
      const toRemove = new Set(indicesToRemove)
      this._docs = this._docs.filter((_, i) => !toRemove.has(i))
      this._myIndex.removeAll(indicesToRemove)

      this._invalidateSearcherCache()
    }

    return results
  }

  removeAt(idx: number): T {
    // Validate before any mutation. The previous code spliced `_docs` first
    // and let FuseIndex.removeAt throw afterward — partial-state on invalid
    // input. Atomic now.
    if (!Number.isInteger(idx) || idx < 0 || idx >= this._docs.length) {
      throw new Error(ErrorMsg.INVALID_DOC_INDEX)
    }

    if (this._invertedIndex) {
      removeAndShiftInvertedIndex(this._invertedIndex, [idx])
    }
    const doc = this._docs.splice(idx, 1)[0]
    this._myIndex.removeAt(idx)
    this._invalidateSearcherCache()
    return doc
  }

  _invalidateSearcherCache(): void {
    this._lastQuery = null
    this._lastSearcher = null
  }

  getIndex(): FuseIndex<T> {
    return this._myIndex

View on GitHub (pinned to edf2fb608e)

Solutions

  1. Validate before calling: ensure Number.isInteger(idx) && idx >= 0 && idx < docs.length.
  2. Prefer remove(doc) or remove(predicate) when you have the document object instead of positional removal.
  3. Recompute the index immediately before removal rather than caching it; iterate backwards when removing multiple items.

Example fix

// before
list.forEach((_, i) => fuse.removeAt(i)) // out of bounds as list shrinks
// after
for (let i = list.length - 1; i >= 0; i--) fuse.removeAt(i)
Defensive patterns

Strategy: validation

Validate before calling

const canRemoveAt = (docsLen, idx) =>
  Number.isInteger(idx) && idx >= 0 && idx < docsLen
if (canRemoveAt(docs.length, idx)) {
  fuse.removeAt(idx)
}

Type guard

const isValidDocIndex = (idx, len) =>
  Number.isInteger(idx) && idx >= 0 && idx < len

Try / catch

try {
  fuse.removeAt(idx)
} catch (e) {
  if (String(e.message).startsWith('Invalid doc index')) {
    return // stale index / already removed; collection untouched
  }
  throw e
}

Prevention

When it happens

Trigger: fuse.removeAt(-1), a float like 1.5, NaN/undefined from an unbound handler, or an index >= docs.length — e.g. reusing a stale index captured before prior removals shrank the collection.

Common situations: Removing items in a loop without adjusting indices; deleting based on a cached position; passing an id or event object instead of the numeric position.

Related errors


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