rapid-fuzzy 0.3.0 → 0.4.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 +59 -1
- package/browser.js +3 -0
- package/highlight.d.ts +55 -0
- package/highlight.js +58 -0
- package/highlight.mjs +57 -0
- package/index.d.mts +4 -0
- package/index.d.ts +32 -0
- package/index.js +6 -0
- package/index.mjs +6 -1
- package/objects.d.ts +42 -0
- package/objects.js +58 -0
- package/package.json +22 -11
package/README.md
CHANGED
|
@@ -69,6 +69,9 @@ search('app', items, 5);
|
|
|
69
69
|
const [match] = search('hlo', ['hello world'], { includePositions: true });
|
|
70
70
|
// → { item: 'hello world', score: 0.75, index: 0, positions: [0, 2, 4] }
|
|
71
71
|
|
|
72
|
+
// Case-sensitive matching (default: smart case)
|
|
73
|
+
search('Type', items, { isCaseSensitive: true });
|
|
74
|
+
|
|
72
75
|
// Find the single best match
|
|
73
76
|
closest('tsc', ['TypeScript', 'JavaScript', 'Python']);
|
|
74
77
|
// → 'TypeScript'
|
|
@@ -78,6 +81,59 @@ closest('xyz', items, 0.5);
|
|
|
78
81
|
// → null
|
|
79
82
|
```
|
|
80
83
|
|
|
84
|
+
### Object Search
|
|
85
|
+
|
|
86
|
+
Search across object properties with weighted keys — a drop-in replacement for fuse.js's `keys` option:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { searchObjects } from 'rapid-fuzzy';
|
|
90
|
+
|
|
91
|
+
const users = [
|
|
92
|
+
{ name: 'John Smith', email: 'john@example.com' },
|
|
93
|
+
{ name: 'Jane Doe', email: 'jane@example.com' },
|
|
94
|
+
{ name: 'Bob Johnson', email: 'bob@test.com' },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// Search across multiple keys
|
|
98
|
+
const results = searchObjects('john', users, {
|
|
99
|
+
keys: ['name', 'email'],
|
|
100
|
+
});
|
|
101
|
+
// → [{ item: { name: 'John Smith', ... }, score: 0.95, keyScores: [0.98, 0.85], index: 0 }]
|
|
102
|
+
|
|
103
|
+
// Weighted keys — prioritize name matches over email
|
|
104
|
+
searchObjects('john', users, {
|
|
105
|
+
keys: [
|
|
106
|
+
{ name: 'name', weight: 2.0 },
|
|
107
|
+
{ name: 'email', weight: 1.0 },
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Nested key paths
|
|
112
|
+
searchObjects('new york', items, { keys: ['address.city'] });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Match Highlighting
|
|
116
|
+
|
|
117
|
+
Convert matched positions into highlighted markup for UI rendering:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { search, highlight, highlightRanges } from 'rapid-fuzzy';
|
|
121
|
+
|
|
122
|
+
const results = search('fzy', ['fuzzy'], { includePositions: true });
|
|
123
|
+
const { item, positions } = results[0];
|
|
124
|
+
|
|
125
|
+
// String markers
|
|
126
|
+
highlight(item, positions, '<b>', '</b>');
|
|
127
|
+
// → '<b>f</b>u<b>zy</b>'
|
|
128
|
+
|
|
129
|
+
// Callback (React, JSX, custom DOM)
|
|
130
|
+
highlight(item, positions, (matched) => `<mark>${matched}</mark>`);
|
|
131
|
+
|
|
132
|
+
// Raw ranges for custom rendering
|
|
133
|
+
highlightRanges(item, positions);
|
|
134
|
+
// → [{ start: 0, end: 1, matched: true }, { start: 1, end: 2, matched: false }, ...]
|
|
135
|
+
```
|
|
136
|
+
|
|
81
137
|
### Token-Based Matching
|
|
82
138
|
|
|
83
139
|
Order-independent and partial string matching, inspired by Python's [RapidFuzz](https://github.com/rapidfuzz/RapidFuzz):
|
|
@@ -199,8 +255,10 @@ cargo bench # Rust internal benchmarks
|
|
|
199
255
|
|---|---|---|---|---|
|
|
200
256
|
| **Algorithms** | Levenshtein, Jaro-Winkler, Sorensen-Dice, Damerau-Levenshtein, token sort/set, partial ratio, fuzzy search | Bitap-based fuzzy | Levenshtein only | Substring fuzzy |
|
|
201
257
|
| **Runtime** | Rust (native + WASM) | Pure JS | Pure JS | Pure JS |
|
|
258
|
+
| **Object search** | Yes (searchObjects with weighted keys) | Yes (keys option) | No | Yes (keys) |
|
|
202
259
|
| **Score threshold** | Yes (minScore) | Yes (threshold) | No | Yes (threshold) |
|
|
203
260
|
| **Match positions** | Yes (includePositions) | Yes | No | Yes |
|
|
261
|
+
| **Highlight utility** | Yes (highlight, highlightRanges) | No (manual) | No | Yes (highlight) |
|
|
204
262
|
| **Batch API** | Yes | No | No | No |
|
|
205
263
|
| **Node.js native** | Yes (napi-rs) | No | No | No |
|
|
206
264
|
| **Browser support** | Yes (WASM) | Yes | Yes | Yes |
|
|
@@ -211,7 +269,7 @@ cargo bench # Rust internal benchmarks
|
|
|
211
269
|
Switching from another library? These guides provide API mapping tables, code examples, and performance comparisons:
|
|
212
270
|
|
|
213
271
|
- [**From string-similarity**](docs/migration/from-string-similarity.md) — Same Dice coefficient algorithm, now maintained and faster
|
|
214
|
-
- [**From fuse.js**](docs/migration/from-fuse-js.md) —
|
|
272
|
+
- [**From fuse.js**](docs/migration/from-fuse-js.md) — 17–40x faster fuzzy search with a simpler API
|
|
215
273
|
- [**From leven / fastest-levenshtein**](docs/migration/from-leven.md) — Multi-algorithm upgrade with batch APIs
|
|
216
274
|
|
|
217
275
|
## License
|
package/browser.js
CHANGED
package/highlight.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** A range within a string, indicating whether it was matched. */
|
|
2
|
+
export interface HighlightRange {
|
|
3
|
+
/** Start index (inclusive). */
|
|
4
|
+
start: number;
|
|
5
|
+
/** End index (exclusive). */
|
|
6
|
+
end: number;
|
|
7
|
+
/** Whether this range was part of the match. */
|
|
8
|
+
matched: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Highlight matched characters in a search result string.
|
|
13
|
+
*
|
|
14
|
+
* Use with `SearchResult.positions` from a search with `includePositions: true`.
|
|
15
|
+
*
|
|
16
|
+
* @example String markers
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const results = search('fzy', ['fuzzy'], { includePositions: true });
|
|
19
|
+
* highlight(results[0].item, results[0].positions, '<b>', '</b>');
|
|
20
|
+
* // → '<b>f</b>u<b>zy</b>'
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example Callback (React, custom DOM, etc.)
|
|
24
|
+
* ```typescript
|
|
25
|
+
* highlight(result.item, result.positions, (matched) => `<mark>${matched}</mark>`);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function highlight(
|
|
29
|
+
item: string,
|
|
30
|
+
positions: Array<number>,
|
|
31
|
+
open: string,
|
|
32
|
+
close: string,
|
|
33
|
+
): string;
|
|
34
|
+
export declare function highlight(
|
|
35
|
+
item: string,
|
|
36
|
+
positions: Array<number>,
|
|
37
|
+
callback: (matched: string) => string,
|
|
38
|
+
): string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Convert matched positions into an array of ranges for custom rendering.
|
|
42
|
+
*
|
|
43
|
+
* Each range indicates a contiguous segment of the string and whether it was
|
|
44
|
+
* part of the match. Useful for building custom highlight components.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const ranges = highlightRanges(result.item, result.positions);
|
|
49
|
+
* // → [{ start: 0, end: 1, matched: true }, { start: 1, end: 2, matched: false }, ...]
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function highlightRanges(
|
|
53
|
+
item: string,
|
|
54
|
+
positions: Array<number>,
|
|
55
|
+
): Array<HighlightRange>;
|
package/highlight.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Pure JS highlight utilities — works in both Node.js and browser environments.
|
|
2
|
+
// This file is manually maintained (not auto-generated by napi-rs).
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} item
|
|
8
|
+
* @param {number[]} positions
|
|
9
|
+
* @returns {Array<{start: number, end: number, matched: boolean}>}
|
|
10
|
+
*/
|
|
11
|
+
function highlightRanges(item, positions) {
|
|
12
|
+
if (!item) return [];
|
|
13
|
+
if (!positions || positions.length === 0) {
|
|
14
|
+
return [{ start: 0, end: item.length, matched: false }];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const set = new Set(positions);
|
|
18
|
+
const ranges = [];
|
|
19
|
+
let i = 0;
|
|
20
|
+
|
|
21
|
+
while (i < item.length) {
|
|
22
|
+
const matched = set.has(i);
|
|
23
|
+
const start = i;
|
|
24
|
+
while (i < item.length && set.has(i) === matched) i++;
|
|
25
|
+
ranges.push({ start, end: i, matched });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return ranges;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} item
|
|
33
|
+
* @param {number[]} positions
|
|
34
|
+
* @param {string | ((substring: string) => string)} openOrCallback
|
|
35
|
+
* @param {string} [close]
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
function highlight(item, positions, openOrCallback, close) {
|
|
39
|
+
if (!positions || positions.length === 0) return item;
|
|
40
|
+
|
|
41
|
+
const ranges = highlightRanges(item, positions);
|
|
42
|
+
const useCallback = typeof openOrCallback === 'function';
|
|
43
|
+
|
|
44
|
+
const parts = [];
|
|
45
|
+
for (const range of ranges) {
|
|
46
|
+
const segment = item.slice(range.start, range.end);
|
|
47
|
+
if (range.matched) {
|
|
48
|
+
parts.push(useCallback ? openOrCallback(segment) : openOrCallback + segment + (close ?? ''));
|
|
49
|
+
} else {
|
|
50
|
+
parts.push(segment);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return parts.join('');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports.highlight = highlight;
|
|
58
|
+
module.exports.highlightRanges = highlightRanges;
|
package/highlight.mjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// ESM version of highlight utilities — for browser bundlers and ESM-only environments.
|
|
2
|
+
// Keep in sync with highlight.js (CJS version).
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Convert matched positions into an array of ranges for custom rendering.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} item - The original string from the search result.
|
|
8
|
+
* @param {number[]} positions - Array of matched character indices.
|
|
9
|
+
* @returns {Array<{start: number, end: number, matched: boolean}>} Array of ranges.
|
|
10
|
+
*/
|
|
11
|
+
export function highlightRanges(item, positions) {
|
|
12
|
+
if (!item) return [];
|
|
13
|
+
if (!positions || positions.length === 0) {
|
|
14
|
+
return [{ start: 0, end: item.length, matched: false }];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const set = new Set(positions);
|
|
18
|
+
const ranges = [];
|
|
19
|
+
let i = 0;
|
|
20
|
+
|
|
21
|
+
while (i < item.length) {
|
|
22
|
+
const matched = set.has(i);
|
|
23
|
+
const start = i;
|
|
24
|
+
while (i < item.length && set.has(i) === matched) i++;
|
|
25
|
+
ranges.push({ start, end: i, matched });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return ranges;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Highlight matched characters in a search result string.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} item - The original string from the search result.
|
|
35
|
+
* @param {number[]} positions - Array of matched character indices.
|
|
36
|
+
* @param {string | ((substring: string) => string)} openOrCallback - Opening tag or callback.
|
|
37
|
+
* @param {string} [close] - Closing tag (required when openOrCallback is a string).
|
|
38
|
+
* @returns {string} The highlighted string.
|
|
39
|
+
*/
|
|
40
|
+
export function highlight(item, positions, openOrCallback, close) {
|
|
41
|
+
if (!positions || positions.length === 0) return item;
|
|
42
|
+
|
|
43
|
+
const ranges = highlightRanges(item, positions);
|
|
44
|
+
const useCallback = typeof openOrCallback === 'function';
|
|
45
|
+
|
|
46
|
+
const parts = [];
|
|
47
|
+
for (const range of ranges) {
|
|
48
|
+
const segment = item.slice(range.start, range.end);
|
|
49
|
+
if (range.matched) {
|
|
50
|
+
parts.push(useCallback ? openOrCallback(segment) : openOrCallback + segment + (close ?? ''));
|
|
51
|
+
} else {
|
|
52
|
+
parts.push(segment);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return parts.join('');
|
|
57
|
+
}
|
package/index.d.mts
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
1
|
export * from './index.d.ts';
|
|
2
|
+
export { highlight, highlightRanges } from './highlight.d.ts';
|
|
3
|
+
export type { HighlightRange } from './highlight.d.ts';
|
|
4
|
+
export { searchObjects } from './objects';
|
|
5
|
+
export type { KeyConfig, ObjectSearchOptions, ObjectSearchResult } from './objects';
|
package/index.d.ts
CHANGED
|
@@ -114,6 +114,19 @@ export declare function jaroWinklerBatch(pairs: Array<Array<string>>): Array<num
|
|
|
114
114
|
*/
|
|
115
115
|
export declare function jaroWinklerMany(reference: string, candidates: Array<string>): Array<number>
|
|
116
116
|
|
|
117
|
+
/** A single result from multi-key fuzzy search. */
|
|
118
|
+
export interface KeySearchResult {
|
|
119
|
+
/** The index of the item in the original input array. */
|
|
120
|
+
index: number
|
|
121
|
+
/** The combined weighted score normalized to 0.0-1.0 range. */
|
|
122
|
+
score: number
|
|
123
|
+
/**
|
|
124
|
+
* Per-key scores in the same order as the input keys.
|
|
125
|
+
* A score of 0.0 means the item did not match on that key.
|
|
126
|
+
*/
|
|
127
|
+
keyScores: Array<number>
|
|
128
|
+
}
|
|
129
|
+
|
|
117
130
|
/**
|
|
118
131
|
* Compute the Levenshtein distance between two strings.
|
|
119
132
|
*
|
|
@@ -196,6 +209,17 @@ export declare function partialRatioMany(reference: string, candidates: Array<st
|
|
|
196
209
|
*/
|
|
197
210
|
export declare function search(query: string, items: Array<string>, options?: number | SearchOptions | undefined | null): Array<SearchResult>
|
|
198
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Perform fuzzy search across multiple text keys with weights.
|
|
214
|
+
*
|
|
215
|
+
* `key_texts[k]` is an array of strings for key `k`, one per item.
|
|
216
|
+
* All inner arrays must have the same length (the number of items).
|
|
217
|
+
* `weights` specifies the relative importance of each key.
|
|
218
|
+
*
|
|
219
|
+
* Returns results sorted by combined weighted score (best match first).
|
|
220
|
+
*/
|
|
221
|
+
export declare function searchKeys(query: string, keyTexts: Array<Array<string>>, weights: Array<number>, options?: SearchOptions | undefined | null): Array<KeySearchResult>
|
|
222
|
+
|
|
199
223
|
/** Options for the search function. */
|
|
200
224
|
export interface SearchOptions {
|
|
201
225
|
/** Maximum number of results to return. */
|
|
@@ -204,6 +228,11 @@ export interface SearchOptions {
|
|
|
204
228
|
minScore?: number
|
|
205
229
|
/** If true, include matched character positions in results. */
|
|
206
230
|
includePositions?: boolean
|
|
231
|
+
/**
|
|
232
|
+
* If true, matching is case-sensitive. Default is smart case
|
|
233
|
+
* (case-insensitive unless the query contains uppercase characters).
|
|
234
|
+
*/
|
|
235
|
+
isCaseSensitive?: boolean
|
|
207
236
|
}
|
|
208
237
|
|
|
209
238
|
/** A single fuzzy search result with the matched item and its score. */
|
|
@@ -314,3 +343,6 @@ export declare function weightedRatioBatch(pairs: Array<Array<string>>): Array<n
|
|
|
314
343
|
* Returns an array of similarity scores, one per candidate, in the same order as the input.
|
|
315
344
|
*/
|
|
316
345
|
export declare function weightedRatioMany(reference: string, candidates: Array<string>): Array<number>
|
|
346
|
+
|
|
347
|
+
// --- JS utilities (appended by scripts/patch-binding.js) ---
|
|
348
|
+
export { highlight, highlightRanges, HighlightRange } from './highlight';
|
package/index.js
CHANGED
|
@@ -597,6 +597,7 @@ module.exports.partialRatio = nativeBinding.partialRatio
|
|
|
597
597
|
module.exports.partialRatioBatch = nativeBinding.partialRatioBatch
|
|
598
598
|
module.exports.partialRatioMany = nativeBinding.partialRatioMany
|
|
599
599
|
module.exports.search = nativeBinding.search
|
|
600
|
+
module.exports.searchKeys = nativeBinding.searchKeys
|
|
600
601
|
module.exports.sorensenDice = nativeBinding.sorensenDice
|
|
601
602
|
module.exports.sorensenDiceBatch = nativeBinding.sorensenDiceBatch
|
|
602
603
|
module.exports.sorensenDiceMany = nativeBinding.sorensenDiceMany
|
|
@@ -609,3 +610,8 @@ module.exports.tokenSortRatioMany = nativeBinding.tokenSortRatioMany
|
|
|
609
610
|
module.exports.weightedRatio = nativeBinding.weightedRatio
|
|
610
611
|
module.exports.weightedRatioBatch = nativeBinding.weightedRatioBatch
|
|
611
612
|
module.exports.weightedRatioMany = nativeBinding.weightedRatioMany
|
|
613
|
+
|
|
614
|
+
// --- JS utilities (appended by scripts/patch-binding.js) ---
|
|
615
|
+
const _hl = require('./highlight.js');
|
|
616
|
+
module.exports.highlight = _hl.highlight;
|
|
617
|
+
module.exports.highlightRanges = _hl.highlightRanges;
|
package/index.mjs
CHANGED
|
@@ -6,6 +6,7 @@ const binding = require('./index.js');
|
|
|
6
6
|
export const {
|
|
7
7
|
FuzzyIndex,
|
|
8
8
|
closest,
|
|
9
|
+
searchKeys,
|
|
9
10
|
damerauLevenshtein,
|
|
10
11
|
damerauLevenshteinBatch,
|
|
11
12
|
damerauLevenshteinMany,
|
|
@@ -37,4 +38,8 @@ export const {
|
|
|
37
38
|
weightedRatio,
|
|
38
39
|
weightedRatioBatch,
|
|
39
40
|
weightedRatioMany,
|
|
40
|
-
|
|
41
|
+
highlight,
|
|
42
|
+
highlightRanges,
|
|
43
|
+
} = { ...binding, ...require('./highlight.js') };
|
|
44
|
+
|
|
45
|
+
export const { searchObjects } = require('./objects.js');
|
package/objects.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { SearchOptions } from './index';
|
|
2
|
+
|
|
3
|
+
export interface KeyConfig {
|
|
4
|
+
name: string;
|
|
5
|
+
weight?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ObjectSearchOptions extends SearchOptions {
|
|
9
|
+
keys: Array<string | KeyConfig>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ObjectSearchResult<T> {
|
|
13
|
+
item: T;
|
|
14
|
+
index: number;
|
|
15
|
+
score: number;
|
|
16
|
+
keyScores: Array<number>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Perform fuzzy search across object arrays with weighted keys.
|
|
21
|
+
*
|
|
22
|
+
* Wraps `searchKeys()` with an ergonomic API that accepts row-oriented
|
|
23
|
+
* objects and returns matched items directly.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const users = [
|
|
28
|
+
* { name: 'John Smith', email: 'john@example.com' },
|
|
29
|
+
* { name: 'Jane Doe', email: 'jane@example.com' },
|
|
30
|
+
* ];
|
|
31
|
+
*
|
|
32
|
+
* const results = searchObjects('john', users, {
|
|
33
|
+
* keys: [{ name: 'name', weight: 2.0 }, 'email'],
|
|
34
|
+
* });
|
|
35
|
+
* // results[0].item → { name: 'John Smith', email: 'john@example.com' }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function searchObjects<T>(
|
|
39
|
+
query: string,
|
|
40
|
+
items: Array<T>,
|
|
41
|
+
options: ObjectSearchOptions,
|
|
42
|
+
): Array<ObjectSearchResult<T>>;
|
package/objects.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { searchKeys } = require('./index.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get a nested property value from an object using a dot-separated path.
|
|
7
|
+
* @param {Record<string, unknown>} obj
|
|
8
|
+
* @param {string} path
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
function getNestedValue(obj, path) {
|
|
12
|
+
let current = obj;
|
|
13
|
+
for (const key of path.split('.')) {
|
|
14
|
+
if (current == null) return '';
|
|
15
|
+
current = current[key];
|
|
16
|
+
}
|
|
17
|
+
return current == null ? '' : String(current);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Perform fuzzy search across object arrays with weighted keys.
|
|
22
|
+
*
|
|
23
|
+
* Wraps `searchKeys()` with an ergonomic API that accepts row-oriented
|
|
24
|
+
* objects and returns matched items directly.
|
|
25
|
+
*
|
|
26
|
+
* @template T
|
|
27
|
+
* @param {string} query - The search query.
|
|
28
|
+
* @param {T[]} items - Array of objects to search.
|
|
29
|
+
* @param {object} options - Search options with keys configuration.
|
|
30
|
+
* @param {Array<string | { name: string; weight?: number }>} options.keys - Keys to search.
|
|
31
|
+
* @param {number} [options.maxResults] - Maximum results to return.
|
|
32
|
+
* @param {number} [options.minScore] - Minimum score threshold.
|
|
33
|
+
* @param {boolean} [options.isCaseSensitive] - Enable case-sensitive matching.
|
|
34
|
+
* @returns {Array<{ item: T; index: number; score: number; keyScores: number[] }>}
|
|
35
|
+
*/
|
|
36
|
+
function searchObjects(query, items, options) {
|
|
37
|
+
const { keys, ...searchOpts } = options;
|
|
38
|
+
|
|
39
|
+
const normalizedKeys = keys.map((k) =>
|
|
40
|
+
typeof k === 'string' ? { name: k, weight: 1.0 } : { name: k.name, weight: k.weight ?? 1.0 },
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const keyTexts = normalizedKeys.map((k) => items.map((item) => getNestedValue(item, k.name)));
|
|
44
|
+
const weights = normalizedKeys.map((k) => k.weight);
|
|
45
|
+
|
|
46
|
+
const nativeOpts = Object.keys(searchOpts).length > 0 ? searchOpts : undefined;
|
|
47
|
+
|
|
48
|
+
const results = searchKeys(query, keyTexts, weights, nativeOpts);
|
|
49
|
+
|
|
50
|
+
return results.map((r) => ({
|
|
51
|
+
item: items[r.index],
|
|
52
|
+
index: r.index,
|
|
53
|
+
score: r.score,
|
|
54
|
+
keyScores: r.keyScores,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { searchObjects };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapid-fuzzy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -46,7 +46,12 @@
|
|
|
46
46
|
"index.mjs",
|
|
47
47
|
"index.d.ts",
|
|
48
48
|
"index.d.mts",
|
|
49
|
-
"
|
|
49
|
+
"objects.js",
|
|
50
|
+
"objects.d.ts",
|
|
51
|
+
"browser.js",
|
|
52
|
+
"highlight.js",
|
|
53
|
+
"highlight.mjs",
|
|
54
|
+
"highlight.d.ts"
|
|
50
55
|
],
|
|
51
56
|
"napi": {
|
|
52
57
|
"binaryName": "rapid-fuzzy",
|
|
@@ -68,6 +73,7 @@
|
|
|
68
73
|
"packageManager": "pnpm@10.32.1",
|
|
69
74
|
"scripts": {
|
|
70
75
|
"build": "napi build --manifest-path crates/core/Cargo.toml --platform --release --js index.js --dts index.d.ts --output-dir .",
|
|
76
|
+
"postbuild": "node scripts/patch-binding.js",
|
|
71
77
|
"build:debug": "napi build --manifest-path crates/core/Cargo.toml --platform --js index.js --dts index.d.ts --output-dir .",
|
|
72
78
|
"build:wasm": "napi build --manifest-path crates/core/Cargo.toml --platform --release --js index.js --dts index.d.ts --output-dir . --target wasm32-wasip1-threads",
|
|
73
79
|
"artifacts": "napi artifacts",
|
|
@@ -80,6 +86,9 @@
|
|
|
80
86
|
"typecheck": "tsc --noEmit",
|
|
81
87
|
"test": "vitest run",
|
|
82
88
|
"test:wasm": "vitest run __test__/wasm.spec.ts",
|
|
89
|
+
"test:browser": "playwright test",
|
|
90
|
+
"test:bun": "bun test e2e/wasm-bun.test.ts",
|
|
91
|
+
"test:deno": "deno test --allow-read --allow-env --allow-net --node-modules-dir=auto e2e/wasm-deno.test.ts",
|
|
83
92
|
"test:watch": "vitest watch",
|
|
84
93
|
"bench": "vitest bench",
|
|
85
94
|
"bench:readme": "npx tsx scripts/update-bench-readme.ts",
|
|
@@ -99,6 +108,7 @@
|
|
|
99
108
|
"@emnapi/runtime": "^1.9.0",
|
|
100
109
|
"@napi-rs/cli": "^3.5.1",
|
|
101
110
|
"@napi-rs/wasm-runtime": "^1.1.1",
|
|
111
|
+
"@playwright/test": "^1.58.2",
|
|
102
112
|
"@tybys/wasm-util": "^0.10.1",
|
|
103
113
|
"@types/node": "^24.0.0",
|
|
104
114
|
"@types/string-similarity": "^4.0.2",
|
|
@@ -111,6 +121,7 @@
|
|
|
111
121
|
"publint": "^0.3.18",
|
|
112
122
|
"string-similarity": "^4.0.4",
|
|
113
123
|
"typescript": "^5.9.3",
|
|
124
|
+
"vite": "^8.0.0",
|
|
114
125
|
"vitest": "^4.1.0"
|
|
115
126
|
},
|
|
116
127
|
"pnpm": {
|
|
@@ -119,14 +130,14 @@
|
|
|
119
130
|
]
|
|
120
131
|
},
|
|
121
132
|
"optionalDependencies": {
|
|
122
|
-
"rapid-fuzzy-darwin-x64": "0.
|
|
123
|
-
"rapid-fuzzy-darwin-arm64": "0.
|
|
124
|
-
"rapid-fuzzy-linux-x64-gnu": "0.
|
|
125
|
-
"rapid-fuzzy-linux-x64-musl": "0.
|
|
126
|
-
"rapid-fuzzy-linux-arm64-gnu": "0.
|
|
127
|
-
"rapid-fuzzy-linux-arm64-musl": "0.
|
|
128
|
-
"rapid-fuzzy-win32-x64-msvc": "0.
|
|
129
|
-
"rapid-fuzzy-win32-arm64-msvc": "0.
|
|
130
|
-
"rapid-fuzzy-wasm32-wasi": "0.
|
|
133
|
+
"rapid-fuzzy-darwin-x64": "0.4.0",
|
|
134
|
+
"rapid-fuzzy-darwin-arm64": "0.4.0",
|
|
135
|
+
"rapid-fuzzy-linux-x64-gnu": "0.4.0",
|
|
136
|
+
"rapid-fuzzy-linux-x64-musl": "0.4.0",
|
|
137
|
+
"rapid-fuzzy-linux-arm64-gnu": "0.4.0",
|
|
138
|
+
"rapid-fuzzy-linux-arm64-musl": "0.4.0",
|
|
139
|
+
"rapid-fuzzy-win32-x64-msvc": "0.4.0",
|
|
140
|
+
"rapid-fuzzy-win32-arm64-msvc": "0.4.0",
|
|
141
|
+
"rapid-fuzzy-wasm32-wasi": "0.4.0"
|
|
131
142
|
}
|
|
132
143
|
}
|