{"version":3,"sources":["../../../src/vanilla/fuzzy.ts"],"sourcesContent":["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"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AACzB,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,OAAO,mBAAmB;AAmEnB,SAAS,MACf,YACA,UAA8B,CAAC,GACpB;AACX,QAAM;AAAA,IACL;AAAA,IACA,QAAQ,SAAS;AAAA,IACjB,QAAQ,SAAS;AAAA,IACjB,WAAW,SAAS;AAAA,IACpB;AAAA,EACD,IAAI;AAEJ,QAAM,kBACL,WAAW,IAAI,CAAC,YAAe;AAC9B,UAAM,QAAQ,SAAS,OAAO,OAAO,IAAI,CAAC,OAAO,OAAO,CAAC;AAEzD,UAAM,oBAAqD,MAAM;AAAA,MAChE,CAAC,SAAS;AACT,cAAM,OAAO,OAAO,IAAI;AACxB,cAAM,iBAAiB,cAAc,IAAI;AACzC,cAAM,YAAY,IAAI,IAAI,eAAe,MAAM,GAAG,CAAC;AAEnD,eAAO,CAAC,MAAM,gBAAgB,SAAS;AAAA,MACxC;AAAA,IACD;AAEA,WAAO,CAAC,SAAS,iBAAiB;AAAA,EACnC,CAAC;AAEF,QAAM,MAAgB,CAAC,UAAoC;AAC1D,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,UAA4B,CAAC;AACnC,UAAM,kBAAkB,cAAc,KAAK;AAE3C,QAAI,CAAC,gBAAgB,QAAQ;AAC5B,aAAO,cAAc,eAAe;AAAA,IACrC;AACA,UAAM,aAAa,gBAAgB,MAAM,GAAG;AAE5C,eAAW,CAAC,SAAS,KAAK,KAAK,iBAAiB;AAC/C,UAAI,YAAY,OAAO;AACvB,YAAM,UAAmB,CAAC;AAE1B,iBAAW,CAAC,MAAM,gBAAgB,SAAS,KAAK,OAAO;AACtD,cAAM,SAAS;AAAA,UACd;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACD;AAEA,YAAI,QAAQ;AACX,sBAAY,KAAK,IAAI,WAAW,OAAO,CAAC,CAAC;AACzC,kBAAQ,KAAK,OAAO,CAAC,CAAC;AAAA,QACvB,OAAO;AACN,kBAAQ,KAAK,IAAI;AAAA,QAClB;AAAA,MACD;AAEA,UAAI,YAAY,OAAO,kBAAkB;AACxC,gBAAQ,KAAK,EAAE,MAAM,SAAS,OAAO,WAAW,QAAQ,CAAC;AAAA,MAC1D;AAAA,IACD;AAIA,UAAM,gBAAgB,aAAa,SAAS,UAAU,OAAO,SAAS;AAEtE,UAAM,UAAU,KAAK,IAAI;AAEzB,UAAM,QAAQ,IAAI,aAAgB;AAAA,MACjC,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,IACD,CAAC,EAAE,MAAM;AAET,QAAI,OAAO;AACV,cAAQ,eAAe,cAAc;AACrC,cAAQ,IAAI,UAAU,KAAK;AAC3B,cAAQ,IAAI,qBAAqB,eAAe;AAChD,cAAQ,IAAI,mBAAmB,MAAM,QAAQ,MAAM;AACnD,cAAQ,MAAM,MAAM,OAAO;AAC3B,cAAQ,IAAI,4BAA4B,eAAe;AACvD,cAAQ,IAAI,SAAS,MAAM,MAAM,IAAI;AACrC,cAAQ,SAAS;AAAA,IAClB;AAEA,WAAO;AAAA,EACR;AAEA,SAAO;AACR;","names":[]}