Skip to main content
Glama
value-matcher.tsβ€’5.16 kB
/** * Value matching utilities for fuzzy matching field values * Uses string-similarity for intelligent suggestions when exact matches fail */ import * as stringSimilarity from 'string-similarity'; export interface ValueMatch { value: string; similarity: number; } export interface ValueMatchResult { exactMatch?: string; suggestions: ValueMatch[]; bestMatch?: ValueMatch; } /** * Configuration for value matching behavior */ export interface ValueMatchConfig { /** Minimum similarity score to consider a match (0-1) */ minSimilarity?: number; /** Maximum number of suggestions to return */ maxSuggestions?: number; /** Whether to use case-sensitive matching */ caseSensitive?: boolean; } const DEFAULT_CONFIG: ValueMatchConfig = { minSimilarity: 0.4, maxSuggestions: 3, caseSensitive: false, }; /** * Find the best matching values from a list of valid options * * @param searchValue - The value to search for * @param validValues - Array of valid values to match against * @param config - Optional configuration for matching behavior * @returns Match result with exact match or suggestions */ export function findBestValueMatch( searchValue: string, validValues: string[], config?: ValueMatchConfig ): ValueMatchResult { const mergedConfig = { ...DEFAULT_CONFIG, ...config }; // Normalize for comparison if not case sensitive const normalizedSearch = mergedConfig.caseSensitive ? searchValue : searchValue.toLowerCase(); // Check for exact match first const exactMatch = validValues.find((value) => { const normalizedValue = mergedConfig.caseSensitive ? value : value.toLowerCase(); return normalizedValue === normalizedSearch; }); if (exactMatch) { return { exactMatch, suggestions: [], bestMatch: { value: exactMatch, similarity: 1.0 }, }; } // No exact match, find similar values const matches = stringSimilarity.findBestMatch(searchValue, validValues); // Filter and sort suggestions by similarity const suggestions = matches.ratings .filter((rating) => rating.rating >= mergedConfig.minSimilarity!) .sort((a, b) => b.rating - a.rating) .slice(0, mergedConfig.maxSuggestions) .map((rating) => ({ value: rating.target, similarity: rating.rating, })); return { suggestions, bestMatch: suggestions[0], }; } /** * Format a value match error message with suggestions * * @param fieldName - The field being searched * @param searchValue - The value that was searched for * @param matchResult - The match result with suggestions * @returns Formatted error message */ export function formatValueMatchError( fieldName: string, searchValue: string, matchResult: ValueMatchResult ): string { let message = `'${searchValue}' not found as '${fieldName}'.`; if (matchResult.bestMatch && matchResult.bestMatch.similarity >= 0.7) { message += ` Did you mean '${matchResult.bestMatch.value}'?`; } else if (matchResult.suggestions.length > 0) { message += ' Did you mean one of these?'; matchResult.suggestions.forEach((suggestion) => { message += `\n - ${suggestion.value} (${Math.round( suggestion.similarity * 100 )}% match)`; }); } return message; } /** * Check if a value contains a partial match * Useful for "contains" type searches * * @param searchValue - The partial value to search for * @param validValues - Array of valid values to check * @param caseSensitive - Whether to use case-sensitive matching * @returns Array of values that contain the search value */ export function findPartialMatches( searchValue: string, validValues: string[], caseSensitive: boolean = false ): string[] { const normalizedSearch = caseSensitive ? searchValue : searchValue.toLowerCase(); return validValues.filter((value) => { const normalizedValue = caseSensitive ? value : value.toLowerCase(); return normalizedValue.includes(normalizedSearch); }); } /** * Get value suggestions for a field * This could be extended to cache or fetch valid values from Attio * * @param fieldSlug - The field slug (e.g., 'b2b_segment', 'categories') * @param searchValue - The value being searched for * @returns Array of suggested values or null if no suggestions available */ export async function getValueSuggestions( fieldSlug: string, searchValue: string ): Promise<ValueMatch[] | null> { // This is where we could integrate with Attio API to fetch valid values // For now, we'll use known values from user configuration // Note: We intentionally don't hardcode custom fields here // Custom field values should be discovered dynamically from Attio API // or configured through user.json mapping const knownValues: Record<string, string[]> = { // Only include standard Attio fields with known valid options // Custom fields like b2b_segment should be handled via user.json mapping }; const validValues = knownValues[fieldSlug]; if (!validValues) { return null; } const matchResult = findBestValueMatch(searchValue, validValues); return matchResult.suggestions; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kesslerio/attio-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server