### Building Fuzzy Search Project with npm Source: https://github.com/m31coding/fuzzy-search/blob/main/README.md Install project dependencies and build the fuzzy search library using npm commands. This requires Node.js and npm to be installed on the system, and should be run after cloning the repository. ```bash npm install npm run build ``` -------------------------------- ### Create Default Searcher with TypeScript Source: https://context7.com/m31coding/fuzzy-search/llms.txt Demonstrates creating a default searcher instance for fuzzy, substring, and prefix searching. Indexes Person entities by first name, last name, and full name using their ID as the key. The example shows basic setup with standard Latin character normalization and outputs indexing metadata. ```typescript import * as fuzzySearch from '@m31coding/fuzzy-search'; // Define entity structure interface Person { id: number; firstName: string; lastName: string; } // Create searcher with default configuration const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); // Define entities const persons: Person[] = [ { id: 23501, firstName: 'Alice', lastName: 'King' }, { id: 99234, firstName: 'Bob', lastName: 'Bishop' }, { id: 45678, firstName: 'Charlie', lastName: 'Knight' }, { id: 12389, firstName: 'Sarah', lastName: 'Johnson' } ]; // Index entities with ID and terms extractors const indexingMeta = searcher.indexEntities( persons, (person) => person.id, (person) => [person.firstName, person.lastName, `${person.firstName} ${person.lastName}`] ); console.log('Indexed:', indexingMeta.get('entityCount'), 'entities'); // Output: Indexed: 4 entities ``` -------------------------------- ### Fuzzy Search Results Example (Text) Source: https://github.com/m31coding/fuzzy-search/blob/main/src/regression-test/output/carcassonne-prefix.txt An example of ranked search results, showing the rank, the matched entity, the string that was matched, and a quality score for each result. This format is typically generated after a search operation. ```text Rank Entity Matched String Quality 1 Carcassonne Carcassonne 2.73 2 Carcasse Carcasse 0.74 3 Carasso Carasso 0.63 4 Carcas Carcas 0.63 5 Carcasi Carcasi 0.63 6 Carcasí Carcasí 0.63 7 Carcaboso Carcaboso 0.57 8 Casaracra Casaracra 0.57 9 Caracase Caracase 0.53 10 Cassou Cassou 0.53 ``` -------------------------------- ### Configure Fuzzy Search Request in JSON Source: https://github.com/m31coding/fuzzy-search/blob/main/src/regression-test/output/carcassonne-substring.txt Defines search parameters with query string 'cassonn', topN limit of 10, and three searcher algorithms with quality thresholds. The configuration enables fuzzy matching at 0.3 quality, plus substring and prefix matching with no minimum quality filter. ```json { "string": "cassonn", "topN": 10, "searchers": [ { "type": "fuzzy", "minQuality": 0.3 }, { "type": "substring", "minQuality": 0 }, { "type": "prefix", "minQuality": 0 } ] } ``` -------------------------------- ### Handle Fuzzy Search Response in JSON Source: https://github.com/m31coding/fuzzy-search/blob/main/src/regression-test/output/carcassonne-substring.txt Shows the response structure containing queryDuration in milliseconds. This minimalist format provides performance metrics for the executed search operation without including full result details. ```json { "queryDuration": 4 } ``` -------------------------------- ### Indexing Plain Strings in Fuzzy Search with JavaScript Source: https://github.com/m31coding/fuzzy-search/blob/main/README.md This example demonstrates indexing a simple array of strings for fuzzy search, using the string itself as the ID and searchable field. It requires a created searcher from the fuzzy-search module and outputs indexing metadata with term counts and duration. ```javascript const indexingMeta = searcher.indexEntities( ["Alice", "Bob", "Carol", "Charlie"], (e) => e, (e) => [e] ); ``` -------------------------------- ### Create and modify fuzzy search configuration - TypeScript Source: https://context7.com/m31coding/fuzzy-search/llms.txt Shows how to create default configuration for fuzzy search and modify it for specific needs. Includes examples for adjusting search types, query length, and Unicode character support. Requires @m31coding/fuzzy-search library. ```typescript import * as fuzzySearch from '@m31coding/fuzzy-search'; // Get default configuration const config = fuzzySearch.Config.createDefaultConfig(); // Inspect default settings console.log('Searcher types:', config.searcherTypes); // Output: ["fuzzy", "substring", "prefix"] console.log('Max query length:', config.maxQueryLength); // Output: 150 console.log('Sort order:', config.sortOrder); // Output: "qualityAndMatchedString" console.log('N-gram size:', config.fuzzySearchConfig?.ngramN); // Output: 3 // Modify for specific needs config.searcherTypes = [fuzzySearch.SearcherType.Fuzzy]; config.maxQueryLength = 50; config.fuzzySearchConfig.inequalityPenalty = 0.1; // Allow all Unicode characters (for multilingual support) config.normalizerConfig.allowCharacter = (_c) => true; // Create searcher with modified config const searcher = fuzzySearch.SearcherFactory.createSearcher(config); // Example: Index Japanese text const products = [ { id: 1, name: 'コーーヒー' }, // Coffee { id: 2, name: '緑茶' } // Green tea ]; searcher.indexEntities( products, (p) => p.id, (p) => [p.name] ); const result = searcher.getMatches(new fuzzySearch.Query('コーーヒー', 10)); console.log('Found:', result.matches[0]?.entity.name); ``` -------------------------------- ### Basic Fuzzy Search Usage in JavaScript Source: https://github.com/m31coding/fuzzy-search/blob/main/README.md This example shows how to create a default fuzzy searcher using ESM syntax, index a list of persons with their IDs and searchable fields, perform fuzzy searches, remove entities by ID, and upsert updated entities. It requires importing the fuzzy-search module and provides outputs like indexing metadata, search results with quality scores, removal confirmations, and upsert metadata for performance tracking. ```javascript import * as fuzzySearch from './path/to/fuzzy-search.module.js'; const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); const persons = [ { id: 23501, firstName: 'Alice', lastName: 'King' }, { id: 99234, firstName: 'Bob', lastName: 'Bishop' }, { id: 5823, firstName: 'Carol', lastName: 'Queen' }, { id: 11923, firstName: 'Charlie', lastName: 'Rook' } ]; function log(obj: T): void { console.log(JSON.stringify(obj, null, 2)); } const indexingMeta = searcher.indexEntities( persons, (e) => e.id, (e) => [e.firstName, e.lastName, `${e.firstName} ${e.lastName}`] ); log(indexingMeta); /* { "entries": { "numberOfTerms": 12, "indexingDurationTotal": 1, ... } } */ const result = searcher.getMatches(new fuzzySearch.Query('alice kign')); log(result); /* { "matches": [ { "entity": { "id": 23501, "firstName": "Alice", "lastName": "King" }, "quality": 0.8636363636363635, "matchedString": "Alice King" } ], "query": { "string": "alice kign", "topN": 10, "searchers": [ { "type": "fuzzy", "minQuality": 0.3 }, { "type": "substring", "minQuality": 0 }, { "type": "prefix", "minQuality": 0 } ] }, "meta": { "entries": { "queryDuration": 1 } } } */ const removalResult = searcher.removeEntities([99234, 5823]); log(removalResult); /* { "removedEntities": [ 99234, 5823 ], "meta": { "entries": { "removalDuration": 0 } } } */ const persons2 = [ { id: 723, firstName: 'David', lastName: 'Knight' }, // new { id: 2634, firstName: 'Eve', lastName: 'Pawn' }, // new { id: 23501, firstName: 'Allie', lastName: 'King' }, // updated { id: 11923, firstName: 'Charles', lastName: 'Rook' } // updated ]; const upsertMeta = searcher.upsertEntities( persons2, (e) => e.id, (e) => [e.firstName, e.lastName, `${e.firstName} ${e.lastName}`] ); log(upsertMeta); /* { "entries": { "numberOfTerms": 12, "upsertDuration": 0, ... } } */ const result2 = searcher.getMatches(new fuzzySearch.Query('allie')); log(result2); /* { "matches": [ { "entity": { "id": 23501, "firstName": "Allie", "lastName": "King" }, "quality": 3, "matchedString": "Allie" } ], "query": { "string": "allie", "topN": 10, "searchers": [ { "type": "fuzzy", "minQuality": 0.3 }, { "type": "substring", "minQuality": 0 }, { "type": "prefix", "minQuality": 0 } ] }, "meta": { "entries": { "queryDuration": 0 } } } */ ``` -------------------------------- ### TypeScript: Search Matches with FuzzySearcher Source: https://context7.com/m31coding/fuzzy-search/llms.txt Demonstrates how to search for entities using the fuzzy-search library in TypeScript, handling typos and configuring searcher types. Includes examples of basic searching, using substring and prefix searchers, and adjusting quality thresholds. ```typescript import * as fuzzySearch from '@m31coding/fuzzy-search'; const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); searcher.indexEntities( [ { id: 1, firstName: 'Alice', lastName: 'King' }, { id: 2, firstName: 'Alicia', lastName: 'Queen' }, { id: 3, firstName: 'Bob', lastName: 'Bishop' } ], (p) => p.id, (p) => [p.firstName, p.lastName] ); // Basic search with typo handling const query1 = new fuzzySearch.Query('alice kign', 10); const result1 = searcher.getMatches(query1); console.log('Query:', result1.query.string); result1.matches.forEach(match => { console.log(` ${match.entity.firstName} ${match.entity.lastName} - Quality: ${match.quality.toFixed(3)} - Matched: "${match.matchedString}"`); }); // Output: // Query: alice kign // Alice King - Quality: 1.000 - Matched: "alice" // Alice King - Quality: 0.791 - Matched: "king" // Alicia Queen - Quality: 0.842 - Matched: "alicia" // Return all matches const query2 = new fuzzySearch.Query('ali', Infinity); const result2 = searcher.getMatches(query2); console.log('Total matches:', result2.matches.length); // Use only substring and prefix searchers const query3 = new fuzzySearch.Query('ali', 10, [ new fuzzySearch.SubstringSearcher(0), new fuzzySearch.PrefixSearcher(0) ]); const result3 = searcher.getMatches(query3); // Adjust quality thresholds const query4 = new fuzzySearch.Query('ali', 10, [ new fuzzySearch.FuzzySearcher(0.5), // Only high-quality fuzzy matches new fuzzySearch.SubstringSearcher(0.2) ]); const result4 = searcher.getMatches(query4); console.log('High-quality matches:', result4.matches.length); ``` -------------------------------- ### Create and Execute Custom Search Queries with TypeScript Fuzzy Search Source: https://context7.com/m31coding/fuzzy-search/llms.txt Shows how to build queries with specific searchers and quality thresholds using the fuzzy-search library. The example indexes person entities and runs four different queries ranging from strict to lenient matching. Outputs match counts and qualities for each scenario. ```TypeScript import * as fuzzySearch from '@m31coding/fuzzy-search'; const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); searcher.indexEntities( [ { id: 1, firstName: 'Alexander', lastName: 'Smith' }, { id: 2, firstName: 'Alex', lastName: 'Johnson' }, { id: 3, firstName: 'Alexandra', lastName: 'Brown' } ], (p) => p.id, (p) => [p.firstName, p.lastName] ); // Query 1: Strict fuzzy matching only const strictQuery = new fuzzySearch.Query('alex', 10, [ new fuzzySearch.FuzzySearcher(0.7) // Only high-quality fuzzy matches ]); const strictResult = searcher.getMatches(strictQuery); console.log('Strict fuzzy:', strictResult.matches.length); // Query 2: Prefix matches preferred with no quality threshold const prefixQuery = new fuzzySearch.Query('alex', 10, [ new fuzzySearch.PrefixSearcher(0), new fuzzySearch.SubstringSearcher(0) ]); const prefixResult = searcher.getMatches(prefixQuery); console.log('Prefix matches:'); prefixResult.matches.forEach(m => { console.log(` ${m.entity.firstName}: ${m.quality.toFixed(3)}`); }); // Query 3: Lenient matching with all searchers const lenientQuery = new fuzzySearch.Query('alx', 10, [ new fuzzySearch.FuzzySearcher(0.2), new fuzzySearch.SubstringSearcher(0), new fuzzySearch.PrefixSearcher(0) ]); const lenientResult = searcher.getMatches(lenientQuery); console.log('Lenient matches:', lenientResult.matches.length); // Query 4: Return all matches without limit const allMatchesQuery = new fuzzySearch.Query('alex', Infinity); const allResult = searcher.getMatches(allMatchesQuery); console.log('All matches:', allResult.matches.length); ``` -------------------------------- ### Index and Search Simple String Arrays Using TypeScript Fuzzy Search Source: https://context7.com/m31coding/fuzzy-search/llms.txt Illustrates indexing a plain array of city names and performing fuzzy searches on it. The example covers basic search queries, typo handling, and updating the index with new entries. Results are printed with entity names and quality scores. ```TypeScript import * as fuzzySearch from '@m31coding/fuzzy-search'; // Index array of strings directly const cities = [ 'New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', 'Philadelphia', 'San Antonio' ]; const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); // Use string itself as both entity and ID const meta = searcher.indexEntities( cities, (city) => city, // ID = the string itself (city) => [city] // Search term = the string itself ); console.log('Indexed cities:', meta.get('entityCount')); // Search const result1 = searcher.getMatches(new fuzzySearch.Query('new', 5)); console.log('\nSearch "new":'); result1.matches.forEach(m => console.log(` ${m.entity} (${m.quality.toFixed(3)})`)); const result2 = searcher.getMatches(new fuzzySearch.Query('angeles', 5)); console.log('\nSearch "angeles":'); result2.matches.forEach(m => console.log(` ${m.entity} (${m.quality.toFixed(3)})`)); // Typo handling const result3 = searcher.getMatches(new fuzzySearch.Query('philadelfia', 5)); console.log('\nSearch "philadelfia" (typo):'); result3.matches.forEach(m => console.log(` ${m.entity} (${m.quality.toFixed(3)})`)); // Update: add new city searcher.upsertEntities(['San Diego'], (c) => c, (c) => [c]); console.log('\nTotal cities after upsert:', searcher.getEntities().length); ``` -------------------------------- ### Get All Indexed Search Terms Source: https://context7.com/m31coding/fuzzy-search/llms.txt Returns all unique search terms indexed across all entities. Duplicate terms from different entities appear only once. Useful for building autocomplete or tag clouds. ```TypeScript import * as fuzzySearch from '@m31coding/fuzzy-search'; const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); searcher.indexEntities( [ { id: 1, firstName: 'Alice', lastName: 'King' }, { id: 2, firstName: 'Bob', lastName: 'King' } ], (p) => p.id, (p) => [p.firstName, p.lastName] ); // Get all terms const allTerms = searcher.getTerms(); console.log('Total unique terms:', allTerms.length); // Output: 3 console.log('Terms:', allTerms); // Output: ["alice", "king", "bob"] // Note: "king" appears only once despite two entities having it // After adding more entities searcher.upsertEntities( [{ id: 3, firstName: 'Charlie', lastName: 'Knight' }], (p) => p.id, (p) => [p.firstName, p.lastName] ); const updatedTerms = searcher.getTerms(); console.log('After upsert:', updatedTerms.length); // Output: 5 console.log('New terms:', updatedTerms); // Includes "charlie" and "knight" ``` -------------------------------- ### Retrieve Search Terms for Entity ID Source: https://context7.com/m31coding/fuzzy-search/llms.txt Gets all searchable terms associated with a specific entity ID. Returns null if the entity doesn't exist. Terms are normalized (lowercased) during indexing. Requires a searcher instance with indexed entities. ```TypeScript import * as fuzzySearch from '@m31coding/fuzzy-search'; const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); searcher.indexEntities( [ { id: 1, firstName: 'Alice', lastName: 'King' }, { id: 2, firstName: 'Bob', lastName: 'Bishop' } ], (p) => p.id, (p) => [p.firstName, p.lastName, `${p.firstName} ${p.lastName}`, p.id.toString()] ); // Get terms for existing entity const aliceTerms = searcher.tryGetTerms(1); if (aliceTerms !== null) { console.log('Terms for Alice:', aliceTerms); // Output: ["alice", "king", "alice king", "1"] // Note: terms are normalized (lowercased) } // Try non-existent entity const unknownTerms = searcher.tryGetTerms(999); console.log('Unknown terms:', unknownTerms); // Output: null // Verify terms after upsert searcher.upsertEntities( [{ id: 1, firstName: 'Allie', lastName: 'King' }], (p) => p.id, (p) => [p.firstName, p.lastName] ); const updatedTerms = searcher.tryGetTerms(1); console.log('Updated terms:', updatedTerms); // Output: ["allie", "king"] ``` -------------------------------- ### Add Product API Source: https://context7.com/m31coding/fuzzy-search/llms.txt Adds a new product to the search index. The product data should be provided in the request body. ```APIDOC ## POST /api/products ### Description Adds a new product to the searchable index. The product details are sent in the request body. ### Method POST ### Endpoint /api/products ### Parameters #### Request Body - **product** (object) - Required - The product object to add. - **id** (string) - Required - Unique identifier for the product. - **name** (string) - Required - Name of the product. - **category** (string) - Required - Category of the product. - **price** (number) - Required - Price of the product. ### Response #### Success Response (201) - **message** (string) - "Product added" - **product** (object) - The added product object. #### Error Response (500) - **error** (string) - "Failed to add product" ### Request Example ```json { "id": "P004", "name": "Smartphone", "category": "Electronics", "price": 699 } ``` ### Response Example ```json { "message": "Product added", "product": { "id": "P004", "name": "Smartphone", "category": "Electronics", "price": 699 } } ``` ``` -------------------------------- ### Product Search API Source: https://context7.com/m31coding/fuzzy-search/llms.txt Allows searching for products using a fuzzy matching algorithm. You can specify a query string and an optional limit for the number of results. ```APIDOC ## GET /api/products/search ### Description Searches for products based on a given query string, allowing for typos and partial matches. Returns a list of matching products. ### Method GET ### Endpoint /api/products/search ### Parameters #### Query Parameters - **q** (string) - Required - The search query string. - **limit** (integer) - Optional - The maximum number of results to return. Defaults to 10. ### Response #### Success Response (200) - **query** (string) - The original search query. - **matches** (array) - An array of matching products. - **product** (object) - The matched product entity. - **quality** (number) - The quality score of the match. - **matchedOn** (string) - The string that was matched. - **meta** (object) - Metadata about the search. - **totalResults** (integer) - The total number of results found. - **queryTime** (number) - The time taken for the query in milliseconds. #### Error Response (400) - **error** (string) - "Query parameter "q" is required" #### Error Response (500) - **error** (string) - "Search failed" ### Request Example ``` GET http://localhost:3000/api/products/search?q=laptap&limit=5 ``` ### Response Example ```json { "query": "laptap", "matches": [ { "product": { "id": "P001", "name": "Laptop Computer", "category": "Electronics", "price": 999 }, "quality": 0.85, "matchedOn": "Laptop Computer" } ], "meta": { "totalResults": 1, "queryTime": 5 } } ``` ``` -------------------------------- ### Fuzzy Search Configuration (JSON) Source: https://github.com/m31coding/fuzzy-search/blob/main/src/regression-test/output/carcassonne-prefix.txt Defines the parameters for a fuzzy search query. It specifies the input string, the number of top results to return, and a list of searcher configurations including their types (fuzzy, substring, prefix) and minimum quality thresholds. ```json { "string": "carcasso", "topN": 10, "searchers": [ { "type": "fuzzy", "minQuality": 0.3 }, { "type": "substring", "minQuality": 0 }, { "type": "prefix", "minQuality": 0 } ] } ``` -------------------------------- ### Fuzzy Search Implementation (JavaScript, Python) Source: https://github.com/m31coding/fuzzy-search/blob/main/src/regression-test/output/boston-deletion.txt Demonstrates a complete fuzzy search pipeline using substring, prefix, and Levenshtein-based fuzzy searchers. Each searcher evaluates candidate entities; results are merged by maximum quality per entity, then sorted and limited to topN. ```javascript // Fuzzy search implementation in JavaScript // Utility: Levenshtein distance (optimal string alignment) function levenshtein(a, b) { if (a === b) return 0; const aLen = a.length, bLen = b.length; if (aLen === 0) return bLen; if (bLen === 0) return aLen; const v0 = new Array(bLen + 1); const v1 = new Array(bLen + 1); for (let i = 0; i <= bLen; i++) v0[i] = i; for (let i = 0; i < aLen; i++) { v1[0] = i + 1; for (let j = 0; j < bLen; j++) { const cost = a[i] === b[j] ? 0 : 1; v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost); } for (let j = 0; j <= bLen; j++) v0[j] = v1[j]; } return v1[bLen]; } function qualityFromDistance(dist, len) { if (len === 0) return 1; return Math.max(0, 1 - dist / len); } class Searcher { constructor(minQuality = 0) { this.minQuality = minQuality; } evaluate(query, entity) {} } class SubstringSearcher extends Searcher { evaluate(query, entity) { const q = query.toLowerCase(); const e = entity.toLowerCase(); if (!q || !e) return 0; return e.includes(q) ? 1 : 0; } } class PrefixSearcher extends Searcher { evaluate(query, entity) { const q = query.toLowerCase(); const e = entity.toLowerCase(); if (!q || !e) return 0; return e.startsWith(q) ? 1 : 0; } } class FuzzySearcher extends Searcher { evaluate(query, entity) { if (!query || !entity) return 0; const q = query.toLowerCase(); const e = entity.toLowerCase(); const dist = levenshtein(q, e); return qualityFromDistance(dist, Math.max(q.length, e.length)); } } function performFuzzySearch(entities, config) { const { string: query, topN = 10, searchers = [] } = config; const searcherMap = { substring: new SubstringSearcher(), prefix: new PrefixSearcher(), fuzzy: new FuzzySearcher() }; const activeSearchers = searchers .filter(s => searcherMap[s.type]) .map(s => ({ impl: searcherMap[s.type], minQuality: s.minQuality ?? 0 })); const scores = new Map(); for (const entity of entities) { let maxQ = 0; for (const s of activeSearchers) { const q = s.impl.evaluate(query, entity); if (q >= s.minQuality && q > maxQ) maxQ = q; } if (maxQ > 0) scores.set(entity, maxQ); } const results = Array.from(scores.entries()) .map(([entity, quality]) => ({ entity, matchedString: entity, quality })) .sort((a, b) => b.quality - a.quality || a.entity.localeCompare(b.entity)) .slice(0, topN); return results; } // Example usage with the provided data const entities = [ "Bosta", "Bosti", "Bosto", "Bost", "Bostan", "Bostel", "Boston", "Boston", "Boston", "Boston" ]; const config = { string: "bostn", topN: 10, searchers: [ { type: "fuzzy", minQuality: 0.3 }, { type: "substring", minQuality: 0 }, { type: "prefix", minQuality: 0 } ] }; const results = performFuzzySearch(entities, config); console.log("Rank Entity Matched String Quality"); results.forEach((r, idx) => { const rank = String(idx + 1).padStart(4, ' '); const entity = r.entity.padEnd(40, ' '); const matched = r.matchedString.padEnd(40, ' '); const quality = r.quality.toFixed(2).padStart(6, ' '); console.log(`${rank} ${entity}${matched}${quality}`); }); ``` ```python from __future__ import annotations from abc import ABC, abstractmethod from typing import List, Dict, Any, Iterable # Utility: Levenshtein distance (optimal string alignment) def levenshtein(a: str, b: str) -> int: if a == b: return 0 a_len, b_len = len(a), len(b) if a_len == 0: return b_len if b_len == 0: return a_len v0 = list(range(b_len + 1)) v1 = [0] * (b_len + 1) for i in range(a_len): v1[0] = i + 1 for j in range(b_len): cost = 0 if a[i] == b[j] else 1 v1[j + 1] = min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost) for j in range(b_len + 1): v0[j] = v1[j] return v1[b_len] def quality_from_distance(dist: int, max_len: int) -> float: if max_len == 0: return 1.0 return max(0.0, 1.0 - (dist / max_len)) class Searcher(ABC): def __init__(self, min_quality: float = 0.0): self.min_quality = min_quality @abstractmethod def evaluate(self, query: str, entity: str) -> float: ... class SubstringSearcher(Searcher): def evaluate(self, query: str, entity: str) -> float: if not query or not entity: return 0.0 q = query.lower() e = entity.lower() return 1.0 if q in e else 0.0 class PrefixSearcher(Searcher): def evaluate(self, query: str, entity: str) -> float: if not query or not entity: return 0.0 q = query.lower() e = entity.lower() return 1.0 if e.startswith(q) else 0.0 class FuzzySearcher(Searcher): def evaluate(self, query: str, entity: str) -> float: if not query or not entity: return 0.0 q = query.lower() e = entity.lower() dist = levenshtein(q, e) return quality_from_distance(dist, max(len(q), len(e))) def perform_fuzzy_search( entities: Iterable[str], config: Dict[str, Any] ) -> List[Dict[str, Any]]: query: str = config.get("string", "") top_n: int = int(config.get("topN", 10)) searchers_cfg: List[Dict[str, Any]] = config.get("searchers", []) searcher_map = { "substring": SubstringSearcher(), "prefix": PrefixSearcher(), "fuzzy": FuzzySearcher() } active = [] for s in searchers_cfg: s_type = s.get("type") if s_type in searcher_map: active.append({ "impl": searcher_map[s_type], "min_quality": s.get("minQuality", 0.0) }) scores = {} for entity in entities: max_q = 0.0 for s in active: q = s["impl"].evaluate(query, entity) if q >= s["min_quality"] and q > max_q: max_q = q if max_q > 0.0: scores[entity] = max_q results = [ {"entity": ent, "matchedString": ent, "quality": q} for ent, q in scores.items() ] results.sort(key=lambda r: (-r["quality"], r["entity"])) return results[:top_n] if __name__ == "__main__": entities = [ "Bosta", "Bosti", "Bosto", "Bost", "Bostan", "Bostel", "Boston", "Boston", "Boston", "Boston" ] config = { "string": "bostn", "topN": 10, "searchers": [ {"type": "fuzzy", "minQuality": 0.3}, {"type": "substring", "minQuality": 0.0}, {"type": "prefix", "minQuality": 0.0} ] } results = perform_fuzzy_search(entities, config) print("Rank Entity Matched String Quality") for idx, r in enumerate(results, start=1): rank = str(idx).rjust(4) entity = r["entity"].ljust(40) matched = r["matchedString"].ljust(40) quality = f"{r['quality']:.2f}".rjust(6) print(f"{rank} {entity}{matched}{quality}") ``` -------------------------------- ### TypeScript Express.js REST API for Fuzzy Search Source: https://context7.com/m31coding/fuzzy-search/llms.txt This snippet shows a complete Express.js server implementation in TypeScript that integrates fuzzy search. It defines product interfaces, initializes a fuzzy searcher, and sets up endpoints for searching, adding, and deleting products. It relies on the 'express' and '@m31coding/fuzzy-search' libraries. ```typescript import express from 'express'; import * as fuzzySearch from '@m31coding/fuzzy-search'; const app = express(); app.use(express.json()); interface Product { id: string; name: string; category: string; price: number; } let productSearcher: fuzzySearch.DynamicSearcher; // Initialize searcher with products function initializeSearcher(products: Product[]) { productSearcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); const meta = productSearcher.indexEntities( products, (p) => p.id, (p) => [p.name, p.category, `${p.name} ${p.category}`] ); console.log(`Indexed ${meta.get('entityCount')} products`); } // Search endpoint app.get('/api/products/search', (req, res) => { try { const query = req.query.q as string; const limit = parseInt(req.query.limit as string) || 10; if (!query) { return res.status(400).json({ error: 'Query parameter "q" is required' }); } const result = productSearcher.getMatches( new fuzzySearch.Query(query, limit) ); res.json({ query: query, matches: result.matches.map(m => ({ product: m.entity, quality: m.quality, matchedOn: m.matchedString })), meta: { totalResults: result.matches.length, queryTime: result.meta.get('queryTime') } }); } catch (error) { res.status(500).json({ error: 'Search failed' }); } }); // Add product endpoint app.post('/api/products', (req, res) => { try { const product: Product = req.body; productSearcher.upsertEntities( [product], (p) => p.id, (p) => [p.name, p.category] ); res.status(201).json({ message: 'Product added', product }); } catch (error) { res.status(500).json({ error: 'Failed to add product' }); } }); // Delete product endpoint app.delete('/api/products/:id', (req, res) => { try { const removed = productSearcher.removeEntity(req.params.id); if (removed) { res.json({ message: 'Product removed' }); } else { res.status(404).json({ error: 'Product not found' }); } } catch (error) { res.status(500).json({ error: 'Failed to remove product' }); } }); // Initialize with sample data const sampleProducts: Product[] = [ { id: 'P001', name: 'Laptop Computer', category: 'Electronics', price: 999 }, { id: 'P002', name: 'Wireless Mouse', category: 'Electronics', price: 29 }, { id: 'P003', name: 'Coffee Maker', category: 'Appliances', price: 79 } ]; initializeSearcher(sampleProducts); app.listen(3000, () => { console.log('API server running on http://localhost:3000'); }); // Example requests: // GET http://localhost:3000/api/products/search?q=laptap&limit=5 // GET http://localhost:3000/api/products/search?q=coffe // POST http://localhost:3000/api/products (with JSON body) // DELETE http://localhost:3000/api/products/P002 ``` -------------------------------- ### Create Custom Searcher Configuration in TypeScript Source: https://context7.com/m31coding/fuzzy-search/llms.txt Creates a custom searcher configuration for multilingual support and specialized fuzzy matching. Configures character normalization for non-Latin scripts, adjusts n-gram size and penalty settings, and demonstrates indexing product data. Shows how to search with typo tolerance for accented characters. ```typescript import * as fuzzySearch from '@m31coding/fuzzy-search'; // Create custom configuration const config = fuzzySearch.Config.createDefaultConfig(); // Allow all characters (for Arabic, Cyrillic, Han, etc.) config.normalizerConfig.allowCharacter = (_c) => true; // Customize fuzzy search parameters config.fuzzySearchConfig.ngramN = 2; // Use 2-grams instead of 3-grams config.fuzzySearchConfig.inequalityPenalty = 0.1; // Higher penalty for non-exact matches // Use only specific searchers config.searcherTypes = [ fuzzySearch.SearcherType.Fuzzy, fuzzySearch.SearcherType.Prefix ]; // Create searcher with custom config const customSearcher = fuzzySearch.SearcherFactory.createSearcher(config); const products = [ { sku: 'A123', name: 'Café Latte', category: 'Beverages' }, { sku: 'B456', name: 'Crème Brûlée', category: 'Desserts' } ]; customSearcher.indexEntities( products, (p) => p.sku, (p) => [p.name, p.category] ); // Search with custom configuration applied const result = customSearcher.getMatches(new fuzzySearch.Query('cafe', 10)); console.log('Matches:', result.matches.length); // Finds "Café Latte" despite missing accent in query ``` -------------------------------- ### Offload fuzzy search indexing to Web Worker - TypeScript Source: https://context7.com/m31coding/fuzzy-search/llms.txt Demonstrates how to perform fuzzy search indexing in a web worker to prevent UI blocking. The main thread communicates with the worker to send data and receive results. Requires @m31coding/fuzzy-search library. ```typescript // main.ts - Main thread import * as fuzzySearch from '@m31coding/fuzzy-search'; let searcher: fuzzySearch.DynamicSearcher; function indexInWorker(persons: Person[]) { const worker = new Worker('./indexing-worker.js'); worker.onmessage = (event) => { const { mementoObjects, indexingMeta } = event.data; console.log('Indexing completed in worker:', indexingMeta.indexingTime); // Create empty searcher and load state searcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); const memento = new fuzzySearch.Memento(mementoObjects); searcher.load(memento); console.log('Searcher ready with', searcher.getEntities().length, 'entities'); // Now can search on main thread const result = searcher.getMatches(new fuzzySearch.Query('alice', 10)); console.log('Search results:', result.matches.length); worker.terminate(); }; worker.onerror = (error) => { console.error('Worker error:', error); }; // Send data to worker worker.postMessage({ persons }); } // indexing-worker.js - Worker thread self.onmessage = (event) => { const { persons } = event.data; // Import library (use importScripts for UMD or ES modules) importScripts('./node_modules/@m31coding/fuzzy-search/dist/fuzzy-search.umd.js'); const fuzzySearch = self.fuzzySearch; // Create and index searcher const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); const indexingMeta = searcher.indexEntities( persons, (p) => p.id, (p) => [p.firstName, p.lastName] ); // Serialize and send back const memento = new fuzzySearch.Memento(); searcher.save(memento); self.postMessage({ mementoObjects: memento.objects, indexingMeta: { indexingTime: indexingMeta.get('indexingTime'), entityCount: indexingMeta.get('entityCount') } }); }; ``` -------------------------------- ### TypeScript: Upsert Entities with FuzzySearcher Source: https://context7.com/m31coding/fuzzy-search/llms.txt Demonstrates updating existing entities and inserting new ones using the upsertEntities function in the fuzzy-search library. Shows how to update data, verify changes, and search for both old and new entities. ```typescript import * as fuzzySearch from '@m31coding/fuzzy-search'; const searcher = fuzzySearch.SearcherFactory.createDefaultSearcher(); // Initial indexing searcher.indexEntities( [ { id: 1, firstName: 'Alice', lastName: 'King' }, { id: 2, firstName: 'Bob', lastName: 'Bishop' } ], (p) => p.id, (p) => [p.firstName, p.lastName, `${p.firstName} ${p.lastName}`] ); // Update Alice's name and add new person const updatedPersons = [ { id: 1, firstName: 'Allie', lastName: 'King' }, // Updated { id: 3, firstName: 'Charlie', lastName: 'Knight' } // New ]; const upsertMeta = searcher.upsertEntities( updatedPersons, (p) => p.id, (p) => [p.firstName, p.lastName, `${p.firstName} ${p.lastName}`] ); console.log('Upsert complete:', upsertMeta.get('entityCount'), 'entities processed'); // Verify update const alice = searcher.tryGetEntity(1); console.log('Updated entity:', alice?.firstName); // Verify new entity const charlie = searcher.tryGetEntity(3); console.log('New entity:', charlie?.firstName); // Search includes both old and new entities const result = searcher.getMatches(new fuzzySearch.Query('knight', 10)); console.log('Found:', result.matches[0]?.entity.firstName); ``` -------------------------------- ### Fuzzy Search Configuration (JSON) Source: https://github.com/m31coding/fuzzy-search/blob/main/src/regression-test/output/tbilisi-deletion.txt Defines a fuzzy search configuration including search types (fuzzy, substring, prefix) and quality thresholds. It specifies the query string, the number of top results, and individual search parameters like minimum quality score. ```json { "string": "თბიისი", "topN": 10, "searchers": [ { "type": "fuzzy", "minQuality": 0.3 }, { "type": "substring", "minQuality": 0 }, { "type": "prefix", "minQuality": 0 } ] } ``` -------------------------------- ### Fuzzy Search Configuration (JavaScript) Source: https://github.com/m31coding/fuzzy-search/blob/main/README.md This snippet shows the default configuration for fuzzy search, including padding characters, n-gram size, a transformation function for n-grams, and a penalty for non-exact matches. The transformNgram function handles n-grams ending with a '$' by discarding them, sorts characters in n-grams without '$', and keeps others as is. ```javascript config.fuzzySearchConfig.paddingLeft = '$$'; config.fuzzySearchConfig.paddingRight = '!'; config.fuzzySearchConfig.paddingMiddle = '!$$'; config.fuzzySearchConfig.ngramN = 3; config.fuzzySearchConfig.transformNgram = (ngram) => ngram.endsWith('$') ? null : ngram.indexOf('$') === -1 ? ngram.split('').sort().join('') : ngram; config.fuzzySearchConfig.inequalityPenalty = 0.05; ``` -------------------------------- ### Configure Fuzzy Search Normalizer Source: https://github.com/m31coding/fuzzy-search/blob/main/README.md This JavaScript code configures the normalizer for the fuzzy search library. It sets up custom replacements, defines characters to be treated as spaces, and specifies allowed characters for normalization. This configuration affects how query strings and data terms are processed. ```javascript config.normalizerConfig.replacements = [fuzzySearch.LatinReplacements.Value]; let spaceEquivalentCharacters = new Set(['_', '-', '–', '/', ',', '\t']); config.normalizerConfig.treatCharacterAsSpace = (c) => spaceEquivalentCharacters.has(c); config.normalizerConfig.allowCharacter = (c) => { return fuzzySearch.StringUtilities.isAlphanumeric(c); }; ``` -------------------------------- ### Search Configuration Schema (JSON) Source: https://github.com/m31coding/fuzzy-search/blob/main/src/regression-test/output/boston-deletion.txt Defines the input structure for fuzzy search, including the query string, topN limit, and an array of searchers with types and minimum quality thresholds. Also includes an optional queryDuration field. ```json { "string": "bostn", "topN": 10, "searchers": [ { "type": "fuzzy", "minQuality": 0.3 }, { "type": "substring", "minQuality": 0 }, { "type": "prefix", "minQuality": 0 } ] } { "queryDuration": 2 } ```