Persistent indexes
search() converts every item on every call. For autocomplete, live search, command palettes and file finders, build an index once and query it many times. A FuzzyIndex costs about 2 ms to build for 10,000 short items, and each query is then about 10x faster than search() on 1,000 to 10,000 items (see Benchmarks).
FuzzyIndex
Section titled “FuzzyIndex”import { FuzzyIndex } from 'rapid-fuzzy';
const index = new FuzzyIndex(['TypeScript', 'JavaScript', 'Python']);index.size; // 3
index.search('typscript', { maxResults: 5 }); // or index.search('typscript', 5)index.closest('tsc'); // → 'TypeScript'
// Indices and scores only: no strings are copied back to JavaScriptindex.searchIndices('typscript', { maxResults: 5 });// → [{ index: 0, score: 0.86, positions: [] }]
// Change the items without rebuildingindex.add('Rust');index.addMany(['Go', 'Zig']);index.remove(2); // swap-remove: the last item moves to position 2; false if out of rangeWhen you no longer need it, index.destroy() frees its Rust-side memory right away (see Memory).
index.search(query, options) always returns the same results as search(query, items, options) over the same items. Prefer searchIndices() in hot paths: it avoids copying item strings and reduces garbage-collection pressure.
Building large indexes off the main thread
Section titled “Building large indexes off the main thread”In Node.js, FuzzyIndex.fromAsync(items) builds the index on the libuv thread pool and resolves with it. In the WebAssembly build it builds synchronously.
const big = await FuzzyIndex.fromAsync(manyStrings);Incremental type-ahead
Section titled “Incremental type-ahead”A FuzzyIndex remembers which items matched the previous query. When the next query only adds characters to it (typing appl, then apple), only those items are scored again:
index.search('appl'); // scores all items, remembers the matchesindex.search('apple'); // scores only the items that matched 'appl'index.search('xyz'); // not an extension: a full scan- Matches are remembered only when fewer than half of the items matched, because a short first keystroke that matches most items is not worth narrowing to.
- A search with a positive
minScoredoes not update them. - They are used only for plain extensions: not once the query contains
!,^,$,'or\, and not when it contains a character that matching normalizes, such as an accented letter.
Results are the same with or without this cache. In the type-ahead benchmark, 12 keystrokes over 10,000 items took about 1.6 ms in total.
FuzzyObjectIndex
Section titled “FuzzyObjectIndex”FuzzyObjectIndex is the indexed form of searchObjects(). It keeps your objects on the JavaScript side and their key texts on the Rust side:
import { FuzzyObjectIndex } from 'rapid-fuzzy';
const userIndex = new FuzzyObjectIndex(users, { keys: [{ name: 'name', weight: 2 }, 'email'],});
userIndex.search('john', { maxResults: 10 });userIndex.closest('john'); // the best-matching object, or nulluserIndex.closest('john', { minScore: 0.9, scoreMode: 'matched' }); // or closest('john', 0.9)userIndex.destroy();It has the same members as FuzzyIndex: size, search(), closest(), add(), addMany(), remove() and destroy(). In Node.js it also has serialize() and FuzzyObjectIndex.deserialize(), for items that are JSON-serializable. addMany() is atomic: if one item is invalid, nothing is added, and the error names the item.
Serialization
Section titled “Serialization”Save a FuzzyIndex (or a KeyedFuzzyIndex) and restore it later:
const data = index.serialize(); // a Buffer in Node.js, a Uint8Array in the browser buildconst restored = FuzzyIndex.deserialize(data);restored.search('aple');The data holds the item strings in UTF-8, with 4 bytes of length each and a 12-byte header. It does not hold precomputed search data, so deserialize() takes about as long as new FuzzyIndex(items). Serialization is useful for persisting an index you built up with add() and remove(), without keeping the source array.
Memory
Section titled “Memory”An index stores the item strings and precomputed search data on the Rust side: a character mask per item and, for non-ASCII items, their UTF-32 text. ASCII text is stored once.
- Call
destroy()when you no longer need an index, to free its memory immediately. - Indexes you drop without
destroy()are reclaimed by the garbage collector. This happens only once the event loop gets a turn to run their finalizers, so code that creates many indexes in a tight synchronous loop should calldestroy()on each. - After
destroy(), an index stays usable. It behaves as an empty index: searches return nothing,sizeis 0, andadd()andaddMany()work as before.