Migrating from uFuzzy to rapid-fuzzy
uFuzzy is a lightweight, fast fuzzy search library written in pure JavaScript using regex-based matching. rapid-fuzzy uses the nucleo fuzzy matching engine (same as the Helix editor) with Rust-powered native bindings, and provides a simpler API with ready-to-use sorted results.
Installation
Section titled “Installation”# Remove uFuzzynpm uninstall @leeoniya/ufuzzy
# Install rapid-fuzzynpm install rapid-fuzzyQuick Start
Section titled “Quick Start”// Before (uFuzzy) — intraMode: 1 allows one missing letter per term;// with the default options 'typscript' finds nothingimport uFuzzy from '@leeoniya/ufuzzy';const uf = new uFuzzy({ intraMode: 1 });const haystack = ['TypeScript', 'JavaScript', 'Python'];const [idxs, info, order] = uf.search(haystack, 'typscript');const results = order.map(i => haystack[info.idx[i]]);console.log(results[0]); // 'TypeScript'
// After (rapid-fuzzy)import { search } from 'rapid-fuzzy';const results = search('typscript', ['TypeScript', 'JavaScript', 'Python']);console.log(results[0].item); // 'TypeScript'API Mapping
Section titled “API Mapping”| uFuzzy | rapid-fuzzy | Notes |
|---|---|---|
new uFuzzy(opts) + uf.search(haystack, needle) |
search(query, items) |
No constructor; returns sorted results directly |
info.idx[order[i]] → index into haystack |
results[i].item |
No manual mapping needed |
info.ranges |
results[i].positions |
Use { includePositions: true } |
uFuzzy.highlight() / mark() |
highlight(item, positions, open, close) |
Built-in utility |
opts.intraMode / opts.interMode |
Query syntax (^, $, ', !) |
Different approach to matching control |
Manual order.slice(0, n) |
{ maxResults: n } or pass a number |
search(q, items, 5) |
| Manual sort + slice + map | Automatic | Results are sorted by relevance |
Result Structure
Section titled “Result Structure”uFuzzy returns three separate values that require manual assembly: idxs (the indices of the matching items, [] when nothing matches), info (match details such as ranges) and order (the ranking, as indices into info.idx and info.ranges; info can leave out some of idxs, so map through info.idx, as uFuzzy’s own README does). All three are null for an empty query. info and order are also null when more items match than infoThresh, the fourth argument of search() (1,000 by default), and idxs is then left unranked, so the uFuzzy snippets in this guide pass Infinity to always rank. rapid-fuzzy returns ready-to-use sorted results:
// uFuzzy — 3 return values, manual result constructionconst uf = new uFuzzy();const [idxs, info, order] = uf.search(haystack, 'query', 0, Infinity);
// Check for no resultsif (idxs == null || idxs.length === 0) { // no matches (idxs is null for an empty query)}
// Assemble results manually (order is null for an empty query)const results = (order ?? []).map((idx) => ({ item: haystack[info.idx[idx]], ranges: info.ranges[idx],}));
// rapid-fuzzy — sorted results returned directlyconst results = search('query', items);// → [{ item: '…', score: 0.86, index: 3, positions: [] }, ...]Common Patterns
Section titled “Common Patterns”Basic search
Section titled “Basic search”// uFuzzyconst uf = new uFuzzy();const [idxs, info, order] = uf.search(haystack, 'query', 0, Infinity);const top = order?.length ? haystack[info.idx[order[0]]] : undefined;
// rapid-fuzzyconst results = search('query', items);const top = results[0]?.item;Limiting results
Section titled “Limiting results”// uFuzzy — manual slicingconst [idxs, info, order] = uf.search(haystack, 'query', 0, Infinity);const top5 = (order ?? []).slice(0, 5).map(i => haystack[info.idx[i]]);
// rapid-fuzzy — either form worksconst results = search('query', items, 5);const results = search('query', items, { maxResults: 5 });Finding the best match
Section titled “Finding the best match”// uFuzzyconst [idxs, info, order] = uf.search(haystack, 'query', 0, Infinity);const best = order?.length ? haystack[info.idx[order[0]]] : null;
// rapid-fuzzyimport { closest } from 'rapid-fuzzy';const best = closest('query', items);Match highlighting
Section titled “Match highlighting”// uFuzzy — mark function (assuming at least one match)const uf = new uFuzzy();const [idxs, info, order] = uf.search(haystack, 'query', 0, Infinity);const highlighted = uFuzzy.highlight( haystack[info.idx[order[0]]], info.ranges[order[0]],);
// rapid-fuzzy — highlight utility with positionsimport { search, highlight, highlightRanges } from 'rapid-fuzzy';const results = search('query', items, { includePositions: true });highlight(results[0].item, results[0].positions, '<b>', '</b>');
// Callback form (React, JSX)highlight(results[0].item, results[0].positions, (matched) => `<mark>${matched}</mark>`);
// Raw ranges for custom renderinghighlightRanges(results[0].item, results[0].positions);// → [{ start: 0, end: 1, matched: true }, ...]Object search
Section titled “Object search”uFuzzy does not have built-in object search — you must extract string values, search them, and map results back manually. rapid-fuzzy provides dedicated object search:
// uFuzzy — manual key extractionconst names = users.map(u => u.name);const [idxs, info, order] = uf.search(names, 'john', 0, Infinity);const matchedUsers = (order ?? []).map(i => users[info.idx[i]]);
// rapid-fuzzy — built-in object search with weighted keysimport { searchObjects } from 'rapid-fuzzy';const results = searchObjects('john', users, { keys: [ { name: 'name', weight: 2.0 }, { name: 'email', weight: 1.0 }, ],});console.log(results[0].item); // { name: 'John Smith', email: '...' }Persistent index
Section titled “Persistent index”// uFuzzy — no persistent index, re-searches the full array each time
// rapid-fuzzy — persistent index with mutation supportimport { FuzzyIndex, FuzzyObjectIndex } from 'rapid-fuzzy';
const index = new FuzzyIndex(items);index.search('query', { maxResults: 10 });
// Mutate without rebuildingindex.add('new item');index.remove(2); // swap-remove by index
// Object index with weighted keysconst userIndex = new FuzzyObjectIndex(users, { keys: [ { name: 'name', weight: 2.0 }, { name: 'email', weight: 1.0 }, ],});
// Free Rust-side memory when doneindex.destroy();userIndex.destroy();Performance
Section titled “Performance”From the benchmarks (Node.js 22, Linux x64, Intel Xeon @ 2.10GHz, 4 vCPUs; ops/s, higher is better; uFuzzy with its default options):
| Dataset size | rapid-fuzzy search() |
FuzzyIndex |
uFuzzy |
|---|---|---|---|
| Small (20 items) | 157,995 ops/s | 208,250 ops/s | 383,890 ops/s |
| Medium (1K items) | 3,502 ops/s | 50,318 ops/s | 16,858 ops/s |
| Large (10K items) | 508 ops/s | 4,803 ops/s | 1,404 ops/s |
| Large (10K items, rotating queries) | 451 ops/s | 4,644 ops/s | 1,037 ops/s |
| Huge (100K items) | — | 1,125 ops/s | 374 ops/s |
uFuzzy was faster than rapid-fuzzy on 20 items (where, with its default options, it found no match for the benchmark query aple), and faster than standalone search(), which converts every item on each call, at every size. FuzzyIndex was about 3-4.5x faster than uFuzzy on 1K-10K items and about 3x faster on 100K items (on 4 threads; about 1.3x on one). The two libraries do not find the same matches, so these numbers compare the cost of a search, not identical work.
Why Choose rapid-fuzzy Over uFuzzy?
Section titled “Why Choose rapid-fuzzy Over uFuzzy?”uFuzzy is tiny, has no native addon or WebAssembly module to load, and is fast in the browser. rapid-fuzzy is worth it when you need:
- Distance functions: Levenshtein, Damerau-Levenshtein, Hamming, Indel, Jaro, Jaro-Winkler, Sorensen-Dice, and token-based ratios — useful beyond search
- Simpler API: Returns ready-to-use sorted results instead of three arrays requiring manual assembly
- Batch APIs:
levenshteinBatch,jaroWinklerMany, etc. for bulk distance computations - Weighted object search:
searchObjects()andFuzzyObjectIndexwith per-key weights — no manual key extraction needed - Persistent mutable index:
FuzzyIndexsupportsadd()/remove()without rebuilding - Prefix and suffix anchors:
^termandterm$, in addition to exclusions (!term) and exact terms ('term)
Additional Capabilities
Section titled “Additional Capabilities”rapid-fuzzy includes distance/similarity functions that uFuzzy does not offer:
import { levenshtein, // edit distance normalizedLevenshtein, // 0-1 similarity jaroWinkler, // name matching sorensenDice, // text similarity damerauLevenshtein, // transposition-aware} from 'rapid-fuzzy';
// Batch APIs for bulk operationsimport { levenshteinBatch, jaroWinklerMany } from 'rapid-fuzzy';