fuse.esm.js 40 KB

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