rapid-fuzzy 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -70
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -8,18 +8,36 @@
|
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
[](https://nodejs.org/)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Blazing-fast fuzzy search for JavaScript — powered by Rust, works everywhere.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
<img src=".github/assets/demo.svg" alt="rapid-fuzzy demo — fuzzy search, query syntax, FuzzyIndex, and string distance" width="580" />
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
|
-
- **Fast**: Up to
|
|
17
|
+
- **Fast**: Up to 7,000x faster than fuse.js with FuzzyIndex (Rust + napi-rs)
|
|
18
18
|
- **Universal**: Works in Node.js (native), browsers (WASM), Deno, and Bun
|
|
19
19
|
- **Zero JS dependencies**: Pure Rust core with napi-rs bindings
|
|
20
20
|
- **Type-safe**: Full TypeScript support with auto-generated type definitions
|
|
21
21
|
- **Drop-in**: API compatible with popular fuzzy search libraries
|
|
22
22
|
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { search } from 'rapid-fuzzy';
|
|
27
|
+
|
|
28
|
+
const results = search('typscript', ['TypeScript', 'JavaScript', 'Python']);
|
|
29
|
+
// → [{ item: 'TypeScript', score: 0.85, index: 0 }, ...]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For repeated searches, use `FuzzyIndex` for up to 182x faster lookups:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { FuzzyIndex } from 'rapid-fuzzy';
|
|
36
|
+
|
|
37
|
+
const index = new FuzzyIndex(['TypeScript', 'JavaScript', 'Python', ...]);
|
|
38
|
+
index.search('typscript'); // sub-millisecond with incremental cache
|
|
39
|
+
```
|
|
40
|
+
|
|
23
41
|
## Installation
|
|
24
42
|
|
|
25
43
|
```bash
|
|
@@ -33,17 +51,7 @@ pnpm add rapid-fuzzy
|
|
|
33
51
|
- **Node.js** (>=20): Uses native bindings via napi-rs for best performance.
|
|
34
52
|
- **Browser / Deno / Bun**: Falls back to a WASM build automatically.
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
### String Distance
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import { levenshtein, jaroWinkler, sorensenDice } from 'rapid-fuzzy';
|
|
42
|
-
|
|
43
|
-
levenshtein('kitten', 'sitting'); // 3
|
|
44
|
-
jaroWinkler('MARTHA', 'MARHTA'); // 0.961
|
|
45
|
-
sorensenDice('night', 'nacht'); // 0.25
|
|
46
|
-
```
|
|
54
|
+
> **Note**: rapid-fuzzy is pre-1.0 — the API is stable but minor versions may include additions.
|
|
47
55
|
|
|
48
56
|
### Fuzzy Search
|
|
49
57
|
|
|
@@ -81,6 +89,32 @@ closest('xyz', items, 0.5);
|
|
|
81
89
|
// → null
|
|
82
90
|
```
|
|
83
91
|
|
|
92
|
+
### String Distance
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { levenshtein, jaroWinkler, sorensenDice } from 'rapid-fuzzy';
|
|
96
|
+
|
|
97
|
+
levenshtein('kitten', 'sitting'); // 3
|
|
98
|
+
jaroWinkler('MARTHA', 'MARHTA'); // 0.961
|
|
99
|
+
sorensenDice('night', 'nacht'); // 0.25
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Query Syntax
|
|
103
|
+
|
|
104
|
+
Queries support extended syntax powered by the [nucleo](https://github.com/helix-editor/nucleo) pattern parser:
|
|
105
|
+
|
|
106
|
+
| Pattern | Match type | Example |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `foo bar` | AND (order-independent) | `john smith` matches "Smith, John" |
|
|
109
|
+
| `!term` | Exclude | `apple !pie` excludes "apple pie" |
|
|
110
|
+
| `^term` | Starts with | `^app` matches "apple" but not "pineapple" |
|
|
111
|
+
| `term$` | Ends with | `pie$` matches "apple pie" |
|
|
112
|
+
| `'term` | Exact substring | `'pie` matches "pie" literally |
|
|
113
|
+
|
|
114
|
+
Diacritics are handled automatically — `cafe` matches `café`, `uber` matches `über`, and `naive` matches `naïve` with no configuration needed.
|
|
115
|
+
|
|
116
|
+
> **Note**: These patterns apply to all search functions: `search()`, `closest()`, `FuzzyIndex.search()`, `FuzzyObjectIndex.search()`, and `searchObjects()`. They do **not** apply to distance functions (`levenshtein`, `jaro`, etc.).
|
|
117
|
+
|
|
84
118
|
### Object Search
|
|
85
119
|
|
|
86
120
|
Search across object properties with weighted keys — a drop-in replacement for fuse.js's `keys` option:
|
|
@@ -119,7 +153,7 @@ For applications that search the same dataset repeatedly (autocomplete, file fin
|
|
|
119
153
|
```typescript
|
|
120
154
|
import { FuzzyIndex, FuzzyObjectIndex } from 'rapid-fuzzy';
|
|
121
155
|
|
|
122
|
-
// String search index — up to
|
|
156
|
+
// String search index — up to 182x faster than standalone search()
|
|
123
157
|
const index = new FuzzyIndex(['TypeScript', 'JavaScript', 'Python', ...]);
|
|
124
158
|
|
|
125
159
|
index.search('typscript', { maxResults: 5 });
|
|
@@ -215,6 +249,27 @@ levenshteinMany('kitten', ['sitting', 'kittens', 'kitchen']);
|
|
|
215
249
|
|
|
216
250
|
> **Tip**: Prefer batch/many variants over calling single-pair functions in a loop — they are significantly faster for multiple comparisons.
|
|
217
251
|
|
|
252
|
+
## Choosing an Algorithm
|
|
253
|
+
|
|
254
|
+
| Use case | Recommended | Why |
|
|
255
|
+
|---|---|---|
|
|
256
|
+
| Typo detection / spell check | `levenshtein`, `damerauLevenshtein` | Counts edits; Damerau adds transposition support |
|
|
257
|
+
| Name / address matching | `jaroWinkler`, `tokenSortRatio` | Prefix-weighted or order-independent matching |
|
|
258
|
+
| Document / text similarity | `sorensenDice` | Bigram-based; handles longer text well |
|
|
259
|
+
| Normalized comparison (0–1) | `normalizedLevenshtein` | Length-independent similarity score |
|
|
260
|
+
| Reordered words / messy data | `tokenSortRatio`, `tokenSetRatio` | Handles word order differences and extra tokens |
|
|
261
|
+
| Substring / abbreviation matching | `partialRatio` | Finds best partial match within longer strings |
|
|
262
|
+
| Best-effort similarity | `weightedRatio` | Picks the best score across all methods automatically |
|
|
263
|
+
| Interactive fuzzy search | `search`, `closest` | Nucleo algorithm (same as Helix editor) |
|
|
264
|
+
| Repeated search on same data | `FuzzyIndex`, `FuzzyObjectIndex` | Persistent Rust-side index with incremental cache, up to 182x faster |
|
|
265
|
+
|
|
266
|
+
**Return types:**
|
|
267
|
+
|
|
268
|
+
- `levenshtein`, `damerauLevenshtein` → integer (edit count)
|
|
269
|
+
- `jaro`, `jaroWinkler`, `sorensenDice`, `normalizedLevenshtein` → float between 0.0 (no match) and 1.0 (identical)
|
|
270
|
+
- `tokenSortRatio`, `tokenSetRatio`, `partialRatio`, `weightedRatio` → float between 0.0 and 1.0
|
|
271
|
+
- `search` → array of `{ item, score, index, positions }` sorted by relevance (score: 0.0–1.0)
|
|
272
|
+
|
|
218
273
|
## Benchmarks
|
|
219
274
|
|
|
220
275
|
Measured on Apple M-series with Node.js v22 using [Vitest bench](https://vitest.dev/guide/features.html#benchmarking). Each benchmark processes 6 realistic string pairs of varying length and similarity.
|
|
@@ -228,46 +283,50 @@ Measured on Apple M-series with Node.js v22 using [Vitest bench](https://vitest.
|
|
|
228
283
|
|
|
229
284
|
| Function | rapid-fuzzy | fastest-levenshtein | leven | string-similarity |
|
|
230
285
|
|---|---:|---:|---:|---:|
|
|
231
|
-
| Levenshtein |
|
|
232
|
-
| Normalized Levenshtein | **
|
|
233
|
-
| Sorensen-Dice | **
|
|
234
|
-
| Jaro-Winkler | **
|
|
235
|
-
| Damerau-Levenshtein | **
|
|
286
|
+
| Levenshtein | 562,063 ops/s | **794,298 ops/s** | 228,688 ops/s | — |
|
|
287
|
+
| Normalized Levenshtein | **546,107 ops/s** | — | — | — |
|
|
288
|
+
| Sorensen-Dice | **147,850 ops/s** | — | — | 84,308 ops/s |
|
|
289
|
+
| Jaro-Winkler | **293,403 ops/s** | — | — | — |
|
|
290
|
+
| Damerau-Levenshtein | **116,153 ops/s** | — | — | — |
|
|
236
291
|
|
|
237
292
|
</details>
|
|
238
293
|
|
|
239
|
-
> **Note**: For single-pair Levenshtein, fastest-levenshtein is ~1.4x faster due to its optimized pure-JS implementation that avoids FFI overhead. rapid-fuzzy is **2.
|
|
294
|
+
> **Note**: For single-pair Levenshtein, fastest-levenshtein is ~1.4x faster due to its optimized pure-JS implementation that avoids FFI overhead. rapid-fuzzy is **2.5x faster** than leven, and provides broader algorithm coverage plus batch / search scenarios.
|
|
240
295
|
|
|
241
296
|
### Search Performance
|
|
242
297
|
|
|
243
|
-
<img src=".github/assets/bench-search.svg" alt="Search performance chart — rapid-fuzzy vs fuse.js vs fuzzysort" width="680" />
|
|
298
|
+
<img src=".github/assets/bench-search.svg" alt="Search performance chart — rapid-fuzzy vs fuse.js vs fuzzysort vs uFuzzy" width="680" />
|
|
299
|
+
|
|
300
|
+
> Both `rapid-fuzzy` columns below show the same library: standalone `search()` vs `FuzzyIndex` (indexed mode for repeated searches).
|
|
244
301
|
|
|
245
302
|
<details>
|
|
246
303
|
<summary>Raw numbers</summary>
|
|
247
304
|
|
|
248
|
-
| Dataset size | rapid-fuzzy |
|
|
249
|
-
|
|
250
|
-
| Small (20 items) |
|
|
251
|
-
| Medium (1K items) | 6,
|
|
252
|
-
| Large (10K items) |
|
|
305
|
+
| Dataset size | rapid-fuzzy | rapid-fuzzy (indexed) | fuse.js | fuzzysort | uFuzzy |
|
|
306
|
+
|---|---:|---:|---:|---:|---:|
|
|
307
|
+
| Small (20 items) | 303,982 ops/s | 405,604 ops/s | 105,568 ops/s | **2,606,394 ops/s** | 923,069 ops/s |
|
|
308
|
+
| Medium (1K items) | 6,787 ops/s | **80,579 ops/s** | 367 ops/s | 64,372 ops/s | 28,953 ops/s |
|
|
309
|
+
| Large (10K items) | 751 ops/s | **136,528 ops/s** | 19 ops/s | 26,112 ops/s | 6,393 ops/s |
|
|
310
|
+
| XL (50K items) | — | **31,903 ops/s** | — | 5,916 ops/s | 1,292 ops/s |
|
|
253
311
|
|
|
254
312
|
</details>
|
|
255
313
|
|
|
256
314
|
### Closest Match (Levenshtein-based)
|
|
257
315
|
|
|
258
|
-
| Dataset size | rapid-fuzzy |
|
|
316
|
+
| Dataset size | rapid-fuzzy | rapid-fuzzy (indexed) | fastest-levenshtein |
|
|
259
317
|
|---|---:|---:|---:|
|
|
260
|
-
| Medium (1K items) | 8,
|
|
261
|
-
| Large (10K items) |
|
|
318
|
+
| Medium (1K items) | 8,611 ops/s | **989,095 ops/s** | 6,797 ops/s |
|
|
319
|
+
| Large (10K items) | 924 ops/s | **156,014 ops/s** | 658 ops/s |
|
|
262
320
|
|
|
263
|
-
>
|
|
321
|
+
> In indexed mode (`FuzzyIndex`), rapid-fuzzy is up to **237x faster** than fastest-levenshtein for closest-match lookups.
|
|
264
322
|
|
|
265
323
|
### Why these numbers matter
|
|
266
324
|
|
|
267
|
-
- **vs fuse.js**:
|
|
268
|
-
- **
|
|
269
|
-
- **vs
|
|
270
|
-
- **
|
|
325
|
+
- **vs fuse.js**: `FuzzyIndex` is **219x faster** on medium datasets and **6,869x faster** on large datasets. Even standalone `search()` is 18x / 40x faster.
|
|
326
|
+
- **Indexed mode**: `FuzzyIndex` keeps data on the Rust side with an incremental search cache, delivering sub-millisecond autocomplete. On large datasets this is **182x faster** than standalone `search()`.
|
|
327
|
+
- **vs fuzzysort**: `FuzzyIndex` now **outperforms fuzzysort** on medium-and-above datasets — 1.25x faster at 1K, 5.2x at 10K, and 5.4x at 50K.
|
|
328
|
+
- **vs uFuzzy**: `FuzzyIndex` is **2.8x faster** at medium and **21x faster** at large datasets.
|
|
329
|
+
- **vs fastest-levenshtein**: With `FuzzyIndex`, closest-match is **145x faster** at 1K and **237x faster** at 10K.
|
|
271
330
|
|
|
272
331
|
Run benchmarks yourself:
|
|
273
332
|
|
|
@@ -276,50 +335,34 @@ pnpm run bench # JavaScript benchmarks
|
|
|
276
335
|
cargo bench # Rust internal benchmarks
|
|
277
336
|
```
|
|
278
337
|
|
|
279
|
-
## Choosing an Algorithm
|
|
280
|
-
|
|
281
|
-
| Use case | Recommended | Why |
|
|
282
|
-
|---|---|---|
|
|
283
|
-
| Typo detection / spell check | `levenshtein`, `damerauLevenshtein` | Counts edits; Damerau adds transposition support |
|
|
284
|
-
| Name / address matching | `jaroWinkler`, `tokenSortRatio` | Prefix-weighted or order-independent matching |
|
|
285
|
-
| Document / text similarity | `sorensenDice` | Bigram-based; handles longer text well |
|
|
286
|
-
| Normalized comparison (0–1) | `normalizedLevenshtein` | Length-independent similarity score |
|
|
287
|
-
| Reordered words / messy data | `tokenSortRatio`, `tokenSetRatio` | Handles word order differences and extra tokens |
|
|
288
|
-
| Substring / abbreviation matching | `partialRatio` | Finds best partial match within longer strings |
|
|
289
|
-
| Best-effort similarity | `weightedRatio` | Picks the best score across all methods automatically |
|
|
290
|
-
| Interactive fuzzy search | `search`, `closest` | Nucleo algorithm (same as Helix editor) |
|
|
291
|
-
| Repeated search on same data | `FuzzyIndex`, `FuzzyObjectIndex` | Persistent Rust-side index, 3–5x faster than standalone |
|
|
292
|
-
|
|
293
|
-
**Return types:**
|
|
294
|
-
|
|
295
|
-
- `levenshtein`, `damerauLevenshtein` → integer (edit count)
|
|
296
|
-
- `jaro`, `jaroWinkler`, `sorensenDice`, `normalizedLevenshtein` → float between 0.0 (no match) and 1.0 (identical)
|
|
297
|
-
- `tokenSortRatio`, `tokenSetRatio`, `partialRatio`, `weightedRatio` → float between 0.0 and 1.0
|
|
298
|
-
- `search` → array of `{ item, score, index, positions }` sorted by relevance (score: 0.0–1.0)
|
|
299
|
-
|
|
300
338
|
## Why rapid-fuzzy?
|
|
301
339
|
|
|
302
|
-
| | rapid-fuzzy | fuse.js | fastest-levenshtein | fuzzysort |
|
|
303
|
-
|
|
304
|
-
| **Algorithms** | 9 (Levenshtein, Jaro, Dice, …) | Bitap | Levenshtein | Substring |
|
|
305
|
-
| **Runtime** | Rust native + WASM | Pure JS | Pure JS | Pure JS |
|
|
306
|
-
| **Object search** | ✅ weighted keys | ✅ | — | ✅ |
|
|
307
|
-
| **Persistent index** | ✅ FuzzyIndex / FuzzyObjectIndex | — | — | ✅ prepared targets |
|
|
308
|
-
| **
|
|
309
|
-
| **
|
|
310
|
-
| **
|
|
311
|
-
| **
|
|
312
|
-
| **
|
|
313
|
-
| **
|
|
314
|
-
| **
|
|
340
|
+
| | rapid-fuzzy | fuse.js | fastest-levenshtein | fuzzysort | uFuzzy |
|
|
341
|
+
|---|:---:|:---:|:---:|:---:|:---:|
|
|
342
|
+
| **Algorithms** | 9 (Levenshtein, Jaro, Dice, …) | Bitap | Levenshtein | Substring | Regex-based |
|
|
343
|
+
| **Runtime** | Rust native + WASM | Pure JS | Pure JS | Pure JS | Pure JS |
|
|
344
|
+
| **Object search** | ✅ weighted keys | ✅ | — | ✅ | — |
|
|
345
|
+
| **Persistent index** | ✅ FuzzyIndex / FuzzyObjectIndex | — | — | ✅ prepared targets | — |
|
|
346
|
+
| **Query syntax** | ✅ exclude, prefix, suffix, exact | ✅ extended search | — | — | partial (`-` only) |
|
|
347
|
+
| **Out-of-order matching** | ✅ automatic | — | — | — | ✅ with option |
|
|
348
|
+
| **Diacritics** | ✅ automatic | ✅ option | — | ✅ auto | ✅ `latinize()` |
|
|
349
|
+
| **Score threshold** | ✅ | ✅ | — | ✅ | — |
|
|
350
|
+
| **Match positions** | ✅ | ✅ | — | ✅ | ✅ |
|
|
351
|
+
| **Highlight utility** | ✅ | — | — | ✅ | ✅ |
|
|
352
|
+
| **Batch API** | ✅ | — | — | — | — |
|
|
353
|
+
| **Node.js native** | ✅ napi-rs | — | — | — | — |
|
|
354
|
+
| **Browser** | ✅ WASM | ✅ | ✅ | ✅ | ✅ |
|
|
355
|
+
| **TypeScript** | ✅ full | ✅ full | ✅ | ✅ | ✅ |
|
|
315
356
|
|
|
316
357
|
## Migration Guides
|
|
317
358
|
|
|
318
359
|
Switching from another library? These guides provide API mapping tables, code examples, and performance comparisons:
|
|
319
360
|
|
|
320
361
|
- [**From string-similarity**](docs/migration/from-string-similarity.md) — Same Dice coefficient algorithm, now maintained and faster
|
|
321
|
-
- [**From fuse.js**](docs/migration/from-fuse-js.md) —
|
|
362
|
+
- [**From fuse.js**](docs/migration/from-fuse-js.md) — Up to 7,000x faster fuzzy search with FuzzyIndex
|
|
322
363
|
- [**From leven / fastest-levenshtein**](docs/migration/from-leven.md) — Multi-algorithm upgrade with batch APIs
|
|
364
|
+
- [**From fuzzysort**](docs/migration/from-fuzzysort.md) — Richer matching with query syntax and 9 distance algorithms
|
|
365
|
+
- [**From uFuzzy**](docs/migration/from-ufuzzy.md) — Weighted object search, batch APIs, and persistent indexes
|
|
323
366
|
|
|
324
367
|
## License
|
|
325
368
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapid-fuzzy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Rust-powered fuzzy search and string distance for JavaScript/TypeScript. 10-50x faster than fuse.js/leven.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "derodero24",
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
"@commitlint/config-conventional": "^20.4.4",
|
|
108
108
|
"@emnapi/core": "^1.9.0",
|
|
109
109
|
"@emnapi/runtime": "^1.9.0",
|
|
110
|
+
"@leeoniya/ufuzzy": "^1.0.19",
|
|
110
111
|
"@napi-rs/cli": "^3.5.1",
|
|
111
112
|
"@napi-rs/wasm-runtime": "^1.1.1",
|
|
112
113
|
"@playwright/test": "^1.58.2",
|
|
@@ -131,14 +132,14 @@
|
|
|
131
132
|
]
|
|
132
133
|
},
|
|
133
134
|
"optionalDependencies": {
|
|
134
|
-
"rapid-fuzzy-darwin-x64": "0.
|
|
135
|
-
"rapid-fuzzy-darwin-arm64": "0.
|
|
136
|
-
"rapid-fuzzy-linux-x64-gnu": "0.
|
|
137
|
-
"rapid-fuzzy-linux-x64-musl": "0.
|
|
138
|
-
"rapid-fuzzy-linux-arm64-gnu": "0.
|
|
139
|
-
"rapid-fuzzy-linux-arm64-musl": "0.
|
|
140
|
-
"rapid-fuzzy-win32-x64-msvc": "0.
|
|
141
|
-
"rapid-fuzzy-win32-arm64-msvc": "0.
|
|
142
|
-
"rapid-fuzzy-wasm32-wasi": "0.
|
|
135
|
+
"rapid-fuzzy-darwin-x64": "0.6.0",
|
|
136
|
+
"rapid-fuzzy-darwin-arm64": "0.6.0",
|
|
137
|
+
"rapid-fuzzy-linux-x64-gnu": "0.6.0",
|
|
138
|
+
"rapid-fuzzy-linux-x64-musl": "0.6.0",
|
|
139
|
+
"rapid-fuzzy-linux-arm64-gnu": "0.6.0",
|
|
140
|
+
"rapid-fuzzy-linux-arm64-musl": "0.6.0",
|
|
141
|
+
"rapid-fuzzy-win32-x64-msvc": "0.6.0",
|
|
142
|
+
"rapid-fuzzy-win32-arm64-msvc": "0.6.0",
|
|
143
|
+
"rapid-fuzzy-wasm32-wasi": "0.6.0"
|
|
143
144
|
}
|
|
144
145
|
}
|