fuse.esm.js 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775
  1. /**
  2. * Fuse.js v6.4.3 - Lightweight fuzzy-search (http://fusejs.io)
  3. *
  4. * Copyright (c) 2020 Kiro Risk (http://kiro.me)
  5. * All Rights Reserved. Apache Software License 2.0
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. */
  9. function isArray(value) {
  10. return !Array.isArray
  11. ? getTag(value) === '[object Array]'
  12. : Array.isArray(value)
  13. }
  14. // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/baseToString.js
  15. const INFINITY = 1 / 0;
  16. function baseToString(value) {
  17. // Exit early for strings to avoid a performance hit in some environments.
  18. if (typeof value == 'string') {
  19. return value
  20. }
  21. let result = value + '';
  22. return result == '0' && 1 / value == -INFINITY ? '-0' : result
  23. }
  24. function toString(value) {
  25. return value == null ? '' : baseToString(value)
  26. }
  27. function isString(value) {
  28. return typeof value === 'string'
  29. }
  30. function isNumber(value) {
  31. return typeof value === 'number'
  32. }
  33. // Adapted from: https://github.com/lodash/lodash/blob/master/isBoolean.js
  34. function isBoolean(value) {
  35. return (
  36. value === true ||
  37. value === false ||
  38. (isObjectLike(value) && getTag(value) == '[object Boolean]')
  39. )
  40. }
  41. function isObject(value) {
  42. return typeof value === 'object'
  43. }
  44. // Checks if `value` is object-like.
  45. function isObjectLike(value) {
  46. return isObject(value) && value !== null
  47. }
  48. function isDefined(value) {
  49. return value !== undefined && value !== null
  50. }
  51. function isBlank(value) {
  52. return !value.trim().length
  53. }
  54. // Gets the `toStringTag` of `value`.
  55. // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/getTag.js
  56. function getTag(value) {
  57. return value == null
  58. ? value === undefined
  59. ? '[object Undefined]'
  60. : '[object Null]'
  61. : Object.prototype.toString.call(value)
  62. }
  63. const EXTENDED_SEARCH_UNAVAILABLE = 'Extended search is not available';
  64. const INCORRECT_INDEX_TYPE = "Incorrect 'index' type";
  65. const LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = (key) =>
  66. `Invalid value for key ${key}`;
  67. const PATTERN_LENGTH_TOO_LARGE = (max) =>
  68. `Pattern length exceeds max of ${max}.`;
  69. const MISSING_KEY_PROPERTY = (name) => `Missing ${name} property in key`;
  70. const INVALID_KEY_WEIGHT_VALUE = (key) =>
  71. `Property 'weight' in key '${key}' must be a positive integer`;
  72. const hasOwn = Object.prototype.hasOwnProperty;
  73. class KeyStore {
  74. constructor(keys) {
  75. this._keys = [];
  76. this._keyMap = {};
  77. let totalWeight = 0;
  78. keys.forEach((key) => {
  79. let obj = createKey(key);
  80. totalWeight += obj.weight;
  81. this._keys.push(obj);
  82. this._keyMap[obj.id] = obj;
  83. totalWeight += obj.weight;
  84. });
  85. // Normalize weights so that their sum is equal to 1
  86. this._keys.forEach((key) => {
  87. key.weight /= totalWeight;
  88. });
  89. }
  90. get(keyId) {
  91. return this._keyMap[keyId]
  92. }
  93. keys() {
  94. return this._keys
  95. }
  96. toJSON() {
  97. return JSON.stringify(this._keys)
  98. }
  99. }
  100. function createKey(key) {
  101. let path = null;
  102. let id = null;
  103. let src = null;
  104. let weight = 1;
  105. if (isString(key) || isArray(key)) {
  106. src = key;
  107. path = createKeyPath(key);
  108. id = createKeyId(key);
  109. } else {
  110. if (!hasOwn.call(key, 'name')) {
  111. throw new Error(MISSING_KEY_PROPERTY('name'))
  112. }
  113. const name = key.name;
  114. src = name;
  115. if (hasOwn.call(key, 'weight')) {
  116. weight = key.weight;
  117. if (weight <= 0) {
  118. throw new Error(INVALID_KEY_WEIGHT_VALUE(name))
  119. }
  120. }
  121. path = createKeyPath(name);
  122. id = createKeyId(name);
  123. }
  124. return { path, id, weight, src }
  125. }
  126. function createKeyPath(key) {
  127. return isArray(key) ? key : key.split('.')
  128. }
  129. function createKeyId(key) {
  130. return isArray(key) ? key.join('.') : key
  131. }
  132. function get(obj, path) {
  133. let list = [];
  134. let arr = false;
  135. const deepGet = (obj, path, index) => {
  136. if (!isDefined(obj)) {
  137. return
  138. }
  139. if (!path[index]) {
  140. // If there's no path left, we've arrived at the object we care about.
  141. list.push(obj);
  142. } else {
  143. let key = path[index];
  144. const value = obj[key];
  145. if (!isDefined(value)) {
  146. return
  147. }
  148. // If we're at the last value in the path, and if it's a string/number/bool,
  149. // add it to the list
  150. if (
  151. index === path.length - 1 &&
  152. (isString(value) || isNumber(value) || isBoolean(value))
  153. ) {
  154. list.push(toString(value));
  155. } else if (isArray(value)) {
  156. arr = true;
  157. // Search each item in the array.
  158. for (let i = 0, len = value.length; i < len; i += 1) {
  159. deepGet(value[i], path, index + 1);
  160. }
  161. } else if (path.length) {
  162. // An object. Recurse further.
  163. deepGet(value, path, index + 1);
  164. }
  165. }
  166. };
  167. // Backwards compatibility (since path used to be a string)
  168. deepGet(obj, isString(path) ? path.split('.') : path, 0);
  169. return arr ? list : list[0]
  170. }
  171. const MatchOptions = {
  172. // Whether the matches should be included in the result set. When `true`, each record in the result
  173. // set will include the indices of the matched characters.
  174. // These can consequently be used for highlighting purposes.
  175. includeMatches: false,
  176. // When `true`, the matching function will continue to the end of a search pattern even if
  177. // a perfect match has already been located in the string.
  178. findAllMatches: false,
  179. // Minimum number of characters that must be matched before a result is considered a match
  180. minMatchCharLength: 1
  181. };
  182. const BasicOptions = {
  183. // When `true`, the algorithm continues searching to the end of the input even if a perfect
  184. // match is found before the end of the same input.
  185. isCaseSensitive: false,
  186. // When true, the matching function will continue to the end of a search pattern even if
  187. includeScore: false,
  188. // List of properties that will be searched. This also supports nested properties.
  189. keys: [],
  190. // Whether to sort the result list, by score
  191. shouldSort: true,
  192. // Default sort function: sort by ascending score, ascending index
  193. sortFn: (a, b) =>
  194. a.score === b.score ? (a.idx < b.idx ? -1 : 1) : a.score < b.score ? -1 : 1
  195. };
  196. const FuzzyOptions = {
  197. // Approximately where in the text is the pattern expected to be found?
  198. location: 0,
  199. // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match
  200. // (of both letters and location), a threshold of '1.0' would match anything.
  201. threshold: 0.6,
  202. // Determines how close the match must be to the fuzzy location (specified above).
  203. // An exact letter match which is 'distance' characters away from the fuzzy location
  204. // would score as a complete mismatch. A distance of '0' requires the match be at
  205. // the exact location specified, a threshold of '1000' would require a perfect match
  206. // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.
  207. distance: 100
  208. };
  209. const AdvancedOptions = {
  210. // When `true`, it enables the use of unix-like search commands
  211. useExtendedSearch: false,
  212. // The get function to use when fetching an object's properties.
  213. // The default will search nested paths *ie foo.bar.baz*
  214. getFn: get,
  215. // When `true`, search will ignore `location` and `distance`, so it won't matter
  216. // where in the string the pattern appears.
  217. // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score
  218. ignoreLocation: false,
  219. // When `true`, the calculation for the relevance score (used for sorting) will
  220. // ignore the field-length norm.
  221. // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm
  222. ignoreFieldNorm: false
  223. };
  224. var Config = {
  225. ...BasicOptions,
  226. ...MatchOptions,
  227. ...FuzzyOptions,
  228. ...AdvancedOptions
  229. };
  230. const SPACE = /[^ ]+/g;
  231. // Field-length norm: the shorter the field, the higher the weight.
  232. // Set to 3 decimals to reduce index size.
  233. function norm(mantissa = 3) {
  234. const cache = new Map();
  235. return {
  236. get(value) {
  237. const numTokens = value.match(SPACE).length;
  238. if (cache.has(numTokens)) {
  239. return cache.get(numTokens)
  240. }
  241. const n = parseFloat((1 / Math.sqrt(numTokens)).toFixed(mantissa));
  242. cache.set(numTokens, n);
  243. return n
  244. },
  245. clear() {
  246. cache.clear();
  247. }
  248. }
  249. }
  250. class FuseIndex {
  251. constructor({ getFn = Config.getFn } = {}) {
  252. this.norm = norm(3);
  253. this.getFn = getFn;
  254. this.isCreated = false;
  255. this.setIndexRecords();
  256. }
  257. setSources(docs = []) {
  258. this.docs = docs;
  259. }
  260. setIndexRecords(records = []) {
  261. this.records = records;
  262. }
  263. setKeys(keys = []) {
  264. this.keys = keys;
  265. this._keysMap = {};
  266. keys.forEach((key, idx) => {
  267. this._keysMap[key.id] = idx;
  268. });
  269. }
  270. create() {
  271. if (this.isCreated || !this.docs.length) {
  272. return
  273. }
  274. this.isCreated = true;
  275. // List is Array<String>
  276. if (isString(this.docs[0])) {
  277. this.docs.forEach((doc, docIndex) => {
  278. this._addString(doc, docIndex);
  279. });
  280. } else {
  281. // List is Array<Object>
  282. this.docs.forEach((doc, docIndex) => {
  283. this._addObject(doc, docIndex);
  284. });
  285. }
  286. this.norm.clear();
  287. }
  288. // Adds a doc to the end of the index
  289. add(doc) {
  290. const idx = this.size();
  291. if (isString(doc)) {
  292. this._addString(doc, idx);
  293. } else {
  294. this._addObject(doc, idx);
  295. }
  296. }
  297. // Removes the doc at the specified index of the index
  298. removeAt(idx) {
  299. this.records.splice(idx, 1);
  300. // Change ref index of every subsquent doc
  301. for (let i = idx, len = this.size(); i < len; i += 1) {
  302. this.records[i].i -= 1;
  303. }
  304. }
  305. getValueForItemAtKeyId(item, keyId) {
  306. return item[this._keysMap[keyId]]
  307. }
  308. size() {
  309. return this.records.length
  310. }
  311. _addString(doc, docIndex) {
  312. if (!isDefined(doc) || isBlank(doc)) {
  313. return
  314. }
  315. let record = {
  316. v: doc,
  317. i: docIndex,
  318. n: this.norm.get(doc)
  319. };
  320. this.records.push(record);
  321. }
  322. _addObject(doc, docIndex) {
  323. let record = { i: docIndex, $: {} };
  324. // Iterate over every key (i.e, path), and fetch the value at that key
  325. this.keys.forEach((key, keyIndex) => {
  326. // console.log(key)
  327. let value = this.getFn(doc, key.path);
  328. if (!isDefined(value)) {
  329. return
  330. }
  331. if (isArray(value)) {
  332. let subRecords = [];
  333. const stack = [{ nestedArrIndex: -1, value }];
  334. while (stack.length) {
  335. const { nestedArrIndex, value } = stack.pop();
  336. if (!isDefined(value)) {
  337. continue
  338. }
  339. if (isString(value) && !isBlank(value)) {
  340. let subRecord = {
  341. v: value,
  342. i: nestedArrIndex,
  343. n: this.norm.get(value)
  344. };
  345. subRecords.push(subRecord);
  346. } else if (isArray(value)) {
  347. value.forEach((item, k) => {
  348. stack.push({
  349. nestedArrIndex: k,
  350. value: item
  351. });
  352. });
  353. }
  354. }
  355. record.$[keyIndex] = subRecords;
  356. } else if (!isBlank(value)) {
  357. let subRecord = {
  358. v: value,
  359. n: this.norm.get(value)
  360. };
  361. record.$[keyIndex] = subRecord;
  362. }
  363. });
  364. this.records.push(record);
  365. }
  366. toJSON() {
  367. return {
  368. keys: this.keys,
  369. records: this.records
  370. }
  371. }
  372. }
  373. function createIndex(keys, docs, { getFn = Config.getFn } = {}) {
  374. const myIndex = new FuseIndex({ getFn });
  375. myIndex.setKeys(keys.map(createKey));
  376. myIndex.setSources(docs);
  377. myIndex.create();
  378. return myIndex
  379. }
  380. function parseIndex(data, { getFn = Config.getFn } = {}) {
  381. const { keys, records } = data;
  382. const myIndex = new FuseIndex({ getFn });
  383. myIndex.setKeys(keys);
  384. myIndex.setIndexRecords(records);
  385. return myIndex
  386. }
  387. function transformMatches(result, data) {
  388. const matches = result.matches;
  389. data.matches = [];
  390. if (!isDefined(matches)) {
  391. return
  392. }
  393. matches.forEach((match) => {
  394. if (!isDefined(match.indices) || !match.indices.length) {
  395. return
  396. }
  397. const { indices, value } = match;
  398. let obj = {
  399. indices,
  400. value
  401. };
  402. if (match.key) {
  403. obj.key = match.key.src;
  404. }
  405. if (match.idx > -1) {
  406. obj.refIndex = match.idx;
  407. }
  408. data.matches.push(obj);
  409. });
  410. }
  411. function transformScore(result, data) {
  412. data.score = result.score;
  413. }
  414. function computeScore(
  415. pattern,
  416. {
  417. errors = 0,
  418. currentLocation = 0,
  419. expectedLocation = 0,
  420. distance = Config.distance,
  421. ignoreLocation = Config.ignoreLocation
  422. } = {}
  423. ) {
  424. const accuracy = errors / pattern.length;
  425. if (ignoreLocation) {
  426. return accuracy
  427. }
  428. const proximity = Math.abs(expectedLocation - currentLocation);
  429. if (!distance) {
  430. // Dodge divide by zero error.
  431. return proximity ? 1.0 : accuracy
  432. }
  433. return accuracy + proximity / distance
  434. }
  435. function convertMaskToIndices(
  436. matchmask = [],
  437. minMatchCharLength = Config.minMatchCharLength
  438. ) {
  439. let indices = [];
  440. let start = -1;
  441. let end = -1;
  442. let i = 0;
  443. for (let len = matchmask.length; i < len; i += 1) {
  444. let match = matchmask[i];
  445. if (match && start === -1) {
  446. start = i;
  447. } else if (!match && start !== -1) {
  448. end = i - 1;
  449. if (end - start + 1 >= minMatchCharLength) {
  450. indices.push([start, end]);
  451. }
  452. start = -1;
  453. }
  454. }
  455. // (i-1 - start) + 1 => i - start
  456. if (matchmask[i - 1] && i - start >= minMatchCharLength) {
  457. indices.push([start, i - 1]);
  458. }
  459. return indices
  460. }
  461. // Machine word size
  462. const MAX_BITS = 32;
  463. function search(
  464. text,
  465. pattern,
  466. patternAlphabet,
  467. {
  468. location = Config.location,
  469. distance = Config.distance,
  470. threshold = Config.threshold,
  471. findAllMatches = Config.findAllMatches,
  472. minMatchCharLength = Config.minMatchCharLength,
  473. includeMatches = Config.includeMatches,
  474. ignoreLocation = Config.ignoreLocation
  475. } = {}
  476. ) {
  477. if (pattern.length > MAX_BITS) {
  478. throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS))
  479. }
  480. const patternLen = pattern.length;
  481. // Set starting location at beginning text and initialize the alphabet.
  482. const textLen = text.length;
  483. // Handle the case when location > text.length
  484. const expectedLocation = Math.max(0, Math.min(location, textLen));
  485. // Highest score beyond which we give up.
  486. let currentThreshold = threshold;
  487. // Is there a nearby exact match? (speedup)
  488. let bestLocation = expectedLocation;
  489. // Performance: only computer matches when the minMatchCharLength > 1
  490. // OR if `includeMatches` is true.
  491. const computeMatches = minMatchCharLength > 1 || includeMatches;
  492. // A mask of the matches, used for building the indices
  493. const matchMask = computeMatches ? Array(textLen) : [];
  494. let index;
  495. // Get all exact matches, here for speed up
  496. while ((index = text.indexOf(pattern, bestLocation)) > -1) {
  497. let score = computeScore(pattern, {
  498. currentLocation: index,
  499. expectedLocation,
  500. distance,
  501. ignoreLocation
  502. });
  503. currentThreshold = Math.min(score, currentThreshold);
  504. bestLocation = index + patternLen;
  505. if (computeMatches) {
  506. let i = 0;
  507. while (i < patternLen) {
  508. matchMask[index + i] = 1;
  509. i += 1;
  510. }
  511. }
  512. }
  513. // Reset the best location
  514. bestLocation = -1;
  515. let lastBitArr = [];
  516. let finalScore = 1;
  517. let binMax = patternLen + textLen;
  518. const mask = 1 << (patternLen - 1);
  519. for (let i = 0; i < patternLen; i += 1) {
  520. // Scan for the best match; each iteration allows for one more error.
  521. // Run a binary search to determine how far from the match location we can stray
  522. // at this error level.
  523. let binMin = 0;
  524. let binMid = binMax;
  525. while (binMin < binMid) {
  526. const score = computeScore(pattern, {
  527. errors: i,
  528. currentLocation: expectedLocation + binMid,
  529. expectedLocation,
  530. distance,
  531. ignoreLocation
  532. });
  533. if (score <= currentThreshold) {
  534. binMin = binMid;
  535. } else {
  536. binMax = binMid;
  537. }
  538. binMid = Math.floor((binMax - binMin) / 2 + binMin);
  539. }
  540. // Use the result from this iteration as the maximum for the next.
  541. binMax = binMid;
  542. let start = Math.max(1, expectedLocation - binMid + 1);
  543. let finish = findAllMatches
  544. ? textLen
  545. : Math.min(expectedLocation + binMid, textLen) + patternLen;
  546. // Initialize the bit array
  547. let bitArr = Array(finish + 2);
  548. bitArr[finish + 1] = (1 << i) - 1;
  549. for (let j = finish; j >= start; j -= 1) {
  550. let currentLocation = j - 1;
  551. let charMatch = patternAlphabet[text.charAt(currentLocation)];
  552. if (computeMatches) {
  553. // Speed up: quick bool to int conversion (i.e, `charMatch ? 1 : 0`)
  554. matchMask[currentLocation] = +!!charMatch;
  555. }
  556. // First pass: exact match
  557. bitArr[j] = ((bitArr[j + 1] << 1) | 1) & charMatch;
  558. // Subsequent passes: fuzzy match
  559. if (i) {
  560. bitArr[j] |=
  561. ((lastBitArr[j + 1] | lastBitArr[j]) << 1) | 1 | lastBitArr[j + 1];
  562. }
  563. if (bitArr[j] & mask) {
  564. finalScore = computeScore(pattern, {
  565. errors: i,
  566. currentLocation,
  567. expectedLocation,
  568. distance,
  569. ignoreLocation
  570. });
  571. // This match will almost certainly be better than any existing match.
  572. // But check anyway.
  573. if (finalScore <= currentThreshold) {
  574. // Indeed it is
  575. currentThreshold = finalScore;
  576. bestLocation = currentLocation;
  577. // Already passed `loc`, downhill from here on in.
  578. if (bestLocation <= expectedLocation) {
  579. break
  580. }
  581. // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`.
  582. start = Math.max(1, 2 * expectedLocation - bestLocation);
  583. }
  584. }
  585. }
  586. // No hope for a (better) match at greater error levels.
  587. const score = computeScore(pattern, {
  588. errors: i + 1,
  589. currentLocation: expectedLocation,
  590. expectedLocation,
  591. distance,
  592. ignoreLocation
  593. });
  594. if (score > currentThreshold) {
  595. break
  596. }
  597. lastBitArr = bitArr;
  598. }
  599. const result = {
  600. isMatch: bestLocation >= 0,
  601. // Count exact matches (those with a score of 0) to be "almost" exact
  602. score: Math.max(0.001, finalScore)
  603. };
  604. if (computeMatches) {
  605. const indices = convertMaskToIndices(matchMask, minMatchCharLength);
  606. if (!indices.length) {
  607. result.isMatch = false;
  608. } else if (includeMatches) {
  609. result.indices = indices;
  610. }
  611. }
  612. return result
  613. }
  614. function createPatternAlphabet(pattern) {
  615. let mask = {};
  616. for (let i = 0, len = pattern.length; i < len; i += 1) {
  617. const char = pattern.charAt(i);
  618. mask[char] = (mask[char] || 0) | (1 << (len - i - 1));
  619. }
  620. return mask
  621. }
  622. class BitapSearch {
  623. constructor(
  624. pattern,
  625. {
  626. location = Config.location,
  627. threshold = Config.threshold,
  628. distance = Config.distance,
  629. includeMatches = Config.includeMatches,
  630. findAllMatches = Config.findAllMatches,
  631. minMatchCharLength = Config.minMatchCharLength,
  632. isCaseSensitive = Config.isCaseSensitive,
  633. ignoreLocation = Config.ignoreLocation
  634. } = {}
  635. ) {
  636. this.options = {
  637. location,
  638. threshold,
  639. distance,
  640. includeMatches,
  641. findAllMatches,
  642. minMatchCharLength,
  643. isCaseSensitive,
  644. ignoreLocation
  645. };
  646. this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
  647. this.chunks = [];
  648. if (!this.pattern.length) {
  649. return
  650. }
  651. const addChunk = (pattern, startIndex) => {
  652. this.chunks.push({
  653. pattern,
  654. alphabet: createPatternAlphabet(pattern),
  655. startIndex
  656. });
  657. };
  658. const len = this.pattern.length;
  659. if (len > MAX_BITS) {
  660. let i = 0;
  661. const remainder = len % MAX_BITS;
  662. const end = len - remainder;
  663. while (i < end) {
  664. addChunk(this.pattern.substr(i, MAX_BITS), i);
  665. i += MAX_BITS;
  666. }
  667. if (remainder) {
  668. const startIndex = len - MAX_BITS;
  669. addChunk(this.pattern.substr(startIndex), startIndex);
  670. }
  671. } else {
  672. addChunk(this.pattern, 0);
  673. }
  674. }
  675. searchIn(text) {
  676. const { isCaseSensitive, includeMatches } = this.options;
  677. if (!isCaseSensitive) {
  678. text = text.toLowerCase();
  679. }
  680. // Exact match
  681. if (this.pattern === text) {
  682. let result = {
  683. isMatch: true,
  684. score: 0
  685. };
  686. if (includeMatches) {
  687. result.indices = [[0, text.length - 1]];
  688. }
  689. return result
  690. }
  691. // Otherwise, use Bitap algorithm
  692. const {
  693. location,
  694. distance,
  695. threshold,
  696. findAllMatches,
  697. minMatchCharLength,
  698. ignoreLocation
  699. } = this.options;
  700. let allIndices = [];
  701. let totalScore = 0;
  702. let hasMatches = false;
  703. this.chunks.forEach(({ pattern, alphabet, startIndex }) => {
  704. const { isMatch, score, indices } = search(text, pattern, alphabet, {
  705. location: location + startIndex,
  706. distance,
  707. threshold,
  708. findAllMatches,
  709. minMatchCharLength,
  710. includeMatches,
  711. ignoreLocation
  712. });
  713. if (isMatch) {
  714. hasMatches = true;
  715. }
  716. totalScore += score;
  717. if (isMatch && indices) {
  718. allIndices = [...allIndices, ...indices];
  719. }
  720. });
  721. let result = {
  722. isMatch: hasMatches,
  723. score: hasMatches ? totalScore / this.chunks.length : 1
  724. };
  725. if (hasMatches && includeMatches) {
  726. result.indices = allIndices;
  727. }
  728. return result
  729. }
  730. }
  731. class BaseMatch {
  732. constructor(pattern) {
  733. this.pattern = pattern;
  734. }
  735. static isMultiMatch(pattern) {
  736. return getMatch(pattern, this.multiRegex)
  737. }
  738. static isSingleMatch(pattern) {
  739. return getMatch(pattern, this.singleRegex)
  740. }
  741. search(/*text*/) {}
  742. }
  743. function getMatch(pattern, exp) {
  744. const matches = pattern.match(exp);
  745. return matches ? matches[1] : null
  746. }
  747. // Token: 'file
  748. class ExactMatch extends BaseMatch {
  749. constructor(pattern) {
  750. super(pattern);
  751. }
  752. static get type() {
  753. return 'exact'
  754. }
  755. static get multiRegex() {
  756. return /^="(.*)"$/
  757. }
  758. static get singleRegex() {
  759. return /^=(.*)$/
  760. }
  761. search(text) {
  762. const isMatch = text === this.pattern;
  763. return {
  764. isMatch,
  765. score: isMatch ? 0 : 1,
  766. indices: [0, this.pattern.length - 1]
  767. }
  768. }
  769. }
  770. // Token: !fire
  771. class InverseExactMatch extends BaseMatch {
  772. constructor(pattern) {
  773. super(pattern);
  774. }
  775. static get type() {
  776. return 'inverse-exact'
  777. }
  778. static get multiRegex() {
  779. return /^!"(.*)"$/
  780. }
  781. static get singleRegex() {
  782. return /^!(.*)$/
  783. }
  784. search(text) {
  785. const index = text.indexOf(this.pattern);
  786. const isMatch = index === -1;
  787. return {
  788. isMatch,
  789. score: isMatch ? 0 : 1,
  790. indices: [0, text.length - 1]
  791. }
  792. }
  793. }
  794. // Token: ^file
  795. class PrefixExactMatch extends BaseMatch {
  796. constructor(pattern) {
  797. super(pattern);
  798. }
  799. static get type() {
  800. return 'prefix-exact'
  801. }
  802. static get multiRegex() {
  803. return /^\^"(.*)"$/
  804. }
  805. static get singleRegex() {
  806. return /^\^(.*)$/
  807. }
  808. search(text) {
  809. const isMatch = text.startsWith(this.pattern);
  810. return {
  811. isMatch,
  812. score: isMatch ? 0 : 1,
  813. indices: [0, this.pattern.length - 1]
  814. }
  815. }
  816. }
  817. // Token: !^fire
  818. class InversePrefixExactMatch extends BaseMatch {
  819. constructor(pattern) {
  820. super(pattern);
  821. }
  822. static get type() {
  823. return 'inverse-prefix-exact'
  824. }
  825. static get multiRegex() {
  826. return /^!\^"(.*)"$/
  827. }
  828. static get singleRegex() {
  829. return /^!\^(.*)$/
  830. }
  831. search(text) {
  832. const isMatch = !text.startsWith(this.pattern);
  833. return {
  834. isMatch,
  835. score: isMatch ? 0 : 1,
  836. indices: [0, text.length - 1]
  837. }
  838. }
  839. }
  840. // Token: .file$
  841. class SuffixExactMatch extends BaseMatch {
  842. constructor(pattern) {
  843. super(pattern);
  844. }
  845. static get type() {
  846. return 'suffix-exact'
  847. }
  848. static get multiRegex() {
  849. return /^"(.*)"\$$/
  850. }
  851. static get singleRegex() {
  852. return /^(.*)\$$/
  853. }
  854. search(text) {
  855. const isMatch = text.endsWith(this.pattern);
  856. return {
  857. isMatch,
  858. score: isMatch ? 0 : 1,
  859. indices: [text.length - this.pattern.length, text.length - 1]
  860. }
  861. }
  862. }
  863. // Token: !.file$
  864. class InverseSuffixExactMatch extends BaseMatch {
  865. constructor(pattern) {
  866. super(pattern);
  867. }
  868. static get type() {
  869. return 'inverse-suffix-exact'
  870. }
  871. static get multiRegex() {
  872. return /^!"(.*)"\$$/
  873. }
  874. static get singleRegex() {
  875. return /^!(.*)\$$/
  876. }
  877. search(text) {
  878. const isMatch = !text.endsWith(this.pattern);
  879. return {
  880. isMatch,
  881. score: isMatch ? 0 : 1,
  882. indices: [0, text.length - 1]
  883. }
  884. }
  885. }
  886. class FuzzyMatch extends BaseMatch {
  887. constructor(
  888. pattern,
  889. {
  890. location = Config.location,
  891. threshold = Config.threshold,
  892. distance = Config.distance,
  893. includeMatches = Config.includeMatches,
  894. findAllMatches = Config.findAllMatches,
  895. minMatchCharLength = Config.minMatchCharLength,
  896. isCaseSensitive = Config.isCaseSensitive,
  897. ignoreLocation = Config.ignoreLocation
  898. } = {}
  899. ) {
  900. super(pattern);
  901. this._bitapSearch = new BitapSearch(pattern, {
  902. location,
  903. threshold,
  904. distance,
  905. includeMatches,
  906. findAllMatches,
  907. minMatchCharLength,
  908. isCaseSensitive,
  909. ignoreLocation
  910. });
  911. }
  912. static get type() {
  913. return 'fuzzy'
  914. }
  915. static get multiRegex() {
  916. return /^"(.*)"$/
  917. }
  918. static get singleRegex() {
  919. return /^(.*)$/
  920. }
  921. search(text) {
  922. return this._bitapSearch.searchIn(text)
  923. }
  924. }
  925. // Token: 'file
  926. class IncludeMatch extends BaseMatch {
  927. constructor(pattern) {
  928. super(pattern);
  929. }
  930. static get type() {
  931. return 'include'
  932. }
  933. static get multiRegex() {
  934. return /^'"(.*)"$/
  935. }
  936. static get singleRegex() {
  937. return /^'(.*)$/
  938. }
  939. search(text) {
  940. let location = 0;
  941. let index;
  942. const indices = [];
  943. const patternLen = this.pattern.length;
  944. // Get all exact matches
  945. while ((index = text.indexOf(this.pattern, location)) > -1) {
  946. location = index + patternLen;
  947. indices.push([index, location - 1]);
  948. }
  949. const isMatch = !!indices.length;
  950. return {
  951. isMatch,
  952. score: isMatch ? 1 : 0,
  953. indices
  954. }
  955. }
  956. }
  957. // ❗Order is important. DO NOT CHANGE.
  958. const searchers = [
  959. ExactMatch,
  960. IncludeMatch,
  961. PrefixExactMatch,
  962. InversePrefixExactMatch,
  963. InverseSuffixExactMatch,
  964. SuffixExactMatch,
  965. InverseExactMatch,
  966. FuzzyMatch
  967. ];
  968. const searchersLen = searchers.length;
  969. // Regex to split by spaces, but keep anything in quotes together
  970. const SPACE_RE = / +(?=([^\"]*\"[^\"]*\")*[^\"]*$)/;
  971. const OR_TOKEN = '|';
  972. // Return a 2D array representation of the query, for simpler parsing.
  973. // Example:
  974. // "^core go$ | rb$ | py$ xy$" => [["^core", "go$"], ["rb$"], ["py$", "xy$"]]
  975. function parseQuery(pattern, options = {}) {
  976. return pattern.split(OR_TOKEN).map((item) => {
  977. let query = item
  978. .trim()
  979. .split(SPACE_RE)
  980. .filter((item) => item && !!item.trim());
  981. let results = [];
  982. for (let i = 0, len = query.length; i < len; i += 1) {
  983. const queryItem = query[i];
  984. // 1. Handle multiple query match (i.e, once that are quoted, like `"hello world"`)
  985. let found = false;
  986. let idx = -1;
  987. while (!found && ++idx < searchersLen) {
  988. const searcher = searchers[idx];
  989. let token = searcher.isMultiMatch(queryItem);
  990. if (token) {
  991. results.push(new searcher(token, options));
  992. found = true;
  993. }
  994. }
  995. if (found) {
  996. continue
  997. }
  998. // 2. Handle single query matches (i.e, once that are *not* quoted)
  999. idx = -1;
  1000. while (++idx < searchersLen) {
  1001. const searcher = searchers[idx];
  1002. let token = searcher.isSingleMatch(queryItem);
  1003. if (token) {
  1004. results.push(new searcher(token, options));
  1005. break
  1006. }
  1007. }
  1008. }
  1009. return results
  1010. })
  1011. }
  1012. // These extended matchers can return an array of matches, as opposed
  1013. // to a singl match
  1014. const MultiMatchSet = new Set([FuzzyMatch.type, IncludeMatch.type]);
  1015. /**
  1016. * Command-like searching
  1017. * ======================
  1018. *
  1019. * Given multiple search terms delimited by spaces.e.g. `^jscript .python$ ruby !java`,
  1020. * search in a given text.
  1021. *
  1022. * Search syntax:
  1023. *
  1024. * | Token | Match type | Description |
  1025. * | ----------- | -------------------------- | -------------------------------------- |
  1026. * | `jscript` | fuzzy-match | Items that fuzzy match `jscript` |
  1027. * | `=scheme` | exact-match | Items that are `scheme` |
  1028. * | `'python` | include-match | Items that include `python` |
  1029. * | `!ruby` | inverse-exact-match | Items that do not include `ruby` |
  1030. * | `^java` | prefix-exact-match | Items that start with `java` |
  1031. * | `!^earlang` | inverse-prefix-exact-match | Items that do not start with `earlang` |
  1032. * | `.js$` | suffix-exact-match | Items that end with `.js` |
  1033. * | `!.go$` | inverse-suffix-exact-match | Items that do not end with `.go` |
  1034. *
  1035. * A single pipe character acts as an OR operator. For example, the following
  1036. * query matches entries that start with `core` and end with either`go`, `rb`,
  1037. * or`py`.
  1038. *
  1039. * ```
  1040. * ^core go$ | rb$ | py$
  1041. * ```
  1042. */
  1043. class ExtendedSearch {
  1044. constructor(
  1045. pattern,
  1046. {
  1047. isCaseSensitive = Config.isCaseSensitive,
  1048. includeMatches = Config.includeMatches,
  1049. minMatchCharLength = Config.minMatchCharLength,
  1050. ignoreLocation = Config.ignoreLocation,
  1051. findAllMatches = Config.findAllMatches,
  1052. location = Config.location,
  1053. threshold = Config.threshold,
  1054. distance = Config.distance
  1055. } = {}
  1056. ) {
  1057. this.query = null;
  1058. this.options = {
  1059. isCaseSensitive,
  1060. includeMatches,
  1061. minMatchCharLength,
  1062. findAllMatches,
  1063. ignoreLocation,
  1064. location,
  1065. threshold,
  1066. distance
  1067. };
  1068. this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
  1069. this.query = parseQuery(this.pattern, this.options);
  1070. }
  1071. static condition(_, options) {
  1072. return options.useExtendedSearch
  1073. }
  1074. searchIn(text) {
  1075. const query = this.query;
  1076. if (!query) {
  1077. return {
  1078. isMatch: false,
  1079. score: 1
  1080. }
  1081. }
  1082. const { includeMatches, isCaseSensitive } = this.options;
  1083. text = isCaseSensitive ? text : text.toLowerCase();
  1084. let numMatches = 0;
  1085. let allIndices = [];
  1086. let totalScore = 0;
  1087. // ORs
  1088. for (let i = 0, qLen = query.length; i < qLen; i += 1) {
  1089. const searchers = query[i];
  1090. // Reset indices
  1091. allIndices.length = 0;
  1092. numMatches = 0;
  1093. // ANDs
  1094. for (let j = 0, pLen = searchers.length; j < pLen; j += 1) {
  1095. const searcher = searchers[j];
  1096. const { isMatch, indices, score } = searcher.search(text);
  1097. if (isMatch) {
  1098. numMatches += 1;
  1099. totalScore += score;
  1100. if (includeMatches) {
  1101. const type = searcher.constructor.type;
  1102. if (MultiMatchSet.has(type)) {
  1103. allIndices = [...allIndices, ...indices];
  1104. } else {
  1105. allIndices.push(indices);
  1106. }
  1107. }
  1108. } else {
  1109. totalScore = 0;
  1110. numMatches = 0;
  1111. allIndices.length = 0;
  1112. break
  1113. }
  1114. }
  1115. // OR condition, so if TRUE, return
  1116. if (numMatches) {
  1117. let result = {
  1118. isMatch: true,
  1119. score: totalScore / numMatches
  1120. };
  1121. if (includeMatches) {
  1122. result.indices = allIndices;
  1123. }
  1124. return result
  1125. }
  1126. }
  1127. // Nothing was matched
  1128. return {
  1129. isMatch: false,
  1130. score: 1
  1131. }
  1132. }
  1133. }
  1134. const registeredSearchers = [];
  1135. function register(...args) {
  1136. registeredSearchers.push(...args);
  1137. }
  1138. function createSearcher(pattern, options) {
  1139. for (let i = 0, len = registeredSearchers.length; i < len; i += 1) {
  1140. let searcherClass = registeredSearchers[i];
  1141. if (searcherClass.condition(pattern, options)) {
  1142. return new searcherClass(pattern, options)
  1143. }
  1144. }
  1145. return new BitapSearch(pattern, options)
  1146. }
  1147. const LogicalOperator = {
  1148. AND: '$and',
  1149. OR: '$or'
  1150. };
  1151. const KeyType = {
  1152. PATH: '$path',
  1153. PATTERN: '$val'
  1154. };
  1155. const isExpression = (query) =>
  1156. !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]);
  1157. const isPath = (query) => !!query[KeyType.PATH];
  1158. const isLeaf = (query) =>
  1159. !isArray(query) && isObject(query) && !isExpression(query);
  1160. const convertToExplicit = (query) => ({
  1161. [LogicalOperator.AND]: Object.keys(query).map((key) => ({
  1162. [key]: query[key]
  1163. }))
  1164. });
  1165. // When `auto` is `true`, the parse function will infer and initialize and add
  1166. // the appropriate `Searcher` instance
  1167. function parse(query, options, { auto = true } = {}) {
  1168. const next = (query) => {
  1169. let keys = Object.keys(query);
  1170. const isQueryPath = isPath(query);
  1171. if (!isQueryPath && keys.length > 1 && !isExpression(query)) {
  1172. return next(convertToExplicit(query))
  1173. }
  1174. if (isLeaf(query)) {
  1175. const key = isQueryPath ? query[KeyType.PATH] : keys[0];
  1176. const pattern = isQueryPath ? query[KeyType.PATTERN] : query[key];
  1177. if (!isString(pattern)) {
  1178. throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key))
  1179. }
  1180. const obj = {
  1181. keyId: createKeyId(key),
  1182. pattern
  1183. };
  1184. if (auto) {
  1185. obj.searcher = createSearcher(pattern, options);
  1186. }
  1187. return obj
  1188. }
  1189. let node = {
  1190. children: [],
  1191. operator: keys[0]
  1192. };
  1193. keys.forEach((key) => {
  1194. const value = query[key];
  1195. if (isArray(value)) {
  1196. value.forEach((item) => {
  1197. node.children.push(next(item));
  1198. });
  1199. }
  1200. });
  1201. return node
  1202. };
  1203. if (!isExpression(query)) {
  1204. query = convertToExplicit(query);
  1205. }
  1206. return next(query)
  1207. }
  1208. class Fuse {
  1209. constructor(docs, options = {}, index) {
  1210. this.options = { ...Config, ...options };
  1211. if (
  1212. this.options.useExtendedSearch &&
  1213. !true
  1214. ) {
  1215. throw new Error(EXTENDED_SEARCH_UNAVAILABLE)
  1216. }
  1217. this._keyStore = new KeyStore(this.options.keys);
  1218. this.setCollection(docs, index);
  1219. }
  1220. setCollection(docs, index) {
  1221. this._docs = docs;
  1222. if (index && !(index instanceof FuseIndex)) {
  1223. throw new Error(INCORRECT_INDEX_TYPE)
  1224. }
  1225. this._myIndex =
  1226. index ||
  1227. createIndex(this.options.keys, this._docs, {
  1228. getFn: this.options.getFn
  1229. });
  1230. }
  1231. add(doc) {
  1232. if (!isDefined(doc)) {
  1233. return
  1234. }
  1235. this._docs.push(doc);
  1236. this._myIndex.add(doc);
  1237. }
  1238. remove(predicate = (/* doc, idx */) => false) {
  1239. const results = [];
  1240. for (let i = 0, len = this._docs.length; i < len; i += 1) {
  1241. const doc = this._docs[i];
  1242. if (predicate(doc, i)) {
  1243. this.removeAt(i);
  1244. i -= 1;
  1245. len -= 1;
  1246. results.push(doc);
  1247. }
  1248. }
  1249. return results
  1250. }
  1251. removeAt(idx) {
  1252. this._docs.splice(idx, 1);
  1253. this._myIndex.removeAt(idx);
  1254. }
  1255. getIndex() {
  1256. return this._myIndex
  1257. }
  1258. search(query, { limit = -1 } = {}) {
  1259. const {
  1260. includeMatches,
  1261. includeScore,
  1262. shouldSort,
  1263. sortFn,
  1264. ignoreFieldNorm
  1265. } = this.options;
  1266. let results = isString(query)
  1267. ? isString(this._docs[0])
  1268. ? this._searchStringList(query)
  1269. : this._searchObjectList(query)
  1270. : this._searchLogical(query);
  1271. computeScore$1(results, { ignoreFieldNorm });
  1272. if (shouldSort) {
  1273. results.sort(sortFn);
  1274. }
  1275. if (isNumber(limit) && limit > -1) {
  1276. results = results.slice(0, limit);
  1277. }
  1278. return format(results, this._docs, {
  1279. includeMatches,
  1280. includeScore
  1281. })
  1282. }
  1283. _searchStringList(query) {
  1284. const searcher = createSearcher(query, this.options);
  1285. const { records } = this._myIndex;
  1286. const results = [];
  1287. // Iterate over every string in the index
  1288. records.forEach(({ v: text, i: idx, n: norm }) => {
  1289. if (!isDefined(text)) {
  1290. return
  1291. }
  1292. const { isMatch, score, indices } = searcher.searchIn(text);
  1293. if (isMatch) {
  1294. results.push({
  1295. item: text,
  1296. idx,
  1297. matches: [{ score, value: text, norm, indices }]
  1298. });
  1299. }
  1300. });
  1301. return results
  1302. }
  1303. _searchLogical(query) {
  1304. const expression = parse(query, this.options);
  1305. const evaluate = (node, item, idx) => {
  1306. if (!node.children) {
  1307. const { keyId, searcher } = node;
  1308. const matches = this._findMatches({
  1309. key: this._keyStore.get(keyId),
  1310. value: this._myIndex.getValueForItemAtKeyId(item, keyId),
  1311. searcher
  1312. });
  1313. if (matches && matches.length) {
  1314. return [
  1315. {
  1316. idx,
  1317. item,
  1318. matches
  1319. }
  1320. ]
  1321. }
  1322. return []
  1323. }
  1324. /*eslint indent: [2, 2, {"SwitchCase": 1}]*/
  1325. switch (node.operator) {
  1326. case LogicalOperator.AND: {
  1327. const res = [];
  1328. for (let i = 0, len = node.children.length; i < len; i += 1) {
  1329. const child = node.children[i];
  1330. const result = evaluate(child, item, idx);
  1331. if (result.length) {
  1332. res.push(...result);
  1333. } else {
  1334. return []
  1335. }
  1336. }
  1337. return res
  1338. }
  1339. case LogicalOperator.OR: {
  1340. const res = [];
  1341. for (let i = 0, len = node.children.length; i < len; i += 1) {
  1342. const child = node.children[i];
  1343. const result = evaluate(child, item, idx);
  1344. if (result.length) {
  1345. res.push(...result);
  1346. break
  1347. }
  1348. }
  1349. return res
  1350. }
  1351. }
  1352. };
  1353. const records = this._myIndex.records;
  1354. const resultMap = {};
  1355. const results = [];
  1356. records.forEach(({ $: item, i: idx }) => {
  1357. if (isDefined(item)) {
  1358. let expResults = evaluate(expression, item, idx);
  1359. if (expResults.length) {
  1360. // Dedupe when adding
  1361. if (!resultMap[idx]) {
  1362. resultMap[idx] = { idx, item, matches: [] };
  1363. results.push(resultMap[idx]);
  1364. }
  1365. expResults.forEach(({ matches }) => {
  1366. resultMap[idx].matches.push(...matches);
  1367. });
  1368. }
  1369. }
  1370. });
  1371. return results
  1372. }
  1373. _searchObjectList(query) {
  1374. const searcher = createSearcher(query, this.options);
  1375. const { keys, records } = this._myIndex;
  1376. const results = [];
  1377. // List is Array<Object>
  1378. records.forEach(({ $: item, i: idx }) => {
  1379. if (!isDefined(item)) {
  1380. return
  1381. }
  1382. let matches = [];
  1383. // Iterate over every key (i.e, path), and fetch the value at that key
  1384. keys.forEach((key, keyIndex) => {
  1385. matches.push(
  1386. ...this._findMatches({
  1387. key,
  1388. value: item[keyIndex],
  1389. searcher
  1390. })
  1391. );
  1392. });
  1393. if (matches.length) {
  1394. results.push({
  1395. idx,
  1396. item,
  1397. matches
  1398. });
  1399. }
  1400. });
  1401. return results
  1402. }
  1403. _findMatches({ key, value, searcher }) {
  1404. if (!isDefined(value)) {
  1405. return []
  1406. }
  1407. let matches = [];
  1408. if (isArray(value)) {
  1409. value.forEach(({ v: text, i: idx, n: norm }) => {
  1410. if (!isDefined(text)) {
  1411. return
  1412. }
  1413. const { isMatch, score, indices } = searcher.searchIn(text);
  1414. if (isMatch) {
  1415. matches.push({
  1416. score,
  1417. key,
  1418. value: text,
  1419. idx,
  1420. norm,
  1421. indices
  1422. });
  1423. }
  1424. });
  1425. } else {
  1426. const { v: text, n: norm } = value;
  1427. const { isMatch, score, indices } = searcher.searchIn(text);
  1428. if (isMatch) {
  1429. matches.push({ score, key, value: text, norm, indices });
  1430. }
  1431. }
  1432. return matches
  1433. }
  1434. }
  1435. // Practical scoring function
  1436. function computeScore$1(results, { ignoreFieldNorm = Config.ignoreFieldNorm }) {
  1437. results.forEach((result) => {
  1438. let totalScore = 1;
  1439. result.matches.forEach(({ key, norm, score }) => {
  1440. const weight = key ? key.weight : null;
  1441. totalScore *= Math.pow(
  1442. score === 0 && weight ? Number.EPSILON : score,
  1443. (weight || 1) * (ignoreFieldNorm ? 1 : norm)
  1444. );
  1445. });
  1446. result.score = totalScore;
  1447. });
  1448. }
  1449. function format(
  1450. results,
  1451. docs,
  1452. {
  1453. includeMatches = Config.includeMatches,
  1454. includeScore = Config.includeScore
  1455. } = {}
  1456. ) {
  1457. const transformers = [];
  1458. if (includeMatches) transformers.push(transformMatches);
  1459. if (includeScore) transformers.push(transformScore);
  1460. return results.map((result) => {
  1461. const { idx } = result;
  1462. const data = {
  1463. item: docs[idx],
  1464. refIndex: idx
  1465. };
  1466. if (transformers.length) {
  1467. transformers.forEach((transformer) => {
  1468. transformer(result, data);
  1469. });
  1470. }
  1471. return data
  1472. })
  1473. }
  1474. Fuse.version = '6.4.3';
  1475. Fuse.createIndex = createIndex;
  1476. Fuse.parseIndex = parseIndex;
  1477. Fuse.config = Config;
  1478. {
  1479. Fuse.parseQuery = parse;
  1480. }
  1481. {
  1482. register(ExtendedSearch);
  1483. }
  1484. export default Fuse;