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 CHANGED
@@ -6,7 +6,7 @@
6
6
  [![npm version](https://img.shields.io/npm/v/rapid-fuzzy)](https://www.npmjs.com/package/rapid-fuzzy)
7
7
  [![npm downloads](https://img.shields.io/npm/dm/rapid-fuzzy)](https://www.npmjs.com/package/rapid-fuzzy)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
- [![Node.js](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen)](https://nodejs.org/)
9
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen)](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** (>=20): Uses native bindings via napi-rs for best performance.
54
- - **Browser / Deno / Bun**: Falls back to a WASM build automatically. The WASM binary is ~660 KB raw (~230 KB gzipped).
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 beyond the SharedArrayBuffer headers above.
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 { levenshtein, jaroWinkler, sorensenDice, hamming } from 'rapid-fuzzy';
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
- jaroWinkler('MARTHA', 'MARHTA'); // 0.961
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
- const buffer = index.serialize();
240
- fs.writeFileSync('search-index.bin', buffer);
347
+ import { FuzzyIndex } from 'rapid-fuzzy';
241
348
 
242
- // Load later (faster than rebuilding from scratch)
243
- const restored = FuzzyIndex.deserialize(fs.readFileSync('search-index.bin'));
244
- restored.search('query');
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
- | Fixed-length comparison | `hamming` | Counts differing positions; only for equal-length strings |
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
@@ -1,4 +1,4 @@
1
- export * from 'rapid-fuzzy-wasm32-wasi'
1
+ export * from './rapid-fuzzy-wasm-bindgen.js';
2
2
 
3
3
  // --- JS utilities (appended by scripts/patch-binding.js) ---
4
4
  export { highlight, highlightRanges } from './highlight.mjs';
package/highlight.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export { highlight, highlightRanges } from './highlight.js';
2
1
  export type { HighlightRange } from './highlight.js';
2
+ export { highlight, highlightRanges } from './highlight.js';
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 { searchObjects, FuzzyObjectIndex } from './objects.js';
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>): Array<number>
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>): Array<number>
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>): Array<number>
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>): Array<number>
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>): Array<number>
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;