{"version":3,"sources":["../../src/vanilla/builders/searcher.builder.ts","../../src/vanilla/constants.ts","../../src/vanilla/helpers/isValidBoundary.ts","../../src/vanilla/helpers/sorters.ts","../../src/vanilla/scores/consecutive.ts","../../src/vanilla/algorithm/findFuzzyMatches.ts","../../src/vanilla/getScore/getScore.ts","../../src/vanilla/helpers/filters.ts","../../src/vanilla/helpers/transformers.ts","../../src/vanilla/normalizeText.ts","../../src/vanilla/fuzzy.ts","../../src/vanilla/single-fuzzy.ts"],"sourcesContent":["import type { FuzzyResponse, Result } from \"../types\";\n\nexport class FuzzyBuilder<T> {\n\tprivate results: Array<Result<T>>;\n\tprivate time: number;\n\tprivate normalizedQuery: string;\n\n\tconstructor({\n\t\tresults = [],\n\t\tstartTime = Date.now(),\n\t\tendTime = Date.now(),\n\t\tnormalizedQuery = \"\",\n\t}: {\n\t\tresults?: Array<Result<T>>;\n\t\tstartTime?: number;\n\t\tendTime?: number;\n\t\tnormalizedQuery?: string;\n\t}) {\n\t\tthis.time = endTime - startTime;\n\t\tthis.results = results;\n\t\tthis.normalizedQuery = normalizedQuery;\n\t}\n\n\tpublic build(): FuzzyResponse<T> {\n\t\treturn {\n\t\t\tresults: this.results,\n\t\t\tlength: this.results.length,\n\t\t\ttime: this.time,\n\t\t\tnormalizedQuery: this.normalizedQuery,\n\t\t\thasExactMatch:\n\t\t\t\tthis.results.filter((result) => result.score === 0).length > 0,\n\t\t\tbestMatch: this.results[0] ?? null,\n\t\t\thasResults: this.results.length > 0,\n\t\t};\n\t}\n}\n","export const defaults = {\n\tlimit: Number.MAX_SAFE_INTEGER,\n\tmaxScore: Number.MAX_SAFE_INTEGER,\n\tdebug: false,\n};\n","const validWordBoundaries = new Set(\"  []()-–—'\\\"“”\".split(\"\"));\n\nexport function isValidWordBoundary(character: string): boolean {\n\treturn validWordBoundaries.has(character);\n}","import type { Result, Range } from \"../types\";\n\nexport const sortByScore = <T>(a: Result<T>, b: Result<T>): number =>\n\ta.score - b.score;\n\ntype SortRangeTuple = (a: Range, b: Range) => number;\nexport const sortRangeTuple: SortRangeTuple = (a, b): number => a[0] - b[0];\n","import type { HighlightRanges } from \"../types\";\n\nexport function scoreConsecutiveLetters(\n\tindices: HighlightRanges,\n\tnormalizedItem: string,\n): [number, HighlightRanges] | null {\n\t// Score: 2 + sum of chunk scores\n\t// Chunk scores:\n\t// - 0.2 for a full word\n\t// - 0.4 for chunk starting at beginning of word\n\t// - 0.8 for chunk in the middle of the word (if >=3 characters)\n\t// - 1.6 for chunk in the middle of the word (if 1 or 2 characters)\n\tlet score = 2;\n\n\tfor (const [firstIdx, lastIdx] of indices) {\n\t\tconst chunkLength = lastIdx - firstIdx + 1;\n\t\tconst isStartOfWord =\n\t\t\tfirstIdx === 0 ||\n\t\t\tnormalizedItem[firstIdx] === \" \" ||\n\t\t\tnormalizedItem[firstIdx - 1] === \" \";\n\t\tconst isEndOfWord =\n\t\t\tlastIdx === normalizedItem.length - 1 ||\n\t\t\tnormalizedItem[lastIdx] === \" \" ||\n\t\t\tnormalizedItem[lastIdx + 1] === \" \";\n\t\tconst isFullWord = isStartOfWord && isEndOfWord;\n\t\t// DEBUG:\n\t\t// console.log({\n\t\t//   firstIdx,\n\t\t//   lastIdx,\n\t\t//   chunkLength,\n\t\t//   isStartOfWord,\n\t\t//   isEndOfWord,\n\t\t//   isFullWord,\n\t\t//   before: normalizedItem[firstIdx - 1],\n\t\t//   after: normalizedItem[lastIdx + 1],\n\t\t// })\n\t\tif (isFullWord) {\n\t\t\tscore += 0.2;\n\t\t} else if (isStartOfWord) {\n\t\t\tscore += 0.4;\n\t\t} else if (chunkLength >= 3) {\n\t\t\tscore += 0.8;\n\t\t} else {\n\t\t\tscore += 1.6;\n\t\t}\n\t}\n\n\treturn [score, indices];\n}\n","import { isValidWordBoundary } from \"../helpers/isValidBoundary\";\nimport { scoreConsecutiveLetters } from \"../scores/consecutive\";\nimport type { HighlightRanges } from \"../types\";\n\nexport function findFuzzyMatches(\n\tnormalizedItem: string,\n\tnormalizedQuery: string,\n): [number, HighlightRanges] | null {\n\tconst normalizedItemLen = normalizedItem.length;\n\n\t// Match by consecutive letters, but only match beginnings of words or chunks of 3+ letters\n\t// Note that there may be multiple valid ways in which such matching can be done, and we'll only\n\t// match each chunk to the first one found that matches these criteria. It's not perfect as it's\n\t// possible that later chunks will fail to match while there's a better match, for example:\n\t// - query: ABC\n\t// - item: A xABC\n\t//         ^___xx (no match)\n\t//         ___^^^ (better match)\n\t// But we want to limit the algorithmic complexity and this should generally work.\n\n\tconst indices: HighlightRanges = [];\n\tlet queryIdx = 0;\n\tlet queryChar = normalizedQuery[queryIdx];\n\tlet chunkFirstIdx = -1;\n\tlet chunkLastIdx = -2;\n\n\t// eslint-disable-next-line no-constant-condition\n\twhile (true) {\n\t\t// Find match for first letter of chunk\n\t\tconst idx = normalizedItem.indexOf(queryChar, chunkLastIdx + 1);\n\t\tif (idx === -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// Check if chunk starts at word boundary\n\t\tif (idx === 0 || isValidWordBoundary(normalizedItem[idx - 1])) {\n\t\t\tchunkFirstIdx = idx;\n\t\t} else {\n\t\t\t// Else, check if chunk is at least 3+ letters\n\t\t\tconst queryCharsLeft = normalizedQuery.length - queryIdx;\n\t\t\tconst itemCharsLeft = normalizedItem.length - idx;\n\t\t\tconst minimumChunkLen = Math.min(3, queryCharsLeft, itemCharsLeft);\n\t\t\tconst minimumQueryChunk = normalizedQuery.slice(\n\t\t\t\tqueryIdx,\n\t\t\t\tqueryIdx + minimumChunkLen,\n\t\t\t);\n\n\t\t\tif (\n\t\t\t\tnormalizedItem.slice(idx, idx + minimumChunkLen) === minimumQueryChunk\n\t\t\t) {\n\t\t\t\tchunkFirstIdx = idx;\n\t\t\t} else {\n\t\t\t\t// Move index to continue search for valid chunk\n\t\t\t\tchunkLastIdx += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t// We have first index of a valid chunk, find its last index\n\t\t// TODO: We could micro-optimize by setting chunkLastIdx earlier if we already know it's len 3 or more\n\t\tfor (\n\t\t\tchunkLastIdx = chunkFirstIdx;\n\t\t\tchunkLastIdx < normalizedItemLen;\n\t\t\tchunkLastIdx += 1\n\t\t) {\n\t\t\tif (normalizedItem[chunkLastIdx] !== queryChar) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tqueryIdx += 1;\n\t\t\tqueryChar = normalizedQuery[queryIdx];\n\t\t}\n\n\t\t// Add chunk to indices\n\t\tchunkLastIdx -= 1; // decrement as we've broken out of loop on non-matching char\n\t\tindices.push([chunkFirstIdx, chunkLastIdx]);\n\n\t\t// Check if we're done\n\t\tif (queryIdx === normalizedQuery.length) {\n\t\t\treturn scoreConsecutiveLetters(indices, normalizedItem);\n\t\t}\n\t}\n\n\treturn null;\n}\n","import { isValidWordBoundary } from \"../helpers/isValidBoundary\";\nimport { sortRangeTuple } from \"../helpers/sorters\";\nimport { findFuzzyMatches } from \"../algorithm/findFuzzyMatches\";\nimport type { HighlightRanges, Range } from \"../types\";\n\nexport function getFuzzyMatchScore(\n\titem: string,\n\tnormalizedItem: string,\n\titemWords: Set<string>,\n\tquery: string,\n\tnormalizedQuery: string,\n\tqueryWords: string[],\n): [number, HighlightRanges] | null {\n\t// quick matches\n\tif (item === query) {\n\t\treturn [0, [[0, item.length - 1]]];\n\t}\n\n\tconst queryLen = query.length;\n\tconst normalizedItemLen = normalizedItem.length;\n\tconst normalizedQueryLen = normalizedQuery.length;\n\n\tif (normalizedItem === normalizedQuery) {\n\t\treturn [0.1, [[0, normalizedItemLen - 1]]];\n\t}\n\tif (normalizedItem.startsWith(normalizedQuery)) {\n\t\treturn [0.5, [[0, normalizedQueryLen - 1]]];\n\t}\n\n\t// contains query (starting at word boundary)\n\tconst exactContainsIdx = item.indexOf(query);\n\tif (\n\t\texactContainsIdx > -1 &&\n\t\tisValidWordBoundary(item[exactContainsIdx - 1])\n\t) {\n\t\treturn [0.9, [[exactContainsIdx, exactContainsIdx + queryLen - 1]]];\n\t}\n\n\t/**\n\t * Finds the starting index of the first occurrence of the normalized query string\n\t * within the normalized item string. If the query is not found, it returns -1.\n\t *\n\t * @type {number}\n\t */\n\tconst containsIdx: number = normalizedItem.indexOf(normalizedQuery);\n\n\t// Match by words included\n\t// Score: 1.5 + 0.2*words (so that it's better than two non-word chunks)\n\tconst queryWordCount = queryWords.length;\n\tif (queryWordCount > 1) {\n\t\tif (queryWords.every((word) => itemWords.has(word))) {\n\t\t\tconst score = 1.5 + queryWordCount * 0.2;\n\t\t\treturn [\n\t\t\t\tscore,\n\t\t\t\tqueryWords\n\t\t\t\t\t.map((word) => {\n\t\t\t\t\t\tconst wordIndex = normalizedItem.indexOf(word);\n\t\t\t\t\t\treturn [wordIndex, wordIndex + word.length - 1] as Range;\n\t\t\t\t\t})\n\t\t\t\t\t.sort(sortRangeTuple),\n\t\t\t];\n\t\t}\n\t}\n\n\t// Contains query (at any position)\n\tif (containsIdx > -1) {\n\t\treturn [2, [[containsIdx, containsIdx + queryLen - 1]]];\n\t}\n\n\treturn findFuzzyMatches(normalizedItem, normalizedQuery);\n}\n","import type { MapResult, Result } from \"../types\";\n\n/**\n * Filters and sorts an array of results based on a maximum score and a limit.\n *\n * @template T - The type of the result data.\n * @param results - An array of results to filter and sort. Each result must have a `score` property.\n * @param maxScore - The maximum score threshold. Results with a score greater than this value will be excluded.\n * @param limit - The maximum number of results to return after filtering and sorting.\n * @returns A new array of results that are filtered by the maximum score, sorted in ascending order of score, and limited to the specified number of items.\n */\nexport const filterResults = <T>(\n\tresults: Result<T>[],\n\tmaxScore: number,\n\tlimit: number,\n): Result<T>[] => {\n\treturn results\n\t\t.filter((result) => result.score <= maxScore)\n\t\t.sort((a, b) => a.score - b.score)\n\t\t.slice(0, limit);\n};\n\n/**\n * Transforms an array of `Result<T>` objects into an array of `Result<U>` objects\n * by applying an optional mapping function to the `item` property of each result.\n *\n * @template T - The type of the input items in the results.\n * @template U - The type of the output items in the results after mapping.\n *\n * @param results - An array of `Result<T>` objects to be transformed.\n * @param mapResult - An optional function that maps an item of type `T` to type `U`.\n *                         If not provided, the original results are returned as `Result<U>[]`.\n *\n * @returns An array of `Result<U>` objects. If `mapResult` is not provided, the original\n *          results are returned with their type cast to `Result<U>[]`.\n */\nexport const getMapResultItem = <T, U>(\n\tresults: Result<T>[],\n\tmapResult?: MapResult<T, U>,\n): Result<U>[] => {\n\t// if mapResult is not provided, return the original results\n\tif (!mapResult) {\n\t\treturn results as unknown as Result<U>[];\n\t}\n\n\t// otherwise, map the results using the provided function\n\treturn results.map(({ item, ...rest }, i, array) => ({\n\t\t...rest,\n\t\titem: mapResult(item, i, array),\n\t}));\n};\n\nexport const parseResults = <T, U>(\n\tresults: Result<T>[],\n\tmaxScore: number,\n\tlimit: number,\n\tmapResult?: MapResult<T, U>,\n): Result<U>[] => {\n\tconst filteredResults = filterResults(results, maxScore, limit);\n\n\treturn getMapResultItem(filteredResults, mapResult);\n};\n","import { FuzzyBuilder } from \"../builders/searcher.builder\";\nimport type { FuzzyResponse, MapResult, Result } from \"../types\";\nimport { parseResults } from \"./filters\";\n\nexport const unmatchedItem = <T>(item: T): Result<T> => {\n\treturn {\n\t\titem,\n\t\tscore: Number.MAX_SAFE_INTEGER,\n\t\tmatches: [],\n\t};\n};\n\nexport const unmatchedItems = <T>(items: T[]): Result<T>[] => {\n\treturn items.map(unmatchedItem);\n};\n\nexport const emptyResponse = <T>(query = \"\"): FuzzyResponse<T> => {\n\tconst startTime = Date.now();\n\n\treturn new FuzzyBuilder<T>({\n\t\tresults: [],\n\t\tstartTime,\n\t\tendTime: startTime,\n\t\tnormalizedQuery: query,\n\t}).build();\n};\n\nexport const unsortedResponse = <T, U = T>(\n\titems: T[],\n\tmaxScore: number,\n\tlimit: number,\n\tmapResult?: MapResult<T, U>,\n\tnormalizedQuery = \"\",\n): FuzzyResponse<U> => {\n\tconst itemsWithResult = unmatchedItems(items);\n\tconst results = parseResults(itemsWithResult, maxScore, limit, mapResult);\n\treturn new FuzzyBuilder<U>({\n\t\tresults,\n\t\tnormalizedQuery,\n\t}).build();\n};\n","// @flow\n\n// biome-ignore lint/suspicious/noMisleadingCharacterClass: <explanation>\nconst diacriticsRegex = /[\\u0300-\\u036f]/g;\nconst regexŁ = /ł/g;\nconst regexÑ = /ñ/g;\n\n/**\n * Normalizes text so that it's suitable to comparisons, sorting, search, etc. by:\n * - turning into lowercase\n * - removing diacritics\n * - removing extra whitespace\n */\nexport default function normalizeText(string: string): string {\n\treturn (\n\t\tstring\n\t\t\t.toLowerCase()\n\t\t\t// get rid of diacritics\n\t\t\t// https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript\n\t\t\t// yeah, it's not perfect, but 97% is good enough and it doesn't seem worth it to add in a whole\n\t\t\t// library for this\n\t\t\t.normalize(\"NFD\")\n\t\t\t.replace(diacriticsRegex, \"\")\n\t\t\t// fix letters that unicode considers separate, not letters with diacritics\n\t\t\t.replace(regexŁ, \"l\")\n\t\t\t.replace(regexÑ, \"n\")\n\t\t\t.trim()\n\t);\n}\n","import { FuzzyBuilder } from \"./builders/searcher.builder\";\nimport { defaults } from \"./constants\";\nimport { getFuzzyMatchScore } from \"./getScore/getScore\";\nimport { parseResults } from \"./helpers/filters\";\nimport { emptyResponse } from \"./helpers/transformers\";\nimport normalizeText from \"./normalizeText\";\nimport type {\n\tFuzzy,\n\tFuzzyOptions,\n\tFuzzyResponse,\n\tMatches,\n\tResult,\n} from \"./types\";\n/*\nBased on https://github.com/Nozbe/microfuzz, but with some changes as direct tailwind support or more customization. Thanks to Nozbe for the original idea.\n\nGeneral idea:\n- case-insensitive\n- diacritics-insensitive\n- works with Latin script, Cyrillic, rudimentary CJK support\n- limited fuzzing: matches query letters in order, but they don't have to be consecutive\n  (but no or very limited transposition or removals are allowed - they cause more confusion than help)\n- no FTS-style stemming, soundex, levenstein, autocorrect - query can be lazy, but not sloppy\n- sort by how well text matches the query\n\nThink VS Code/Dash-like search, not Google-like search\n\n## Sorting\n\nResults are sorted in roughly this order:\n\n1. [0]    Exact match (found === query)\n2. [0.1]  Full match (like exact, but case&diacritics-insensitive)\n3. [0.5]  \"Starts with\" match\n4. [0.9]  Contains query (starting at word boundary) exactly\n5. [1]    Contains query (starting at word boundary)\n6. [1.5+] Contains query words (space separated) in any order\n7. [2]    Contains query\n8. [2+]   Contains letters -- the fewer chunks, the better\n\nNote:\n- Lower score is better (think \"error level\")\n- Exact scoring values are not an API contract, can change between releases\n- Secondary sort criteria (for equal fuzzy sort) is input order\n\n*/\n\n/**\n * Performs a fuzzy search on a collection of items and returns a function that can be used to query the collection.\n *\n * @template T - The type of the items in the collection.\n * @template U - The type of the items in the search results (defaults to `T`).\n *\n * @param collection - The array of items to search through.\n * @param options - Configuration options for the fuzzy search.\n * @param options.getKey - A function that extracts an array of strings (keys) from each item in the collection.\n *                         If not provided, the items themselves are converted to strings.\n * @param options.debug - If `true`, logs debug information about the search process to the console.\n * @param options.limit - The maximum number of results to return. Defaults to `Number.MAX_SAFE_INTEGER`.\n * @param options.maxScore - The maximum score a result can have to be included in the results. Defaults to `100`.\n * @param options.mapResult - A function to map each result item to a different type.\n *\n * @returns A function that takes a query string and returns a `FuzzyResponse` containing the search results.\n *\n * @example\n * ```typescript\n * const collection = ['apple', 'banana', 'cherry'];\n * const search = fuzzySearch(collection, { limit: 2 });\n * const results = search('app');\n * console.log(results);\n * ```\n */\nexport function fuzzy<T, U = T>(\n\tcollection: T[],\n\toptions: FuzzyOptions<T, U> = {},\n): Fuzzy<U> {\n\tconst {\n\t\tgetKey,\n\t\tdebug = defaults.debug,\n\t\tlimit = defaults.limit,\n\t\tmaxScore = defaults.maxScore,\n\t\tmapResult,\n\t} = options;\n\n\tconst normalizedTexts: [T, [string, string, Set<string>][]][] =\n\t\tcollection.map((element: T) => {\n\t\t\tconst texts = getKey ? getKey(element) : [String(element)];\n\n\t\t\tconst preprocessedTexts: [string, string, Set<string>][] = texts.map(\n\t\t\t\t(text) => {\n\t\t\t\t\tconst item = String(text);\n\t\t\t\t\tconst normalizedItem = normalizeText(item);\n\t\t\t\t\tconst itemWords = new Set(normalizedItem.split(\" \"));\n\n\t\t\t\t\treturn [item, normalizedItem, itemWords];\n\t\t\t\t},\n\t\t\t);\n\n\t\t\treturn [element, preprocessedTexts];\n\t\t});\n\n\tconst res: Fuzzy<U> = (query: string): FuzzyResponse<U> => {\n\t\tconst startTime = Date.now();\n\t\tconst results: Array<Result<T>> = [];\n\t\tconst normalizedQuery = normalizeText(query);\n\n\t\tif (!normalizedQuery.length) {\n\t\t\treturn emptyResponse(normalizedQuery);\n\t\t}\n\t\tconst queryWords = normalizedQuery.split(\" \");\n\n\t\tfor (const [element, texts] of normalizedTexts) {\n\t\t\tlet bestScore = Number.MAX_SAFE_INTEGER;\n\t\t\tconst matches: Matches = [];\n\n\t\t\tfor (const [item, normalizedItem, itemWords] of texts) {\n\t\t\t\tconst result = getFuzzyMatchScore(\n\t\t\t\t\titem,\n\t\t\t\t\tnormalizedItem,\n\t\t\t\t\titemWords,\n\t\t\t\t\tquery,\n\t\t\t\t\tnormalizedQuery,\n\t\t\t\t\tqueryWords,\n\t\t\t\t);\n\n\t\t\t\tif (result) {\n\t\t\t\t\tbestScore = Math.min(bestScore, result[0]);\n\t\t\t\t\tmatches.push(result[1]);\n\t\t\t\t} else {\n\t\t\t\t\tmatches.push(null);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (bestScore < Number.MAX_SAFE_INTEGER) {\n\t\t\t\tresults.push({ item: element, score: bestScore, matches });\n\t\t\t}\n\t\t}\n\n\t\t// `results` is the array of results, sorted by score, now we filter and transform it\n\n\t\tconst parsedResults = parseResults(results, maxScore, limit, mapResult);\n\n\t\tconst endTime = Date.now();\n\n\t\tconst built = new FuzzyBuilder<U>({\n\t\t\tresults: parsedResults,\n\t\t\tstartTime,\n\t\t\tendTime,\n\t\t\tnormalizedQuery,\n\t\t}).build();\n\n\t\tif (debug) {\n\t\t\tconsole.groupCollapsed(\"Fuzzy Search\");\n\t\t\tconsole.log(\"Query:\", query);\n\t\t\tconsole.log(\"Normalized Query:\", normalizedQuery);\n\t\t\tconsole.log(\"Results Length:\", built.results.length);\n\t\t\tconsole.table(built.results);\n\t\t\tconsole.log(\"Preprocessed Collection:\", normalizedTexts);\n\t\t\tconsole.log(\"Time:\", built.time, \"ms\");\n\t\t\tconsole.groupEnd();\n\t\t}\n\n\t\treturn built;\n\t};\n\n\treturn res;\n}\n","import { getFuzzyMatchScore } from \"./getScore/getScore\";\nimport normalizeText from \"./normalizeText\";\nimport type { Result } from \"./types\";\n\n/**\n * Finds a fuzzy match between a given text and a query string.\n *\n * Runs a one-off fuzzy search matching on `text` against `queryText`.\n *\n * Use `fuzzyMatch` whenever you have a single item to search.\n *\n * This function normalizes both the input text and query, splits them into words,\n * and calculates a fuzzy match score. If a match is found, it returns the result\n * containing the original text, the match score, and the matched segments.\n *\n * @param text - The input text to search for a fuzzy match.\n * @param query - The query string to match against the input text.\n * @returns A `Result<string>` object containing the match details if a match is found,\n *          or `null` if no match is found.\n */\nexport function singleFuzzy(text: string, query: string): Result<string> | null {\n\t\tconst normalizedQuery = normalizeText(query);\n\t\tconst queryWords = normalizedQuery.split(\" \");\n\n\t\tconst normalizedText = normalizeText(text);\n\t\tconst itemWords = new Set(normalizedText.split(\" \"));\n\n\t\tconst result = getFuzzyMatchScore(\n\t\t\ttext,\n\t\t\tnormalizedText,\n\t\t\titemWords,\n\t\t\tquery,\n\t\t\tnormalizedQuery,\n\t\t\tqueryWords,\n\t\t);\n\t\tif (result) {\n\t\t\treturn { item: text, score: result[0], matches: [result[1]] };\n\t\t}\n\t\treturn null;\n\t}\n"],"mappings":"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","names":["FuzzyBuilder","results","startTime","endTime","normalizedQuery","result","defaults","validWordBoundaries","isValidWordBoundary","character","sortRangeTuple","a","b","scoreConsecutiveLetters","indices","normalizedItem","score","firstIdx","lastIdx","chunkLength","isStartOfWord","isEndOfWord","findFuzzyMatches","normalizedItem","normalizedQuery","normalizedItemLen","indices","queryIdx","queryChar","chunkFirstIdx","chunkLastIdx","idx","isValidWordBoundary","queryCharsLeft","itemCharsLeft","minimumChunkLen","minimumQueryChunk","scoreConsecutiveLetters","getFuzzyMatchScore","item","normalizedItem","itemWords","query","normalizedQuery","queryWords","queryLen","normalizedItemLen","normalizedQueryLen","exactContainsIdx","isValidWordBoundary","containsIdx","queryWordCount","word","wordIndex","sortRangeTuple","findFuzzyMatches","filterResults","results","maxScore","limit","result","a","b","getMapResultItem","mapResult","item","rest","i","array","parseResults","filteredResults","emptyResponse","query","startTime","FuzzyBuilder","diacriticsRegex","regexŁ","regexÑ","normalizeText","string","fuzzy","collection","options","getKey","debug","defaults","limit","maxScore","mapResult","normalizedTexts","element","preprocessedTexts","text","item","normalizedItem","normalizeText","itemWords","query","startTime","results","normalizedQuery","emptyResponse","queryWords","texts","bestScore","matches","result","getFuzzyMatchScore","parsedResults","parseResults","endTime","built","FuzzyBuilder","singleFuzzy","text","query","normalizedQuery","normalizeText","queryWords","normalizedText","itemWords","result","getFuzzyMatchScore"]}