rapid-fuzzy 1.1.1 → 2.0.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 +312 -25
- package/browser.js +1 -1
- package/highlight.d.mts +1 -1
- package/index.d.mts +5 -5
- package/index.d.ts +128 -5
- package/index.js +74 -52
- package/index.mjs +21 -0
- package/objects.d.mts +3 -3
- package/objects.d.ts +13 -0
- package/objects.js +55 -5
- package/package.json +30 -16
- package/rapid-fuzzy-wasm-bindgen.d.ts +247 -0
- package/rapid-fuzzy-wasm-bindgen.js +9 -0
- package/rapid-fuzzy-wasm-bindgen_bg.js +1606 -0
- package/rapid-fuzzy-wasm-bindgen_bg.wasm.d.ts +76 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/rapid-fuzzy)
|
|
7
7
|
[](https://www.npmjs.com/package/rapid-fuzzy)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
|
-
[](https://nodejs.org/)
|
|
10
10
|
|
|
11
11
|
Blazing-fast fuzzy search for JavaScript — powered by Rust, works everywhere.
|
|
12
12
|
|
|
@@ -28,7 +28,7 @@ Try rapid-fuzzy in the browser — no installation required: **[Open Playground]
|
|
|
28
28
|
import { search } from 'rapid-fuzzy';
|
|
29
29
|
|
|
30
30
|
const results = search('typscript', ['TypeScript', 'JavaScript', 'Python']);
|
|
31
|
-
// → [{ item: 'TypeScript', score: 0.85, index: 0 }, ...]
|
|
31
|
+
// → [{ item: 'TypeScript', score: 0.85, index: 0, positions: [] }, ...]
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
For repeated searches, use `FuzzyIndex` for up to 297x faster lookups:
|
|
@@ -50,15 +50,9 @@ pnpm add rapid-fuzzy
|
|
|
50
50
|
|
|
51
51
|
### Runtime-specific notes
|
|
52
52
|
|
|
53
|
-
- **Node.js** (>=
|
|
54
|
-
- **
|
|
55
|
-
|
|
56
|
-
> **Browser WASM requirement**: The WASM build uses `SharedArrayBuffer` for threading, which requires the following HTTP headers on your page:
|
|
57
|
-
> ```
|
|
58
|
-
> Cross-Origin-Opener-Policy: same-origin
|
|
59
|
-
> Cross-Origin-Embedder-Policy: require-corp
|
|
60
|
-
> ```
|
|
61
|
-
> Without these headers, you will see `SharedArrayBuffer is not defined`. See [MDN: SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements) for details.
|
|
53
|
+
- **Node.js** (>=22): Uses native bindings via napi-rs for best performance.
|
|
54
|
+
- **Bun**: Uses native napi-rs bindings. WASM fallback also works — see [Bun section](#bun) below.
|
|
55
|
+
- **Browser / CDN / Cloudflare Workers / Deno**: Falls back to the wasm-bindgen WASM build (~195 KB raw). No `SharedArrayBuffer` or COOP/COEP headers required.
|
|
62
56
|
|
|
63
57
|
### Framework Integration (SSR)
|
|
64
58
|
|
|
@@ -84,7 +78,103 @@ export default {
|
|
|
84
78
|
};
|
|
85
79
|
```
|
|
86
80
|
|
|
87
|
-
On the client side, rapid-fuzzy automatically falls back to WASM — no additional configuration needed
|
|
81
|
+
On the client side, rapid-fuzzy automatically falls back to WASM — no additional configuration needed.
|
|
82
|
+
|
|
83
|
+
### Browser and Edge Runtime Usage
|
|
84
|
+
|
|
85
|
+
#### CDN (no bundler required)
|
|
86
|
+
|
|
87
|
+
Import directly from [esm.sh](https://esm.sh) in any `<script type="module">`:
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<script type="module">
|
|
91
|
+
import { search, FuzzyIndex } from 'https://esm.sh/rapid-fuzzy';
|
|
92
|
+
|
|
93
|
+
const results = search('typscript', ['TypeScript', 'JavaScript', 'Python']);
|
|
94
|
+
console.log(results[0].item); // 'TypeScript'
|
|
95
|
+
|
|
96
|
+
const index = new FuzzyIndex(['TypeScript', 'JavaScript', 'Python']);
|
|
97
|
+
console.log(index.search('typscript')[0].item); // 'TypeScript'
|
|
98
|
+
index.destroy();
|
|
99
|
+
</script>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
See [`examples/cdn-usage/`](examples/cdn-usage/) for a complete HTML example.
|
|
103
|
+
|
|
104
|
+
#### Cloudflare Workers
|
|
105
|
+
|
|
106
|
+
Install the package and import it in your Worker script. Wrangler bundles the WASM binary automatically:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm install rapid-fuzzy
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
// worker.js
|
|
114
|
+
import { search, FuzzyIndex } from 'rapid-fuzzy';
|
|
115
|
+
|
|
116
|
+
// Create the index once at module scope (shared across requests)
|
|
117
|
+
const index = new FuzzyIndex(['hello', 'world', 'foo', 'bar']);
|
|
118
|
+
|
|
119
|
+
export default {
|
|
120
|
+
fetch(request) {
|
|
121
|
+
const { searchParams } = new URL(request.url);
|
|
122
|
+
const query = searchParams.get('q') ?? '';
|
|
123
|
+
const results = index.search(query);
|
|
124
|
+
return Response.json(results);
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```toml
|
|
130
|
+
# wrangler.toml
|
|
131
|
+
name = "rapid-fuzzy-worker"
|
|
132
|
+
main = "worker.js"
|
|
133
|
+
compatibility_date = "2025-01-01"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
See [`examples/cloudflare-workers/`](examples/cloudflare-workers/) for a complete example.
|
|
137
|
+
|
|
138
|
+
#### Deno
|
|
139
|
+
|
|
140
|
+
Use rapid-fuzzy via the `npm:` specifier (Deno 1.28+):
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { search } from 'npm:rapid-fuzzy';
|
|
144
|
+
|
|
145
|
+
const results = search('typscript', ['TypeScript', 'JavaScript', 'Python']);
|
|
146
|
+
console.log(results[0].item); // 'TypeScript'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Or import from a CDN directly:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { search } from 'https://esm.sh/rapid-fuzzy';
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Bun
|
|
156
|
+
|
|
157
|
+
Bun uses the native napi-rs bindings when available (fastest). You can also use the wasm-bindgen WASM files directly if needed:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import * as wasm from 'rapid-fuzzy/rapid-fuzzy-wasm-bindgen_bg.js';
|
|
161
|
+
import { readFileSync } from 'node:fs';
|
|
162
|
+
|
|
163
|
+
const wasmPath = require.resolve('rapid-fuzzy/rapid-fuzzy-wasm-bindgen_bg.wasm');
|
|
164
|
+
const wasmModule = new WebAssembly.Module(readFileSync(wasmPath));
|
|
165
|
+
const wasmInstance = new WebAssembly.Instance(wasmModule, {
|
|
166
|
+
'./rapid-fuzzy-wasm-bindgen_bg.js': wasm,
|
|
167
|
+
});
|
|
168
|
+
wasm.__wbg_set_wasm(wasmInstance.exports);
|
|
169
|
+
|
|
170
|
+
// Now use the WASM API directly
|
|
171
|
+
const results = wasm.search('typscript', ['TypeScript', 'JavaScript', 'Python']);
|
|
172
|
+
const index = new wasm.FuzzyIndex(['apple', 'banana', 'cherry']);
|
|
173
|
+
index.search('aple');
|
|
174
|
+
index.destroy();
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
See [`e2e/wasm-bun.test.ts`](e2e/wasm-bun.test.ts) for a complete working example.
|
|
88
178
|
|
|
89
179
|
## API
|
|
90
180
|
|
|
@@ -127,12 +217,25 @@ closest('xyz', items, 0.5);
|
|
|
127
217
|
### String Distance
|
|
128
218
|
|
|
129
219
|
```typescript
|
|
130
|
-
import {
|
|
220
|
+
import {
|
|
221
|
+
levenshtein,
|
|
222
|
+
jaro,
|
|
223
|
+
jaroWinkler,
|
|
224
|
+
sorensenDice,
|
|
225
|
+
hamming,
|
|
226
|
+
normalizedHamming,
|
|
227
|
+
indel,
|
|
228
|
+
normalizedIndel,
|
|
229
|
+
} from 'rapid-fuzzy';
|
|
131
230
|
|
|
132
231
|
levenshtein('kitten', 'sitting'); // 3
|
|
133
|
-
|
|
232
|
+
jaro('martha', 'marhta'); // 0.944 (similarity between 0–1)
|
|
233
|
+
jaroWinkler('MARTHA', 'MARHTA'); // 0.961 (jaro + prefix bonus)
|
|
134
234
|
sorensenDice('night', 'nacht'); // 0.25
|
|
135
235
|
hamming('karolin', 'kathrin'); // 3 (null if lengths differ)
|
|
236
|
+
normalizedHamming('karolin', 'kathrin'); // 0.571 (similarity 0–1, null if lengths differ)
|
|
237
|
+
indel('abc', 'ac'); // 1 (insertions + deletions only, no substitutions)
|
|
238
|
+
normalizedIndel('kitten', 'sitting'); // 0.615 (similarity 0–1)
|
|
136
239
|
```
|
|
137
240
|
|
|
138
241
|
### Query Syntax
|
|
@@ -184,7 +287,9 @@ searchObjects('new york', items, { keys: ['address.city'] });
|
|
|
184
287
|
|
|
185
288
|
### Persistent Index
|
|
186
289
|
|
|
187
|
-
For applications that search the same dataset repeatedly (autocomplete, file finders, etc.), use `FuzzyIndex` or `FuzzyObjectIndex` to keep data on the Rust side and eliminate per-search FFI overhead
|
|
290
|
+
For applications that search the same dataset repeatedly (autocomplete, file finders, etc.), use `FuzzyIndex` or `FuzzyObjectIndex` to keep data on the Rust side and eliminate per-search FFI overhead.
|
|
291
|
+
|
|
292
|
+
**When to use which:** The standalone `search()` function requires zero setup and is ideal for one-off queries or small datasets. `FuzzyIndex` has an initial build cost but delivers sub-millisecond repeated queries, making it the better choice when querying the same dataset multiple times (autocomplete, live search, file finders).
|
|
188
293
|
|
|
189
294
|
```typescript
|
|
190
295
|
import { FuzzyIndex, FuzzyObjectIndex } from 'rapid-fuzzy';
|
|
@@ -195,6 +300,9 @@ const index = new FuzzyIndex(['TypeScript', 'JavaScript', 'Python', ...]);
|
|
|
195
300
|
index.search('typscript', { maxResults: 5 });
|
|
196
301
|
index.closest('tsc');
|
|
197
302
|
|
|
303
|
+
// Tip: FuzzyIndex caches results internally — extending a previous query
|
|
304
|
+
// (e.g. typing "app" → "apple") reuses cached candidates for faster lookups.
|
|
305
|
+
|
|
198
306
|
// Index-only results (no string cloning — less GC pressure)
|
|
199
307
|
const hits = index.searchIndices('typscript', { maxResults: 5 });
|
|
200
308
|
// → [{ index: 0, score: 0.85, positions: [] }, ...]
|
|
@@ -233,15 +341,19 @@ This makes FuzzyIndex ideal for search-as-you-type UIs where each keystroke exte
|
|
|
233
341
|
|
|
234
342
|
#### Index Serialization
|
|
235
343
|
|
|
236
|
-
Save a FuzzyIndex to avoid rebuilding on startup:
|
|
344
|
+
Save and restore a `FuzzyIndex` to avoid rebuilding on startup:
|
|
237
345
|
|
|
238
346
|
```typescript
|
|
239
|
-
|
|
240
|
-
fs.writeFileSync('search-index.bin', buffer);
|
|
347
|
+
import { FuzzyIndex } from 'rapid-fuzzy';
|
|
241
348
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
349
|
+
const index = new FuzzyIndex(['apple', 'banana', 'cherry']);
|
|
350
|
+
|
|
351
|
+
// Serialize to Buffer
|
|
352
|
+
const data = index.serialize();
|
|
353
|
+
|
|
354
|
+
// Restore from serialized data
|
|
355
|
+
const restored = FuzzyIndex.deserialize(data);
|
|
356
|
+
restored.search('aple'); // works immediately
|
|
245
357
|
```
|
|
246
358
|
|
|
247
359
|
> **Note:** The serialization format is version-specific. Regenerate the index after updating rapid-fuzzy.
|
|
@@ -301,7 +413,10 @@ All token-based functions include `Batch` and `Many` variants (e.g., `tokenSortR
|
|
|
301
413
|
<details>
|
|
302
414
|
<summary><strong>Batch Operations</strong></summary>
|
|
303
415
|
|
|
304
|
-
All distance functions have `Batch` and `Many` variants that amortize FFI overhead
|
|
416
|
+
All distance functions have `Batch` and `Many` variants that amortize FFI overhead. `*Batch` functions compute metrics for multiple pairs at once, while `*Many` functions compare a single reference string against multiple candidates.
|
|
417
|
+
|
|
418
|
+
- **`*Batch`** — compute distances for an array of string pairs (many-to-many)
|
|
419
|
+
- **`*Many`** — compare one reference string against many candidates (one-to-many)
|
|
305
420
|
|
|
306
421
|
```typescript
|
|
307
422
|
import { levenshteinBatch, levenshteinMany } from 'rapid-fuzzy';
|
|
@@ -327,13 +442,142 @@ jaroWinklerMany('MARTHA', candidates, 0.8); // minSimilarity → returns 0
|
|
|
327
442
|
|
|
328
443
|
</details>
|
|
329
444
|
|
|
445
|
+
<details>
|
|
446
|
+
<summary><strong>TypedArray Variants</strong></summary>
|
|
447
|
+
|
|
448
|
+
All `*Many` functions have TypedArray counterparts that return `Uint32Array` or `Float64Array` instead of `Array<number>`. These avoid boxing overhead and GC pressure when processing large candidate sets.
|
|
449
|
+
|
|
450
|
+
- **`*ManyU32`** — returns `Uint32Array` (for integer distances: `levenshteinManyU32`, `damerauLevenshteinManyU32`, `indelManyU32`)
|
|
451
|
+
- **`*ManyF64`** — returns `Float64Array` (for similarity scores: `jaroManyF64`, `jaroWinklerManyF64`, `normalizedLevenshteinManyF64`, `normalizedIndelManyF64`, `sorensenDiceManyF64`, `tokenSortRatioManyF64`, `tokenSetRatioManyF64`, `partialRatioManyF64`, `weightedRatioManyF64`)
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
import { levenshteinManyU32, jaroWinklerManyF64 } from 'rapid-fuzzy';
|
|
455
|
+
|
|
456
|
+
const candidates = ['sitting', 'kittens', 'kitchen'];
|
|
457
|
+
|
|
458
|
+
// Returns Uint32Array instead of Array<number>
|
|
459
|
+
levenshteinManyU32('kitten', candidates); // Uint32Array [3, 1, 2]
|
|
460
|
+
|
|
461
|
+
// Returns Float64Array instead of Array<number>
|
|
462
|
+
jaroWinklerManyF64('kitten', candidates); // Float64Array [0.746, 0.976, 0.933]
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
> **When to use**: Prefer TypedArray variants when comparing against thousands of candidates. The returned typed arrays can also be passed directly to WebGL, WASM, or worker threads without copying.
|
|
466
|
+
|
|
467
|
+
</details>
|
|
468
|
+
|
|
469
|
+
## Framework Integration
|
|
470
|
+
|
|
471
|
+
`FuzzyIndex` and `FuzzyObjectIndex` are designed for repeated search on the same data — build the index once, search many times. The examples below show the recommended pattern for each major framework.
|
|
472
|
+
|
|
473
|
+
### React
|
|
474
|
+
|
|
475
|
+
```tsx
|
|
476
|
+
import { useEffect, useRef, useState } from 'react';
|
|
477
|
+
import { FuzzyObjectIndex } from 'rapid-fuzzy/objects';
|
|
478
|
+
|
|
479
|
+
const users = [
|
|
480
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
481
|
+
{ name: 'Bob', email: 'bob@example.com' },
|
|
482
|
+
];
|
|
483
|
+
|
|
484
|
+
function UserSearch() {
|
|
485
|
+
const indexRef = useRef<FuzzyObjectIndex<typeof users[number]> | null>(null);
|
|
486
|
+
const [query, setQuery] = useState('');
|
|
487
|
+
const [results, setResults] = useState(users);
|
|
488
|
+
|
|
489
|
+
useEffect(() => {
|
|
490
|
+
indexRef.current = new FuzzyObjectIndex(users, {
|
|
491
|
+
keys: [{ name: 'name', weight: 2.0 }, 'email'],
|
|
492
|
+
});
|
|
493
|
+
return () => indexRef.current?.destroy();
|
|
494
|
+
}, []); // rebuild only when data changes — pass `users` as dependency if dynamic
|
|
495
|
+
|
|
496
|
+
useEffect(() => {
|
|
497
|
+
if (!indexRef.current) return;
|
|
498
|
+
if (!query) { setResults(users); return; }
|
|
499
|
+
setResults(indexRef.current.search(query).map((r) => r.item));
|
|
500
|
+
}, [query]);
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
<>
|
|
504
|
+
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search…" />
|
|
505
|
+
<ul>{results.map((u) => <li key={u.email}>{u.name}</li>)}</ul>
|
|
506
|
+
</>
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Vue
|
|
512
|
+
|
|
513
|
+
```vue
|
|
514
|
+
<script setup lang="ts">
|
|
515
|
+
import { ref, watch, onUnmounted } from 'vue';
|
|
516
|
+
import { FuzzyObjectIndex } from 'rapid-fuzzy/objects';
|
|
517
|
+
|
|
518
|
+
const users = [
|
|
519
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
520
|
+
{ name: 'Bob', email: 'bob@example.com' },
|
|
521
|
+
];
|
|
522
|
+
|
|
523
|
+
const query = ref('');
|
|
524
|
+
const results = ref(users);
|
|
525
|
+
|
|
526
|
+
const index = new FuzzyObjectIndex(users, {
|
|
527
|
+
keys: [{ name: 'name', weight: 2.0 }, 'email'],
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
watch(query, (q) => {
|
|
531
|
+
results.value = q ? index.search(q).map((r) => r.item) : users;
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
onUnmounted(() => index.destroy());
|
|
535
|
+
</script>
|
|
536
|
+
|
|
537
|
+
<template>
|
|
538
|
+
<input v-model="query" placeholder="Search…" />
|
|
539
|
+
<ul><li v-for="u in results" :key="u.email">{{ u.name }}</li></ul>
|
|
540
|
+
</template>
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Svelte
|
|
544
|
+
|
|
545
|
+
```svelte
|
|
546
|
+
<script lang="ts">
|
|
547
|
+
import { onDestroy } from 'svelte';
|
|
548
|
+
import { FuzzyObjectIndex } from 'rapid-fuzzy/objects';
|
|
549
|
+
|
|
550
|
+
const users = [
|
|
551
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
552
|
+
{ name: 'Bob', email: 'bob@example.com' },
|
|
553
|
+
];
|
|
554
|
+
|
|
555
|
+
let query = '';
|
|
556
|
+
|
|
557
|
+
const index = new FuzzyObjectIndex(users, {
|
|
558
|
+
keys: [{ name: 'name', weight: 2.0 }, 'email'],
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
$: results = query ? index.search(query).map((r) => r.item) : users;
|
|
562
|
+
|
|
563
|
+
onDestroy(() => index.destroy());
|
|
564
|
+
</script>
|
|
565
|
+
|
|
566
|
+
<input bind:value={query} placeholder="Search…" />
|
|
567
|
+
<ul>{#each results as u}<li>{u.name}</li>{/each}</ul>
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
> **Note**: Always call `index.destroy()` in your cleanup handler (`useEffect` return, `onUnmounted`, `onDestroy`) to free Rust-side memory.
|
|
571
|
+
|
|
330
572
|
## Choosing an Algorithm
|
|
331
573
|
|
|
332
574
|
| Use case | Recommended | Why |
|
|
333
575
|
|---|---|---|
|
|
334
576
|
| Typo detection / spell check | `levenshtein`, `damerauLevenshtein` | Counts edits; Damerau adds transposition support |
|
|
335
|
-
|
|
|
577
|
+
| Insertion/deletion only edits | `indel`, `normalizedIndel` | No substitutions — useful for diff-like or DNA alignment scenarios |
|
|
578
|
+
| Fixed-length comparison | `hamming`, `normalizedHamming` | Counts differing positions; only for equal-length strings |
|
|
336
579
|
| Name / address matching | `jaroWinkler`, `tokenSortRatio` | Prefix-weighted or order-independent matching |
|
|
580
|
+
| Character-level similarity | `jaro` | Good baseline similarity without prefix weighting |
|
|
337
581
|
| Document / text similarity | `sorensenDice` | Bigram-based; handles longer text well |
|
|
338
582
|
| Normalized comparison (0–1) | `normalizedLevenshtein` | Length-independent similarity score |
|
|
339
583
|
| Reordered words / messy data | `tokenSortRatio`, `tokenSetRatio` | Handles word order differences and extra tokens |
|
|
@@ -344,11 +588,27 @@ jaroWinklerMany('MARTHA', candidates, 0.8); // minSimilarity → returns 0
|
|
|
344
588
|
|
|
345
589
|
**Return types:**
|
|
346
590
|
|
|
347
|
-
- `levenshtein`, `damerauLevenshtein`, `hamming` → integer (edit/difference count; `hamming` returns `null` if lengths differ)
|
|
348
|
-
- `jaro`, `jaroWinkler`, `sorensenDice`, `normalizedLevenshtein` → float between 0.0 (no match) and 1.0 (identical)
|
|
591
|
+
- `levenshtein`, `damerauLevenshtein`, `hamming`, `indel` → integer (edit/difference count; `hamming` returns `null` if lengths differ)
|
|
592
|
+
- `jaro`, `jaroWinkler`, `sorensenDice`, `normalizedLevenshtein`, `normalizedIndel` → float between 0.0 (no match) and 1.0 (identical)
|
|
593
|
+
- `normalizedHamming` → float between 0.0 and 1.0 (`null` if lengths differ)
|
|
349
594
|
- `tokenSortRatio`, `tokenSetRatio`, `partialRatio`, `weightedRatio` → float between 0.0 and 1.0
|
|
350
595
|
- `search` → array of `{ item, score, index, positions }` sorted by relevance (score: 0.0–1.0)
|
|
351
596
|
|
|
597
|
+
### Memory Usage
|
|
598
|
+
|
|
599
|
+
`FuzzyIndex` and `FuzzyObjectIndex` store items and precomputed data (UTF-32 representations, character masks, bigram index) on the Rust side. Always call `.destroy()` when the index is no longer needed to free this memory immediately rather than waiting for garbage collection.
|
|
600
|
+
|
|
601
|
+
For read-heavy workloads, prefer `searchIndices()` over `search()` — it returns only indices and scores without cloning item strings back to JavaScript, reducing GC pressure.
|
|
602
|
+
|
|
603
|
+
Serialized indexes use a compact binary format suitable for disk or IndexedDB storage. The serialized size is larger than raw text due to precomputed data, but deserialization is faster than rebuilding.
|
|
604
|
+
|
|
605
|
+
### Error Handling
|
|
606
|
+
|
|
607
|
+
- `hamming()` / `normalizedHamming()` return `null` when the input strings have different lengths.
|
|
608
|
+
- `closest()` returns `null` if no match meets the `minScore` threshold (or if the item list is empty).
|
|
609
|
+
- Calling methods on a `FuzzyIndex` or `FuzzyObjectIndex` after `.destroy()` throws an error.
|
|
610
|
+
- `searchObjects()` and `FuzzyObjectIndex` throw a `TypeError` if `options.keys` is missing or empty.
|
|
611
|
+
|
|
352
612
|
## Benchmarks
|
|
353
613
|
|
|
354
614
|
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.
|
|
@@ -424,6 +684,30 @@ Measured on Apple M-series with Node.js v22 using [Vitest bench](https://vitest.
|
|
|
424
684
|
| **Browser** | ✅ WASM (~230 KB gzipped) | ✅ | ✅ | ✅ | ✅ |
|
|
425
685
|
| **TypeScript** | ✅ full | ✅ full | ✅ | ✅ | ✅ |
|
|
426
686
|
|
|
687
|
+
## Troubleshooting
|
|
688
|
+
|
|
689
|
+
### "Cannot find native binding" error
|
|
690
|
+
|
|
691
|
+
The native binary for your platform may not have been installed correctly. Run `npm rebuild rapid-fuzzy` or delete `node_modules` and reinstall. Ensure your platform and architecture are [supported by napi-rs](https://napi.rs/docs/cross-build/summary).
|
|
692
|
+
|
|
693
|
+
### WASM fails to load in the browser
|
|
694
|
+
|
|
695
|
+
Verify that your bundler is configured to handle `.wasm` files. If loading from a CDN, check that the server serves `.wasm` files with the correct `application/wasm` MIME type and that CORS headers allow the request.
|
|
696
|
+
|
|
697
|
+
### SSR or Edge runtime errors
|
|
698
|
+
|
|
699
|
+
Server-side rendering frameworks need to externalize rapid-fuzzy so the native module is not bundled. See [Framework Integration (SSR)](#framework-integration-ssr) above. For edge runtimes (Cloudflare Workers, Deno Deploy), rapid-fuzzy automatically uses the WASM build — see [Browser and Edge Runtime Usage](#browser-and-edge-runtime-usage).
|
|
700
|
+
|
|
701
|
+
### Bun WASM initialization
|
|
702
|
+
|
|
703
|
+
Bun does not yet support the TC39 WebAssembly ESM integration that wasm-bindgen relies on. If you need the WASM build in Bun, initialize it manually — see the [Bun section](#bun) for a complete code example.
|
|
704
|
+
|
|
705
|
+
## Limitations
|
|
706
|
+
|
|
707
|
+
- **WASM memory limit**: The WASM build is subject to the WebAssembly linear memory maximum of 4 GB (65,536 pages of 64 KB). This is sufficient for most use cases but may be a constraint for extremely large datasets.
|
|
708
|
+
- **Synchronous search**: All search and distance functions are synchronous. This is by design — operations are fast enough (sub-millisecond for indexed search) that async overhead would be counterproductive. For large index construction, use `FuzzyIndex.fromAsync()`.
|
|
709
|
+
- **No phonetic or language-specific matching**: rapid-fuzzy focuses on edit-distance and character-level fuzzy matching. It does not perform phonetic matching (e.g., Soundex, Metaphone) or language-specific stemming/lemmatization.
|
|
710
|
+
|
|
427
711
|
## Migration Guides
|
|
428
712
|
|
|
429
713
|
Switching from another library? These guides provide API mapping tables, code examples, and performance comparisons:
|
|
@@ -433,6 +717,9 @@ Switching from another library? These guides provide API mapping tables, code ex
|
|
|
433
717
|
- [**From leven / fastest-levenshtein**](docs/migration/from-leven.md) — Multi-algorithm upgrade with batch APIs
|
|
434
718
|
- [**From fuzzysort**](docs/migration/from-fuzzysort.md) — Richer matching with query syntax and 10 distance algorithms
|
|
435
719
|
- [**From uFuzzy**](docs/migration/from-ufuzzy.md) — Weighted object search, batch APIs, and persistent indexes
|
|
720
|
+
- [**From fuzzball**](docs/migration/from-fuzzball.md) — Same ratio functions (token sort, token set, weighted), now 0.0–1.0 scale
|
|
721
|
+
- [**From FlexSearch**](docs/migration/from-flexsearch.md) — Typo-tolerant fuzzy search to replace exact-token full-text search
|
|
722
|
+
- [**From MiniSearch**](docs/migration/from-minisearch.md) — Faster fuzzy search with richer distance algorithms
|
|
436
723
|
|
|
437
724
|
## License
|
|
438
725
|
|
package/browser.js
CHANGED
package/highlight.d.mts
CHANGED
package/index.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export * from './index.js';
|
|
2
|
-
export { highlight, highlightRanges } from './highlight.js';
|
|
3
1
|
export type { HighlightRange } from './highlight.js';
|
|
4
|
-
export {
|
|
2
|
+
export { highlight, highlightRanges } from './highlight.js';
|
|
3
|
+
export * from './index.js';
|
|
5
4
|
export type {
|
|
6
5
|
KeyConfig,
|
|
7
|
-
ObjectSearchOptions,
|
|
8
|
-
ObjectSearchResult,
|
|
9
6
|
ObjectIndexOptions,
|
|
10
7
|
ObjectIndexSearchOptions,
|
|
8
|
+
ObjectSearchOptions,
|
|
9
|
+
ObjectSearchResult,
|
|
11
10
|
} from './objects.js';
|
|
11
|
+
export { FuzzyObjectIndex, searchObjects } from './objects.js';
|
package/index.d.ts
CHANGED
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
export declare class FuzzyIndex {
|
|
14
14
|
/** Create a new FuzzyIndex from an array of strings. */
|
|
15
15
|
constructor(items: Array<string>)
|
|
16
|
+
/**
|
|
17
|
+
* Construct a FuzzyIndex on the libuv thread pool, returning a Promise.
|
|
18
|
+
*
|
|
19
|
+
* For large datasets this keeps the JavaScript event loop unblocked during
|
|
20
|
+
* index construction. The synchronous constructor is fine for small datasets.
|
|
21
|
+
*/
|
|
22
|
+
static fromAsync(items: Array<string>): Promise<FuzzyIndex>
|
|
16
23
|
/** Return the number of items in the index. */
|
|
17
24
|
get size(): number
|
|
18
25
|
/**
|
|
@@ -94,6 +101,15 @@ export declare class KeyedFuzzyIndex {
|
|
|
94
101
|
* Returns results sorted by combined weighted score (best match first).
|
|
95
102
|
*/
|
|
96
103
|
search(query: string, options?: SearchOptions | undefined | null): Array<KeySearchResult>
|
|
104
|
+
/**
|
|
105
|
+
* Find the index of the closest matching item.
|
|
106
|
+
*
|
|
107
|
+
* Returns the index of the best match, or null if no match is found.
|
|
108
|
+
* If `min_score` is provided, returns null when the best match scores below the threshold.
|
|
109
|
+
*
|
|
110
|
+
* Use the returned index to look up the item in your own data array.
|
|
111
|
+
*/
|
|
112
|
+
closest(query: string, minScore?: number | undefined | null): number | null
|
|
97
113
|
/**
|
|
98
114
|
* Add a single item to the index.
|
|
99
115
|
*
|
|
@@ -116,6 +132,16 @@ export declare class KeyedFuzzyIndex {
|
|
|
116
132
|
remove(index: number): boolean
|
|
117
133
|
/** Free the internal data. After calling this, the index is empty. */
|
|
118
134
|
destroy(): void
|
|
135
|
+
/**
|
|
136
|
+
* Serialize the index to a compact binary format.
|
|
137
|
+
*
|
|
138
|
+
* The returned Buffer can be written to disk, stored in IndexedDB,
|
|
139
|
+
* or transferred over the network. Use `KeyedFuzzyIndex.deserialize()` to
|
|
140
|
+
* reconstruct the index.
|
|
141
|
+
*/
|
|
142
|
+
serialize(): Buffer
|
|
143
|
+
/** Reconstruct a KeyedFuzzyIndex from a previously serialized Buffer. */
|
|
144
|
+
static deserialize(data: Buffer): KeyedFuzzyIndex
|
|
119
145
|
}
|
|
120
146
|
|
|
121
147
|
/**
|
|
@@ -178,6 +204,35 @@ export declare function hammingBatch(pairs: Array<Array<string>>): Array<number
|
|
|
178
204
|
*/
|
|
179
205
|
export declare function hammingMany(reference: string, candidates: Array<string>, maxDistance?: number | undefined | null): Array<number | undefined | null>
|
|
180
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Compute the Indel distance between two strings.
|
|
209
|
+
*
|
|
210
|
+
* The Indel distance counts the minimum number of insertions and deletions
|
|
211
|
+
* (no substitutions) required to transform one string into the other.
|
|
212
|
+
* It equals `len(a) + len(b) - 2 * LCS_length(a, b)`.
|
|
213
|
+
*
|
|
214
|
+
* Useful when substitutions are semantically two operations (one deletion +
|
|
215
|
+
* one insertion), such as in DNA sequence alignment.
|
|
216
|
+
*/
|
|
217
|
+
export declare function indel(a: string, b: string): number
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Compute the Indel distance for multiple pairs of strings in a single call.
|
|
221
|
+
*
|
|
222
|
+
* Returns an array of distances in the same order as the input pairs.
|
|
223
|
+
* Each pair must be an array of exactly two strings `[a, b]`.
|
|
224
|
+
*/
|
|
225
|
+
export declare function indelBatch(pairs: Array<Array<string>>): Array<number>
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Compute the Indel distance from one reference string to many candidates.
|
|
229
|
+
*
|
|
230
|
+
* Returns an array of distances, one per candidate, in the same order as the input.
|
|
231
|
+
* If `max_distance` is provided, candidates with distance exceeding the threshold
|
|
232
|
+
* will return `max_distance + 1` (enabling early termination for better performance).
|
|
233
|
+
*/
|
|
234
|
+
export declare function indelMany(reference: string, candidates: Array<string>, maxDistance?: number | undefined | null): Array<number>
|
|
235
|
+
|
|
181
236
|
/**
|
|
182
237
|
* A lightweight search result containing only index and score (no item string).
|
|
183
238
|
*
|
|
@@ -304,6 +359,55 @@ export declare const enum MatchType {
|
|
|
304
359
|
Fuzzy = 'Fuzzy'
|
|
305
360
|
}
|
|
306
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Compute the normalized Hamming similarity between two strings.
|
|
364
|
+
*
|
|
365
|
+
* Returns `null` if the strings have different lengths.
|
|
366
|
+
* Returns a value between 0.0 (no matching characters) and 1.0 (identical).
|
|
367
|
+
*/
|
|
368
|
+
export declare function normalizedHamming(a: string, b: string): number | null
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Compute the normalized Hamming similarity for multiple pairs of strings in a single call.
|
|
372
|
+
*
|
|
373
|
+
* Returns an array of scores in the same order as the input pairs.
|
|
374
|
+
* Returns `null` for pairs with different lengths.
|
|
375
|
+
*/
|
|
376
|
+
export declare function normalizedHammingBatch(pairs: Array<Array<string>>): Array<number | undefined | null>
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Compute the normalized Hamming similarity from one reference string to many candidates.
|
|
380
|
+
*
|
|
381
|
+
* Returns an array of scores, one per candidate, in the same order as the input.
|
|
382
|
+
* Returns `null` for candidates with a different length than the reference.
|
|
383
|
+
* If `min_similarity` is provided, candidates with similarity below the threshold
|
|
384
|
+
* will also return `null` (enabling early termination for better performance).
|
|
385
|
+
*/
|
|
386
|
+
export declare function normalizedHammingMany(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Array<number | undefined | null>
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Compute the normalized Indel similarity between two strings.
|
|
390
|
+
*
|
|
391
|
+
* Returns a value between 0.0 (completely different) and 1.0 (identical).
|
|
392
|
+
*/
|
|
393
|
+
export declare function normalizedIndel(a: string, b: string): number
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Compute the normalized Indel similarity for multiple pairs of strings in a single call.
|
|
397
|
+
*
|
|
398
|
+
* Returns an array of similarity scores in the same order as the input pairs.
|
|
399
|
+
*/
|
|
400
|
+
export declare function normalizedIndelBatch(pairs: Array<Array<string>>): Array<number>
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Compute the normalized Indel similarity from one reference string to many candidates.
|
|
404
|
+
*
|
|
405
|
+
* Returns an array of similarity scores, one per candidate, in the same order as the input.
|
|
406
|
+
* If `min_similarity` is provided, candidates with similarity below the threshold
|
|
407
|
+
* will return `0.0` (enabling early termination for better performance).
|
|
408
|
+
*/
|
|
409
|
+
export declare function normalizedIndelMany(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Array<number>
|
|
410
|
+
|
|
307
411
|
/**
|
|
308
412
|
* Compute the normalized Levenshtein similarity between two strings.
|
|
309
413
|
*
|
|
@@ -348,8 +452,9 @@ export declare function partialRatioBatch(pairs: Array<Array<string>>): Array<nu
|
|
|
348
452
|
* Compute the partial ratio from one reference string to many candidates.
|
|
349
453
|
*
|
|
350
454
|
* Returns an array of similarity scores, one per candidate, in the same order as the input.
|
|
455
|
+
* If `min_similarity` is provided, candidates scoring below the threshold return `0.0`.
|
|
351
456
|
*/
|
|
352
|
-
export declare function partialRatioMany(reference: string, candidates: Array<string
|
|
457
|
+
export declare function partialRatioMany(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Array<number>
|
|
353
458
|
|
|
354
459
|
/**
|
|
355
460
|
* Perform fuzzy search over a list of strings.
|
|
@@ -436,8 +541,10 @@ export declare function sorensenDiceBatch(pairs: Array<Array<string>>): Array<nu
|
|
|
436
541
|
* Compute the Sorensen-Dice coefficient from one reference string to many candidates.
|
|
437
542
|
*
|
|
438
543
|
* Returns an array of similarity scores, one per candidate, in the same order as the input.
|
|
544
|
+
* If `min_similarity` is provided, candidates scoring below the threshold return `0.0`.
|
|
545
|
+
* Reference bigrams are pre-computed once and reused for all candidates.
|
|
439
546
|
*/
|
|
440
|
-
export declare function sorensenDiceMany(reference: string, candidates: Array<string
|
|
547
|
+
export declare function sorensenDiceMany(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Array<number>
|
|
441
548
|
|
|
442
549
|
/**
|
|
443
550
|
* Compute the token set ratio between two strings.
|
|
@@ -460,8 +567,9 @@ export declare function tokenSetRatioBatch(pairs: Array<Array<string>>): Array<n
|
|
|
460
567
|
* Compute the token set ratio from one reference string to many candidates.
|
|
461
568
|
*
|
|
462
569
|
* Returns an array of similarity scores, one per candidate, in the same order as the input.
|
|
570
|
+
* If `min_similarity` is provided, candidates scoring below the threshold return `0.0`.
|
|
463
571
|
*/
|
|
464
|
-
export declare function tokenSetRatioMany(reference: string, candidates: Array<string
|
|
572
|
+
export declare function tokenSetRatioMany(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Array<number>
|
|
465
573
|
|
|
466
574
|
/**
|
|
467
575
|
* Compute the token sort ratio between two strings.
|
|
@@ -484,8 +592,9 @@ export declare function tokenSortRatioBatch(pairs: Array<Array<string>>): Array<
|
|
|
484
592
|
* Compute the token sort ratio from one reference string to many candidates.
|
|
485
593
|
*
|
|
486
594
|
* Returns an array of similarity scores, one per candidate, in the same order as the input.
|
|
595
|
+
* If `min_similarity` is provided, candidates scoring below the threshold return `0.0`.
|
|
487
596
|
*/
|
|
488
|
-
export declare function tokenSortRatioMany(reference: string, candidates: Array<string
|
|
597
|
+
export declare function tokenSortRatioMany(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Array<number>
|
|
489
598
|
|
|
490
599
|
/**
|
|
491
600
|
* Compute the weighted ratio between two strings.
|
|
@@ -508,8 +617,22 @@ export declare function weightedRatioBatch(pairs: Array<Array<string>>): Array<n
|
|
|
508
617
|
* Compute the weighted ratio from one reference string to many candidates.
|
|
509
618
|
*
|
|
510
619
|
* Returns an array of similarity scores, one per candidate, in the same order as the input.
|
|
620
|
+
* If `min_similarity` is provided, candidates scoring below the threshold return `0.0`.
|
|
511
621
|
*/
|
|
512
|
-
export declare function weightedRatioMany(reference: string, candidates: Array<string
|
|
622
|
+
export declare function weightedRatioMany(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Array<number>
|
|
513
623
|
|
|
514
624
|
// --- JS utilities (appended by scripts/patch-binding.js) ---
|
|
515
625
|
export { highlight, highlightRanges, HighlightRange } from './highlight';
|
|
626
|
+
/** TypedArray variants — identical to the `*Many` counterparts but return a typed array instead of `Array<number>`, reducing GC pressure for large candidate sets. */
|
|
627
|
+
export declare function levenshteinManyU32(reference: string, candidates: Array<string>, maxDistance?: number | undefined | null): Uint32Array;
|
|
628
|
+
export declare function damerauLevenshteinManyU32(reference: string, candidates: Array<string>, maxDistance?: number | undefined | null): Uint32Array;
|
|
629
|
+
export declare function indelManyU32(reference: string, candidates: Array<string>, maxDistance?: number | undefined | null): Uint32Array;
|
|
630
|
+
export declare function jaroManyF64(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Float64Array;
|
|
631
|
+
export declare function jaroWinklerManyF64(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Float64Array;
|
|
632
|
+
export declare function sorensenDiceManyF64(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Float64Array;
|
|
633
|
+
export declare function normalizedLevenshteinManyF64(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Float64Array;
|
|
634
|
+
export declare function normalizedIndelManyF64(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Float64Array;
|
|
635
|
+
export declare function tokenSortRatioManyF64(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Float64Array;
|
|
636
|
+
export declare function tokenSetRatioManyF64(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Float64Array;
|
|
637
|
+
export declare function partialRatioManyF64(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Float64Array;
|
|
638
|
+
export declare function weightedRatioManyF64(reference: string, candidates: Array<string>, minSimilarity?: number | undefined | null): Float64Array;
|