@aiquants/fuzzy-search
v1.5.1
Published
Advanced fuzzy search library with Levenshtein distance, n-gram indexing, and Web Worker support
Readme
@aiquants/fuzzy-search
Advanced fuzzy search library with Levenshtein distance, n-gram indexing, and Web Worker support.
Features
- Advanced Fuzzy Search: Levenshtein distance-based fuzzy matching
- Performance Optimized: Web Worker support for non-blocking search
- Multiple Index Types: N-gram, word, and phonetic indexing
- Learning Capabilities: Adapts based on user search patterns
- React Integration: Ready-to-use hooks and components
- Voice Search: Web Speech API integration
- TypeScript First: Full type safety and IntelliSense support
- Highly Configurable: Extensive options for customization
Installation
# Using pnpm (recommended)
pnpm add @aiquants/fuzzy-search
# Using npm
npm install @aiquants/fuzzy-search
# Using yarn
yarn add @aiquants/fuzzy-searchNote: This package is optimized for pnpm. The package manager is specified as [email protected] for better dependency management.
Quick Start
Basic Usage
import { quickSearch } from '@aiquants/fuzzy-search'
const items = [
{ name: 'Apple', category: 'Fruit' },
{ name: 'Banana', category: 'Fruit' },
{ name: 'Carrot', category: 'Vegetable' },
]
// Simple search
const results = await quickSearch('aple', items, ['name'], {
threshold: 0.4,
maxResults: 10
})
console.log(results) // [{ name: 'Apple', category: 'Fruit' }]Advanced Usage
import { FuzzySearchManager } from '@aiquants/fuzzy-search'
const searchManager = new FuzzySearchManager({
threshold: 0.4,
maxResults: 50,
enableNgramIndex: true,
enablePhonetic: true,
learningWeight: 1.2,
debounceMs: 300,
enableHighlighting: true,
})
// Perform search
const results = await searchManager.search('query', items, ['name', 'description'])
// Handle results
results.forEach(result => {
console.log(`Item: ${result.item.name}, Score: ${result.score}`)
if (result.highlighted) {
console.log(`Highlighted: ${result.highlighted.name}`)
}
})
// Clean up
searchManager.dispose()Logging and Worker Configuration
You can configure logging levels and custom logger implementations. You can also specify a workerId to manage multiple independent worker instances.
Worker Isolation vs Sharing
The workerId option controls how Web Workers are managed:
- Isolation (Different IDs): Using different
workerIds creates separate worker instances. This is useful when you have multiple search components that need to process different datasets independently and in parallel. - Sharing (Same ID): Using the same
workerIdallows multipleFuzzySearchManagerinstances to share the same underlying Web Workers. This is efficient for memory usage and allows reusing the built index if the data is the same.
import { FuzzySearchManager, LogLevel } from '@aiquants/fuzzy-search'
// Instance 1: Uses "shared-worker"
const manager1 = new FuzzySearchManager({
workerId: "shared-worker",
// ...
})
// Instance 2: Also uses "shared-worker"
// This instance will share the same Web Workers as manager1.
// If the data is the same, the index will not be rebuilt.
const manager2 = new FuzzySearchManager({
workerId: "shared-worker",
// ...
})
// Instance 3: Uses "isolated-worker"
// This creates a completely new set of Web Workers.
const manager3 = new FuzzySearchManager({
workerId: "isolated-worker",
// ...
})import { FuzzySearchManager, LogLevel } from '@aiquants/fuzzy-search'
const manager = new FuzzySearchManager({
// ... other options
logLevel: LogLevel.DEBUG, // Set log level (DEBUG, INFO, WARN, ERROR, NONE)
workerId: "my-search-instance", // Unique ID for worker isolation
logger: console, // Optional: Custom logger implementation
})React Integration
Hooks
import { useFuzzySearch } from '@aiquants/fuzzy-search/react'
function SearchComponent() {
const {
searchTerm,
setSearchTerm,
filteredItems,
isLoading,
highlightText,
stats
} = useFuzzySearch(items, ['name', 'email'], {
threshold: 0.4,
debounceMs: 300,
enableHighlighting: true,
})
return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
{isLoading ? (
<div>Searching...</div>
) : (
<ul>
{filteredItems.map((item, index) => (
<li key={index}>
<div dangerouslySetInnerHTML={{
__html: highlightText(item.name)
}} />
</li>
))}
</ul>
)}
<div>Found {stats.totalResults} results in {stats.executionTime}ms</div>
</div>
)
}Important Note: Memoization Required
When using useFuzzySearch, you must memoize the keys array and options object using useMemo. If you pass new object references on every render, the hook will detect them as configuration changes and re-initialize the search worker, potentially causing an infinite loop.
import { useMemo } from 'react'
function SearchComponent() {
// ✅ Correct: Memoize keys and options
const keys = useMemo(() => ['name', 'email'], [])
const options = useMemo(() => ({
threshold: 0.4,
debounceMs: 300,
enableHighlighting: true,
}), [])
const {
searchTerm,
setSearchTerm,
filteredItems,
// ...
} = useFuzzySearch(items, keys, options)
// ...
}Voice Search
import { useVoiceSearch } from '@aiquants/fuzzy-search/react'
function VoiceSearchComponent() {
const {
isListening,
transcript,
startListening,
stopListening,
isSupported
} = useVoiceSearch({
onResult: (text) => console.log('Speech result:', text),
continuous: false,
interimResults: true,
})
if (!isSupported) {
return <div>Voice search not supported</div>
}
return (
<div>
<button onClick={isListening ? stopListening : startListening}>
{isListening ? 'Stop' : 'Start'} Voice Search
</button>
<div>Transcript: {transcript}</div>
</div>
)
}Configuration Options
interface FuzzySearchOptions {
// Basic options
threshold: number // Similarity threshold (0-1)
maxResults: number // Maximum number of results
caseSensitive: boolean // Case-sensitive matching
// Performance options
enableNgramIndex: boolean // Enable n-gram indexing
enablePhonetic: boolean // Enable phonetic matching
ngramSize: number // N-gram size
minNgramOverlap: number // Minimum n-gram overlap
// Learning options
learningWeight: number // Weight for learning adjustments
// UI options
debounceMs: number // Debounce delay in milliseconds
enableHighlighting: boolean // Enable result highlighting
// Custom weights
customWeights: Record<string, number> // Field-specific weights
}Web Worker Support
For large datasets, the search can be performed in a Web Worker to avoid blocking the main thread:
import { FuzzySearchManager } from '@aiquants/fuzzy-search'
const searchManager = new FuzzySearchManager({
useWebWorker: true, // Enable Web Worker
threshold: 0.4,
})
// Search will automatically use Web Worker for large datasets
const results = await searchManager.search('query', largeItems, ['name'])Performance Tips
- Enable n-gram indexing for large datasets (>1000 items)
- Use Web Workers for datasets with >5000 items
- Adjust threshold based on your use case (lower = more results)
- Use debouncing to reduce search frequency
- Configure custom weights for field importance
API Reference
Core Classes
FuzzySearchManager: Main search management classSearchIndexer: Advanced indexing system
React Hooks
useFuzzySearch: Main search hookuseVoiceSearch: Voice search hookuseSearchHistory: Search history management
Utility Functions
quickSearch: Simple search functionlevenshteinDistance: Distance calculationhighlightText: Text highlighting
Browser Support
- Chrome / Chromium 60+
- Firefox 55+
- Safari 11+
- Edge 79+
License
MIT License
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
