{"version":3,"file":"index.cjs","sources":["../src/SearchableMap/TreeIterator.ts","../src/SearchableMap/fuzzySearch.ts","../src/SearchableMap/SearchableMap.ts","../src/info.ts","../src/constant.ts","../src/utils.ts","../src/warning.ts","../src/term.ts","../src/add.ts","../src/defaults.ts","../src/symbols.ts","../src/results.ts","../src/search.ts","../src/autoSuggest.ts","../src/SearchIndex.ts","../src/init.ts","../src/vacuum.ts","../src/remove.ts","../src/replace.ts"],"sourcesContent":["import type { Entry, LeafType, RadixTree } from \"./typings.js\";\n\nexport const ENTRIES = \"ENTRIES\";\n\nexport const KEYS = \"KEYS\";\n\nexport const VALUES = \"VALUES\";\n\nexport const LEAF = \"\" as LeafType;\n\ninterface Iterators<T> {\n  ENTRIES: Entry<T>;\n  KEYS: string;\n  VALUES: T;\n}\n\ntype Kind<T> = keyof Iterators<T>;\ntype Result<T, K extends keyof Iterators<T>> = Iterators<T>[K];\n\ntype IteratorPath<T> = {\n  node: RadixTree<T>;\n  keys: string[];\n}[];\n\nexport interface IterableSet<T> {\n  _tree: RadixTree<T>;\n  _prefix: string;\n}\n\n/**\n * @private\n */\nexport class TreeIterator<T, K extends Kind<T>>\n  implements Iterator<Result<T, K>>\n{\n  set: IterableSet<T>;\n  _type: K;\n  _path: IteratorPath<T>;\n\n  constructor(set: IterableSet<T>, type: K) {\n    const node = set._tree;\n    const keys = Array.from(node.keys());\n\n    this.set = set;\n    this._type = type;\n    this._path = keys.length > 0 ? [{ node, keys }] : [];\n  }\n\n  next(): IteratorResult<Result<T, K>> {\n    const value = this.dive();\n\n    this.backtrack();\n\n    return value;\n  }\n\n  dive(): IteratorResult<Result<T, K>> {\n    if (this._path.length === 0) return { done: true, value: undefined };\n\n    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n    const { node, keys } = last(this._path)!;\n\n    if (last(keys) === LEAF) return { done: false, value: this.result() };\n\n    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n    const child = node.get(last(keys)!)!;\n\n    this._path.push({ node: child, keys: Array.from(child.keys()) });\n\n    return this.dive();\n  }\n\n  backtrack(): void {\n    if (this._path.length === 0) return;\n\n    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n    const keys = last(this._path)!.keys;\n\n    keys.pop();\n    if (keys.length > 0) return;\n\n    this._path.pop();\n    this.backtrack();\n  }\n\n  key(): string {\n    return (\n      this.set._prefix +\n      this._path\n        .map(({ keys }) => last(keys))\n        .filter((key) => key !== LEAF)\n        .join(\"\")\n    );\n  }\n\n  value(): T {\n    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n    return last(this._path)!.node.get(LEAF)!;\n  }\n\n  result(): Result<T, K> {\n    switch (this._type) {\n      case VALUES:\n        return this.value() as Result<T, K>;\n      case KEYS:\n        return this.key() as Result<T, K>;\n      default:\n        return [this.key(), this.value()] as Result<T, K>;\n    }\n  }\n\n  [Symbol.iterator](): this {\n    return this;\n  }\n}\n\nconst last = <T>(array: T[]): T | undefined => {\n  return array[array.length - 1];\n};\n","import { LEAF } from \"./TreeIterator.js\";\nimport type { FuzzyResults, RadixTree } from \"./typings.js\";\n\nexport const fuzzySearch = <Value = any>(\n  node: RadixTree<Value>,\n  query: string,\n  maxDistance: number,\n): FuzzyResults<Value> => {\n  const results: FuzzyResults<Value> = new Map();\n\n  if (typeof query !== \"string\") return results;\n\n  // Number of columns in the Levenshtein matrix.\n  const n = query.length + 1;\n\n  // Matching terms can never be longer than N + maxDistance.\n  const m = n + maxDistance;\n\n  // Fill first matrix row and column with numbers: 0 1 2 3 ...\n  const matrix = new Uint8Array(m * n).fill(maxDistance + 1);\n\n  for (let j = 0; j < n; ++j) matrix[j] = j;\n  for (let i = 1; i < m; ++i) matrix[i * n] = i;\n\n  recurse(node, query, maxDistance, results, matrix, 1, n, \"\");\n\n  return results;\n};\n\n// Modified version of http://stevehanov.ca/blog/?id=114\n\n// This builds a Levenshtein matrix for a given query and continuously updates\n// it for nodes in the radix tree that fall within the given maximum edit\n// distance. Keeping the same matrix around is beneficial especially for larger\n// edit distances.\n//\n//           k   a   t   e   <-- query\n//       0   1   2   3   4\n//   c   1   1   2   3   4\n//   a   2   2   1   2   3\n//   t   3   3   2   1  [2]  <-- edit distance\n//   ^\n//   ^ term in radix tree, rows are added and removed as needed\n\nconst recurse = <Value = any>(\n  node: RadixTree<Value>,\n  query: string,\n  maxDistance: number,\n  results: FuzzyResults<Value>,\n  matrix: Uint8Array,\n  m: number,\n  n: number,\n  prefix: string,\n): void => {\n  const offset = m * n;\n\n  key: for (const key of node.keys())\n    if (key === LEAF) {\n      // We've reached a leaf node. Check if the edit distance acceptable and\n      // store the result if it is.\n      const distance = matrix[offset - 1];\n\n      if (distance <= maxDistance)\n        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n        results.set(prefix, [node.get(key)!, distance]);\n    } else {\n      // Iterate over all characters in the key. Update the Levenshtein matrix\n      // and check if the minimum distance in the last row is still within the\n      // maximum edit distance. If it is, we can recurse over all child nodes.\n      let i = m;\n\n      for (let pos = 0; pos < key.length; ++pos, ++i) {\n        const char = key[pos];\n        const thisRowOffset = n * i;\n        const prevRowOffset = thisRowOffset - n;\n\n        // Set the first column based on the previous row, and initialize the\n        // minimum distance in the current row.\n        let minDistance = matrix[thisRowOffset];\n\n        const jmin = Math.max(0, i - maxDistance - 1);\n        const jmax = Math.min(n - 1, i + maxDistance);\n\n        // Iterate over remaining columns (characters in the query).\n        for (let j = jmin; j < jmax; ++j) {\n          const different = char !== query[j];\n\n          // It might make sense to only read the matrix positions used for\n          // deletion/insertion if the characters are different. But we want to\n          // avoid conditional reads for performance reasons.\n          const rpl = matrix[prevRowOffset + j] + +different;\n          const del = matrix[prevRowOffset + j + 1] + 1;\n          const ins = matrix[thisRowOffset + j] + 1;\n\n          const dist = (matrix[thisRowOffset + j + 1] = Math.min(\n            rpl,\n            del,\n            ins,\n          ));\n\n          if (dist < minDistance) minDistance = dist;\n        }\n\n        // Because distance will never decrease, we can stop. There will be no\n        // matching child nodes.\n        if (minDistance > maxDistance) continue key;\n      }\n\n      recurse(\n        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n        node.get(key)!,\n        query,\n        maxDistance,\n        results,\n        matrix,\n        i,\n        n,\n        prefix + key,\n      );\n    }\n};\n","import { ENTRIES, KEYS, LEAF, TreeIterator, VALUES } from \"./TreeIterator.js\";\nimport { fuzzySearch } from \"./fuzzySearch.js\";\nimport type { Entry, FuzzyResults, Path, RadixTree } from \"./typings.js\";\n\n/**\n * A class implementing the same interface as a standard JavaScript\n * [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)\n * with string keys, but adding support for efficiently searching entries with\n * prefix or fuzzy search. This class is used internally by {@link SearchIndex} as\n * the inverted index data structure. The implementation is a radix tree\n * (compressed prefix tree).\n *\n * Since this class can be of general utility beyond _SlimSearch_, it is\n * exported by the `slimsearch` package and can be imported (or required) as\n * `slimsearch/SearchableMap`.\n *\n * @typeParam Value The type of the values stored in the map.\n */\nexport class SearchableMap<Value = any> {\n  /**\n   * @ignore\n   */\n  _tree: RadixTree<Value>;\n\n  /**\n   * @ignore\n   */\n  _prefix: string;\n\n  private _size: number | undefined = undefined;\n\n  /**\n   * The constructor is normally called without arguments, creating an empty\n   * map. In order to create a {@link SearchableMap} from an iterable or from an\n   * object, check {@link SearchableMap.from} and {@link SearchableMap.fromObject}.\n   *\n   * The constructor arguments are for internal use, when creating derived\n   * mutable views of a map at a prefix.\n   */\n  constructor(tree: RadixTree<Value> = new Map(), prefix = \"\") {\n    this._tree = tree;\n    this._prefix = prefix;\n  }\n\n  /**\n   * Creates and returns a mutable view of this {@link SearchableMap}, containing only\n   * entries that share the given prefix.\n   *\n   * ### Usage:\n   *\n   * ```js\n   * const map = new SearchableMap()\n   * map.set(\"unicorn\", 1)\n   * map.set(\"universe\", 2)\n   * map.set(\"university\", 3)\n   * map.set(\"unique\", 4)\n   * map.set(\"hello\", 5)\n   *\n   * const uni = map.atPrefix(\"uni\")\n   * uni.get(\"unique\") // => 4\n   * uni.get(\"unicorn\") // => 1\n   * uni.get(\"hello\") // => undefined\n   *\n   * const univer = map.atPrefix(\"univer\")\n   * univer.get(\"unique\") // => undefined\n   * univer.get(\"universe\") // => 2\n   * univer.get(\"university\") // => 3\n   * ```\n   *\n   * @param prefix  The prefix\n   * @return A {@link SearchableMap} representing a mutable view of the original Map at the given prefix\n   */\n  atPrefix(prefix: string): SearchableMap<Value> {\n    if (!prefix.startsWith(this._prefix)) throw new Error(\"Mismatched prefix\");\n\n    const [node, path] = trackDown(\n      this._tree,\n      prefix.slice(this._prefix.length),\n    );\n\n    if (node === undefined) {\n      const [parentNode, key] = last(path);\n\n      for (const k of parentNode.keys())\n        if (k !== LEAF && k.startsWith(key)) {\n          const node = new Map();\n\n          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n          node.set(k.slice(key.length), parentNode.get(k)!);\n\n          return new SearchableMap<Value>(node, prefix);\n        }\n    }\n\n    return new SearchableMap<Value>(node, prefix);\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear\n   */\n  clear(): void {\n    this._size = undefined;\n    this._tree.clear();\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete\n   * @param key  Key to delete\n   */\n  delete(key: string): void {\n    this._size = undefined;\n\n    return remove(this._tree, key);\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries\n   * @return An iterator iterating through `[key, value]` entries.\n   */\n  entries(): TreeIterator<Value, \"ENTRIES\"> {\n    return new TreeIterator(this, ENTRIES);\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach\n   * @param fn  Iteration function\n   */\n  forEach(fn: (key: string, value: Value, map: SearchableMap) => void): void {\n    for (const [key, value] of this) fn(key, value, this);\n  }\n\n  /**\n   * Returns a Map of all the entries that have a key within the given edit\n   * distance from the search key. The keys of the returned Map are the matching\n   * keys, while the values are two-element arrays where the first element is\n   * the value associated to the key, and the second is the edit distance of the\n   * key to the search key.\n   *\n   * ### Usage:\n   *\n   * ```js\n   * const map = new SearchableMap()\n   * map.set('hello', 'world')\n   * map.set('hell', 'yeah')\n   * map.set('ciao', 'mondo')\n   *\n   * // Get all entries that match the key 'hallo' with a maximum edit distance of 2\n   * map.fuzzyGet('hallo', 2)\n   * // => Map(2) { 'hello' => ['world', 1], 'hell' => ['yeah', 2] }\n   *\n   * // In the example, the \"hello\" key has value \"world\" and edit distance of 1\n   * // (change \"e\" to \"a\"), the key \"hell\" has value \"yeah\" and edit distance of 2\n   * // (change \"e\" to \"a\", delete \"o\")\n   * ```\n   *\n   * @param key  The search key\n   * @param maxEditDistance  The maximum edit distance (Levenshtein)\n   * @return A Map of the matching keys to their value and edit distance\n   */\n  fuzzyGet(key: string, maxEditDistance: number): FuzzyResults<Value> {\n    return fuzzySearch<Value>(this._tree, key, maxEditDistance);\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get\n   * @param key  Key to get\n   * @return Value associated to the key, or `undefined` if the key is not\n   * found.\n   */\n  get(key: string): Value | undefined {\n    const node = lookup<Value>(this._tree, key);\n\n    return node !== undefined ? node.get(LEAF) : undefined;\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has\n   * @param key  Key\n   * @return True if the key is in the map, false otherwise\n   */\n  has(key: string): boolean {\n    const node = lookup(this._tree, key);\n\n    return node?.has(LEAF) ?? false;\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys\n   * @return An `Iterable` iterating through keys\n   */\n  keys(): TreeIterator<Value, \"KEYS\"> {\n    return new TreeIterator(this, KEYS);\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set\n   * @param key  Key to set\n   * @param value  Value to associate to the key\n   * @return The {@link SearchableMap} itself, to allow chaining\n   */\n  set(key: string, value: Value): this {\n    if (typeof key !== \"string\") throw new Error(\"key must be a string\");\n\n    this._size = undefined;\n    const node = createPath(this._tree, key);\n\n    node.set(LEAF, value);\n\n    return this;\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size\n   */\n  get size(): number {\n    if (this._size) return this._size;\n\n    /** @ignore */\n    this._size = 0;\n\n    const iter = this.entries();\n\n    while (!iter.next().done) this._size += 1;\n\n    return this._size;\n  }\n\n  /**\n   * Updates the value at the given key using the provided function. The function\n   * is called with the current value at the key, and its return value is used as\n   * the new value to be set.\n   *\n   * ### Example:\n   *\n   * ```js\n   * // Increment the current value by one\n   * searchableMap.update('somekey', (currentValue) => currentValue == null ? 0 : currentValue + 1)\n   * ```\n   *\n   * If the value at the given key is or will be an object, it might not require\n   * re-assignment. In that case it is better to use `fetch()`, because it is\n   * faster.\n   *\n   * @param key  The key to update\n   * @param fn  The function used to compute the new value from the current one\n   * @return The {@link SearchableMap} itself, to allow chaining\n   */\n  update(key: string, fn: (value: Value | undefined) => Value): this {\n    if (typeof key !== \"string\") throw new Error(\"key must be a string\");\n\n    this._size = undefined;\n    const node = createPath(this._tree, key);\n\n    node.set(LEAF, fn(node.get(LEAF)));\n\n    return this;\n  }\n\n  /**\n   * Fetches the value of the given key. If the value does not exist, calls the\n   * given function to create a new value, which is inserted at the given key\n   * and subsequently returned.\n   *\n   * ### Example:\n   *\n   * ```js\n   * const map = searchableMap.fetch('somekey', () => new Map())\n   * map.set('foo', 'bar')\n   * ```\n   *\n   * @param key  The key to update\n   * @param initial  A function that creates a new value if the key does not exist\n   * @return The existing or new value at the given key\n   */\n  fetch(key: string, initial: () => Value): Value {\n    if (typeof key !== \"string\") throw new Error(\"key must be a string\");\n\n    this._size = undefined;\n    const node = createPath(this._tree, key);\n\n    let value = node.get(LEAF);\n\n    if (value === undefined) node.set(LEAF, (value = initial()));\n\n    return value;\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values\n   * @return An `Iterable` iterating through values.\n   */\n  values(): TreeIterator<Value, \"VALUES\"> {\n    return new TreeIterator(this, VALUES);\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/@@iterator\n   */\n  [Symbol.iterator](): TreeIterator<Value, \"ENTRIES\"> {\n    return this.entries();\n  }\n\n  /**\n   * Creates a {@link SearchableMap} from an `Iterable` of entries\n   *\n   * @param entries  Entries to be inserted in the {@link SearchableMap}\n   * @return A new {@link SearchableMap} with the given entries\n   */\n  static from<T = any>(\n    entries: Iterable<Entry<T>> | Entry<T>[],\n  ): SearchableMap<T> {\n    const tree = new SearchableMap<T>();\n\n    for (const [key, value] of entries) tree.set(key, value);\n\n    return tree;\n  }\n\n  /**\n   * Creates a {@link SearchableMap} from the iterable properties of a JavaScript object\n   *\n   * @param object  Object of entries for the {@link SearchableMap}\n   * @return A new {@link SearchableMap} with the given entries\n   */\n  static fromObject<T = any>(object: Record<string, T>): SearchableMap<T> {\n    return SearchableMap.from<T>(Object.entries(object));\n  }\n}\n\nconst trackDown = <T = any>(\n  tree: RadixTree<T> | undefined,\n  key: string,\n  path: Path<T> = [],\n): [RadixTree<T> | undefined, Path<T>] => {\n  if (key.length === 0 || tree == null) return [tree, path];\n\n  for (const treeKey of tree.keys())\n    if (treeKey !== LEAF && key.startsWith(treeKey)) {\n      path.push([tree, treeKey]); // performance: update in place\n\n      return trackDown(tree.get(treeKey), key.slice(treeKey.length), path);\n    }\n\n  path.push([tree, key]); // performance: update in place\n\n  return trackDown(undefined, \"\", path);\n};\n\nconst lookup = <T = any>(\n  tree: RadixTree<T>,\n  key: string,\n): RadixTree<T> | undefined => {\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (key.length === 0 || !tree) return tree;\n\n  for (const treeKey of tree.keys())\n    if (treeKey !== LEAF && key.startsWith(treeKey))\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      return lookup(tree.get(treeKey)!, key.slice(treeKey.length));\n};\n\n// Create a path in the radix tree for the given key, and returns the deepest\n// node. This function is in the hot path for indexing. It avoids unnecessary\n// string operations and recursion for performance.\nconst createPath = <T = any>(node: RadixTree<T>, key: string): RadixTree<T> => {\n  const keyLength = key.length;\n\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  outer: for (let pos = 0; node && pos < keyLength; ) {\n    // Check whether this key is a candidate: the first characters must match.\n    for (const k of node.keys())\n      if (k !== LEAF && key[pos] === k[0]) {\n        const len = Math.min(keyLength - pos, k.length);\n\n        // Advance offset to the point where key and k no longer match.\n        let offset = 1;\n\n        while (offset < len && key[pos + offset] === k[offset]) ++offset;\n\n        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n        const child = node.get(k)!;\n\n        if (offset === k.length) {\n          // The existing key is shorter than the key we need to create.\n          node = child;\n        } else {\n          // Partial match: we need to insert an intermediate node to contain\n          // both the existing subtree and the new node.\n          const intermediate = new Map();\n\n          intermediate.set(k.slice(offset), child);\n          node.set(key.slice(pos, pos + offset), intermediate);\n          node.delete(k);\n          node = intermediate;\n        }\n\n        pos += offset;\n        continue outer;\n      }\n\n    // Create a final child node to contain the final suffix of the key.\n    const child = new Map();\n\n    node.set(key.slice(pos), child);\n\n    return child;\n  }\n\n  return node;\n};\n\nconst remove = <T = any>(tree: RadixTree<T>, key: string): void => {\n  const [node, path] = trackDown(tree, key);\n\n  if (node === undefined) return;\n\n  node.delete(LEAF);\n\n  if (node.size === 0) {\n    cleanup(path);\n  } else if (node.size === 1) {\n    const [key, value] = (\n      node.entries().next() as IteratorResult<\n        [string, RadixTree<T>],\n        [string, RadixTree<T>]\n      >\n    ).value;\n\n    merge(path, key, value);\n  }\n};\n\nconst cleanup = <T = any>(path: Path<T>): void => {\n  if (path.length === 0) return;\n\n  const [node, key] = last(path);\n\n  node.delete(key);\n\n  if (node.size === 0) {\n    cleanup(path.slice(0, -1));\n  } else if (node.size === 1) {\n    const [key, value] = (\n      node.entries().next() as IteratorResult<\n        [string, RadixTree<T>],\n        [string, RadixTree<T>]\n      >\n    ).value;\n\n    if (key !== LEAF) merge(path.slice(0, -1), key, value);\n  }\n};\n\nconst merge = <T = any>(\n  path: Path<T>,\n  key: string,\n  value: RadixTree<T>,\n): void => {\n  if (path.length === 0) return;\n\n  const [node, nodeKey] = last(path);\n\n  node.set(nodeKey + key, value);\n  node.delete(nodeKey);\n};\n\nconst last = <T = any>(array: T[]): T => {\n  return array[array.length - 1];\n};\n","import type { SearchIndex } from \"./SearchIndex.js\";\n\n/**\n * Returns `true` if a document with the given ID is present in the index and\n * available for search, `false` otherwise\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex The search index\n * @param id  The document ID\n */\nexport const has = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  id: ID,\n): boolean => searchIndex._idToShortId.has(id);\n\n/**\n * Returns the stored fields (as configured in the `storeFields` constructor\n * option) for the given document ID. Returns `undefined` if the document is\n * not present in the index.\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex The search index\n * @param id  The document ID\n * @returns The stored document index\n */\nexport const getStoredFields = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  id: ID,\n): Index | undefined => {\n  const shortId = searchIndex._idToShortId.get(id);\n\n  if (shortId == null) return undefined;\n\n  return searchIndex._storedFields.get(shortId);\n};\n","// This regular expression matches any Unicode space, newline, or punctuation\n// character\nexport const SPACE_OR_PUNCTUATION = /[\\n\\r\\p{Z}\\p{P}]+/u;\n\nexport const OR = \"or\";\nexport const AND = \"and\";\nexport const AND_NOT = \"and_not\";\n","import { AND, AND_NOT, OR } from \"./constant.js\";\nimport type {\n  BM25Params,\n  LowercaseCombinationOperator,\n  MatchInfo,\n  SearchOptions,\n} from \"./typings.js\";\n\nexport const wait = (ms: number): Promise<void> =>\n  new Promise((resolve) => setTimeout(resolve, ms));\n\nexport const assignUniqueTerm = (target: string[], term: string): void => {\n  // Avoid adding duplicate terms.\n  if (!target.includes(term)) target.push(term);\n};\n\nexport const assignUniqueTerms = (\n  target: string[],\n  source: readonly string[],\n): void => {\n  // Avoid adding duplicate terms.\n  for (const term of source) if (!target.includes(term)) target.push(term);\n};\n\ninterface Scored {\n  score: number;\n}\n\nexport const byScore = ({ score: a }: Scored, { score: b }: Scored): number =>\n  b - a;\n\nexport const createMap = <K, V>(): Map<K, V> => new Map<K, V>();\n\nexport const objectToNumericMap = <Value>(\n  object: Record<string, Value>,\n): Map<number, Value> => {\n  const map = new Map<number, Value>();\n\n  for (const key of Object.keys(object))\n    map.set(parseInt(key, 10), object[key]);\n\n  return map;\n};\n\nexport const objectToNumericMapAsync = async <Value>(\n  object: Record<string, Value>,\n): Promise<Map<number, Value>> => {\n  const map = new Map();\n  let count = 0;\n\n  for (const key of Object.keys(object)) {\n    map.set(parseInt(key, 10), object[key]);\n    if (++count % 1000 === 0) {\n      await wait(0);\n    }\n  }\n\n  return map;\n};\n\nexport const getOwnProperty = (object: any, property: string): unknown =>\n  Object.prototype.hasOwnProperty.call(object, property)\n    ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n      object[property]\n    : undefined;\n\ninterface RawResultValue {\n  // Intermediate score, before applying the final score based on number of\n  // matched terms.\n  score: number;\n\n  // Set of all query terms that were matched. They may not be present in the\n  // text exactly in the case of prefix/fuzzy matches. We must check for\n  // uniqueness before adding a new term. This is much faster than using a set,\n  // because the number of elements is relatively small.\n  terms: string[];\n\n  // All terms that were found in the content, including the fields in which\n  // they were present. This object will be provided as part of the final search\n  // results.\n  match: MatchInfo;\n}\n\nexport type RawResult = Map<number, RawResultValue>;\n\nexport type CombinatorFunction = (a: RawResult, b: RawResult) => RawResult;\n\nexport const combinators: Record<\n  LowercaseCombinationOperator,\n  CombinatorFunction\n> = {\n  [OR]: (a: RawResult, b: RawResult) => {\n    for (const docId of b.keys()) {\n      const existing = a.get(docId);\n\n      if (existing == null) {\n        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n        a.set(docId, b.get(docId)!);\n      } else {\n        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n        const { score, terms, match } = b.get(docId)!;\n\n        existing.score = existing.score + score;\n        existing.match = Object.assign(existing.match, match);\n        assignUniqueTerms(existing.terms, terms);\n      }\n    }\n\n    return a;\n  },\n  [AND]: (a: RawResult, b: RawResult) => {\n    const combined = new Map();\n\n    for (const docId of b.keys()) {\n      const existing = a.get(docId);\n\n      if (existing == null) continue;\n\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      const { score, terms, match } = b.get(docId)!;\n\n      assignUniqueTerms(existing.terms, terms);\n      combined.set(docId, {\n        score: existing.score + score,\n        terms: existing.terms,\n        match: Object.assign(existing.match, match),\n      });\n    }\n\n    return combined;\n  },\n  [AND_NOT]: (a: RawResult, b: RawResult) => {\n    for (const docId of b.keys()) a.delete(docId);\n\n    return a;\n  },\n};\n\nexport const calcBM25Score = (\n  termFreq: number,\n  matchingCount: number,\n  totalCount: number,\n  fieldLength: number,\n  avgFieldLength: number,\n  bm25params: BM25Params,\n): number => {\n  const { k, b, d } = bm25params;\n  const invDocFreq = Math.log(\n    1 + (totalCount - matchingCount + 0.5) / (matchingCount + 0.5),\n  );\n\n  return (\n    invDocFreq *\n    (d +\n      (termFreq * (k + 1)) /\n        (termFreq + k * (1 - b + (b * fieldLength) / avgFieldLength)))\n  );\n};\n\nexport interface QuerySpec {\n  prefix: boolean;\n  fuzzy: number | boolean;\n  term: string;\n  termBoost: number;\n}\n\nexport const termToQuerySpec =\n  (options: SearchOptions) =>\n  (term: string, index: number, terms: string[]): QuerySpec => ({\n    term,\n    fuzzy:\n      typeof options.fuzzy === \"function\"\n        ? options.fuzzy(term, index, terms)\n        : (options.fuzzy ?? false),\n    prefix:\n      typeof options.prefix === \"function\"\n        ? options.prefix(term, index, terms)\n        : options.prefix === true,\n    termBoost:\n      typeof options.boostTerm === \"function\"\n        ? options.boostTerm(term, index, terms)\n        : 1,\n  });\n","import type { SearchIndex } from \"./SearchIndex.js\";\n\nexport const warnDocumentChanged = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  shortDocumentId: number,\n  fieldId: number,\n  term: string,\n): void => {\n  for (const fieldName of Object.keys(searchIndex._fieldIds))\n    if (searchIndex._fieldIds[fieldName] === fieldId) {\n      searchIndex._options.logger(\n        \"warn\",\n        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n        `SlimSearch: document with ID ${searchIndex._documentIds.get(\n          shortDocumentId,\n        )} has changed before removal: term \"${term}\" was not present in field \"${fieldName}\". Removing a document after it has changed can corrupt the index!`,\n        \"version_conflict\",\n      );\n\n      return;\n    }\n};\n","import type { SearchIndex } from \"./SearchIndex.js\";\nimport { createMap } from \"./utils.js\";\nimport { warnDocumentChanged } from \"./warning.js\";\n\n/**\n * @private\n */\nexport const addTerm = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  fieldId: number,\n  documentId: number,\n  term: string,\n): void => {\n  const indexData = searchIndex._index.fetch(term, createMap);\n\n  let fieldIndex = indexData.get(fieldId);\n\n  if (fieldIndex == null) {\n    fieldIndex = new Map();\n    fieldIndex.set(documentId, 1);\n    indexData.set(fieldId, fieldIndex);\n  } else {\n    const docs = fieldIndex.get(documentId);\n\n    fieldIndex.set(documentId, (docs ?? 0) + 1);\n  }\n};\n\n/**\n * @private\n */\nexport const removeTerm = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  fieldId: number,\n  documentId: number,\n  term: string,\n): void => {\n  if (!searchIndex._index.has(term)) {\n    warnDocumentChanged(searchIndex, documentId, fieldId, term);\n\n    return;\n  }\n\n  const indexData = searchIndex._index.fetch(term, createMap);\n\n  const fieldIndex = indexData.get(fieldId);\n\n  const amount = fieldIndex?.get(documentId);\n\n  if (!fieldIndex || typeof amount === \"undefined\")\n    warnDocumentChanged(searchIndex, documentId, fieldId, term);\n  else if (amount <= 1)\n    if (fieldIndex.size <= 1) indexData.delete(fieldId);\n    else fieldIndex.delete(documentId);\n  else fieldIndex.set(documentId, amount - 1);\n\n  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n  if (searchIndex._index.get(term)!.size === 0) searchIndex._index.delete(term);\n};\n","import type { SearchIndex } from \"./SearchIndex.js\";\nimport { has } from \"./info.js\";\nimport { addTerm } from \"./term.js\";\n\nconst addFieldLength = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  documentId: number,\n  fieldId: number,\n  count: number,\n  length: number,\n): void => {\n  let fieldLengths = searchIndex._fieldLength.get(documentId);\n\n  if (fieldLengths == null)\n    searchIndex._fieldLength.set(documentId, (fieldLengths = []));\n  fieldLengths[fieldId] = length;\n\n  const averageFieldLength = searchIndex._avgFieldLength[fieldId] || 0;\n  const totalFieldLength = averageFieldLength * count + length;\n\n  searchIndex._avgFieldLength[fieldId] = totalFieldLength / (count + 1);\n};\n\nconst addDocumentId = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  documentId: ID,\n): number => {\n  const shortDocumentId = searchIndex._nextId;\n\n  searchIndex._idToShortId.set(documentId, shortDocumentId);\n  searchIndex._documentIds.set(shortDocumentId, documentId);\n  searchIndex._documentCount += 1;\n  searchIndex._nextId += 1;\n\n  return shortDocumentId;\n};\n\nconst saveStoredFields = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  documentId: number,\n  doc: Document,\n): void => {\n  const { storeFields, extractField } = searchIndex._options;\n\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (storeFields?.length === 0) return;\n\n  let documentFields: Record<string, unknown> | undefined =\n    searchIndex._storedFields.get(documentId);\n\n  if (documentFields === undefined)\n    searchIndex._storedFields.set(documentId, (documentFields = {} as Index));\n\n  for (const fieldName of storeFields) {\n    const fieldValue = extractField(doc, fieldName);\n\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (fieldValue != null) documentFields[fieldName] = fieldValue;\n  }\n};\n\n/**\n * Adds a document to the index\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex  The search index\n * @param document  The document to be indexed\n */\nexport const add = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  document: Document,\n): void => {\n  const { extractField, tokenize, processTerm, fields, idField } =\n    searchIndex._options;\n  const id = extractField(document, idField) as ID;\n\n  if (id == null)\n    throw new Error(`SlimSearch: document does not have ID field \"${idField}\"`);\n\n  if (has(searchIndex, id))\n    throw new Error(`SlimSearch: duplicate ID ${id as string}`);\n\n  const shortDocumentId = addDocumentId(searchIndex, id);\n\n  saveStoredFields(searchIndex, shortDocumentId, document);\n\n  for (const field of fields) {\n    const fieldValue = extractField(document, field);\n\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (fieldValue == null) continue;\n\n    const tokens = tokenize(fieldValue.toString(), field);\n    const fieldId = searchIndex._fieldIds[field];\n\n    const uniqueTerms = new Set(tokens).size;\n\n    addFieldLength(\n      searchIndex,\n      shortDocumentId,\n      fieldId,\n      searchIndex._documentCount - 1,\n      uniqueTerms,\n    );\n\n    for (const term of tokens) {\n      const processedTerm = processTerm(term, field);\n\n      if (Array.isArray(processedTerm))\n        for (const t of processedTerm)\n          addTerm(searchIndex, fieldId, shortDocumentId, t);\n      else if (processedTerm)\n        addTerm(searchIndex, fieldId, shortDocumentId, processedTerm);\n    }\n  }\n};\n\n/**\n * Adds all the given documents to the index\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex  The search index\n * @param documents  An array of documents to be indexed\n */\nexport const addAll = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  documents: readonly Document[],\n): void => {\n  for (const document of documents) add(searchIndex, document);\n};\n\n/**\n * Adds all the given documents to the index asynchronously.\n *\n * Returns a promise that resolves (to `undefined`) when the indexing is done.\n * This method is useful when index many documents, to avoid blocking the main\n * thread. The indexing is performed asynchronously and in chunks.\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex  The search index\n * @param documents  An array of documents to be indexed\n * @param options  Configuration options\n * @return A promise resolving when the indexing is done\n */\nexport const addAllAsync = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  documents: readonly Document[],\n  options: { chunkSize?: number } = {},\n): Promise<void> => {\n  const { chunkSize = 10 } = options;\n  const acc: { chunk: Document[]; promise: Promise<void> } = {\n    chunk: [],\n    promise: Promise.resolve(),\n  };\n\n  const { chunk, promise } = documents.reduce(\n    ({ chunk, promise }, document, index) => {\n      chunk.push(document);\n      if ((index + 1) % chunkSize === 0)\n        return {\n          chunk: [],\n          promise: promise\n            .then(() => new Promise((resolve) => setTimeout(resolve, 0)))\n            .then(() => addAll(searchIndex, chunk)),\n        };\n\n      return { chunk, promise };\n    },\n    acc,\n  );\n\n  return promise.then(() => addAll(searchIndex, chunk));\n};\n","import { AND, OR, SPACE_OR_PUNCTUATION } from \"./constant.js\";\nimport type { BM25Params, LogLevel } from \"./typings.js\";\nimport { getOwnProperty } from \"./utils.js\";\n\nexport const defaultBM25params: BM25Params = { k: 1.2, b: 0.7, d: 0.5 };\n\nexport const defaultOptions = {\n  idField: \"id\",\n  extractField: (document: any, fieldName: string): unknown =>\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n    document[fieldName],\n  tokenize: (text: string): string[] => text.split(SPACE_OR_PUNCTUATION),\n  processTerm: (term: string): string => term.toLowerCase(),\n  fields: undefined,\n  searchOptions: undefined,\n  storeFields: [],\n  logger: (level: LogLevel, message: string): void => {\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    console?.[level]?.(message);\n  },\n  autoVacuum: true,\n} as const;\n\nexport const defaultSearchOptions = {\n  combineWith: OR,\n  prefix: false,\n  fuzzy: false,\n  maxFuzzy: 6,\n  boost: {},\n  weights: { fuzzy: 0.45, prefix: 0.375 },\n  bm25: defaultBM25params,\n} as const;\n\nexport const defaultAutoSuggestOptions = {\n  combineWith: AND,\n  prefix: (_term: string, index: number, terms: string[]): boolean =>\n    index === terms.length - 1,\n} as const;\n\nexport const defaultVacuumOptions = { batchSize: 1000, batchWait: 10 };\nexport const defaultVacuumConditions = { minDirtFactor: 0.1, minDirtCount: 20 };\n\nexport const defaultAutoVacuumOptions = {\n  ...defaultVacuumOptions,\n  ...defaultVacuumConditions,\n};\n\n/**\n * Returns the default value of an option. It will throw an error if no option\n * with the given name exists.\n *\n * ### Usage:\n *\n * ```js\n * // Get default tokenizer\n * getDefaultValue('tokenize')\n *\n * // Get default term processor\n * getDefaultValue('processTerm')\n *\n * // Unknown options will throw an error\n * getDefaultValue('notExisting')\n * // => throws 'SlimSearch: unknown option \"notExisting\"'\n * ```\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param optionName  Name of the option\n * @return The default value of the given option\n */\nexport const getDefaultValue = (optionName: string): unknown => {\n  // eslint-disable-next-line no-prototype-builtins\n  if (defaultOptions.hasOwnProperty(optionName))\n    return getOwnProperty(defaultOptions, optionName);\n  else throw new Error(`SlimSearch: unknown option \"${optionName}\"`);\n};\n","export const WILDCARD = Symbol(\"*\");\n","import type { FieldTermData, SearchIndex } from \"./SearchIndex.js\";\nimport { OR } from \"./constant.js\";\nimport { defaultSearchOptions } from \"./defaults.js\";\nimport { WILDCARD } from \"./symbols.js\";\nimport { removeTerm } from \"./term.js\";\nimport type {\n  BM25Params,\n  CombinationOperator,\n  LowercaseCombinationOperator,\n  Query,\n  SearchOptions,\n} from \"./typings.js\";\nimport type { QuerySpec, RawResult } from \"./utils.js\";\nimport {\n  assignUniqueTerm,\n  calcBM25Score,\n  combinators,\n  getOwnProperty,\n  termToQuerySpec,\n} from \"./utils.js\";\n\nexport interface SearchOptionsWithDefaults<\n  ID = any,\n  Index extends Record<string, any> = Record<string, never>,\n> extends SearchOptions<ID, Index> {\n  boost: Record<string, number>;\n\n  weights: { fuzzy: number; prefix: number };\n\n  prefix: boolean | ((term: string, index: number, terms: string[]) => boolean);\n\n  fuzzy:\n    | boolean\n    | number\n    | ((term: string, index: number, terms: string[]) => boolean | number);\n\n  maxFuzzy: number;\n\n  combineWith: CombinationOperator;\n\n  bm25: BM25Params;\n}\n\nexport type DocumentTermFrequencies = Map<number, number>;\n\nconst executeWildcardQuery = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  searchOptions: SearchOptions<ID, Index>,\n): RawResult => {\n  const results = new Map() as RawResult;\n  // @ts-expect-error: some option is optional\n  const options: SearchOptionsWithDefaults<ID, Index> = {\n    ...searchIndex._options.searchOptions,\n    ...searchOptions,\n  };\n\n  for (const [shortId, id] of searchIndex._documentIds) {\n    const score = options.boostDocument\n      ? options.boostDocument(id, \"\", searchIndex._storedFields.get(shortId))\n      : 1;\n\n    results.set(shortId, {\n      score,\n      terms: [],\n      match: {},\n    });\n  }\n\n  return results;\n};\n\nconst combineResults = (\n  results: RawResult[],\n  combineWith: CombinationOperator = OR,\n): RawResult => {\n  if (results.length === 0) return new Map();\n\n  const operator = combineWith.toLowerCase() as LowercaseCombinationOperator;\n\n  if (!(operator in combinators))\n    throw new Error(`Invalid combination operator: ${combineWith}`);\n\n  return results.reduce(combinators[operator]);\n};\n\nconst termResults = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  sourceTerm: string,\n  derivedTerm: string,\n  termWeight: number,\n  termBoost: number,\n  fieldTermData: FieldTermData | undefined,\n  fieldBoosts: Record<string, number>,\n  boostDocumentFn:\n    | ((id: ID, term: string, storedFields?: Index) => number)\n    | undefined,\n  bm25params: BM25Params,\n  results: RawResult = new Map(),\n): RawResult => {\n  if (fieldTermData == null) return results;\n\n  for (const field of Object.keys(fieldBoosts)) {\n    const fieldBoost = fieldBoosts[field];\n    const fieldId = searchIndex._fieldIds[field];\n\n    const fieldTermFrequencies = fieldTermData.get(fieldId);\n\n    if (fieldTermFrequencies == null) continue;\n\n    let matchingFields = fieldTermFrequencies.size;\n    const avgFieldLength = searchIndex._avgFieldLength[fieldId];\n\n    for (const docId of fieldTermFrequencies.keys()) {\n      if (!searchIndex._documentIds.has(docId)) {\n        removeTerm(searchIndex, fieldId, docId, derivedTerm);\n        matchingFields -= 1;\n        continue;\n      }\n\n      const docBoost = boostDocumentFn\n        ? boostDocumentFn(\n            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n            searchIndex._documentIds.get(docId)!,\n            derivedTerm,\n            searchIndex._storedFields.get(docId),\n          )\n        : 1;\n\n      if (!docBoost) continue;\n\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      const termFreq = fieldTermFrequencies.get(docId)!;\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      const fieldLength = searchIndex._fieldLength.get(docId)![fieldId];\n\n      // NOTE: The total number of fields is set to the number of documents\n      // `this._documentCount`. It could also make sense to use the number of\n      // documents where the current field is non-blank as a normalization\n      // factor. This will make a difference in scoring if the field is rarely\n      // present. This is currently not supported, and may require further\n      // analysis to see if it is a valid use case.\n      const rawScore = calcBM25Score(\n        termFreq,\n        matchingFields,\n        searchIndex._documentCount,\n        fieldLength,\n        avgFieldLength,\n        bm25params,\n      );\n      const weightedScore =\n        termWeight * termBoost * fieldBoost * docBoost * rawScore;\n\n      const result = results.get(docId);\n\n      if (result) {\n        result.score += weightedScore;\n        assignUniqueTerm(result.terms, sourceTerm);\n        const match = getOwnProperty(result.match, derivedTerm) as\n          | string[]\n          | undefined;\n\n        if (match) match.push(field);\n        else result.match[derivedTerm] = [field];\n      } else {\n        results.set(docId, {\n          score: weightedScore,\n          terms: [sourceTerm],\n          match: { [derivedTerm]: [field] },\n        });\n      }\n    }\n  }\n\n  return results;\n};\n\nconst executeQuerySpec = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  query: QuerySpec,\n  searchOptions: SearchOptions<ID, Index>,\n): RawResult => {\n  // @ts-expect-error: some option is optional\n  const options: SearchOptionsWithDefaults<ID, Index> = {\n    ...searchIndex._options.searchOptions,\n    ...searchOptions,\n  };\n\n  const boosts = (options.fields ?? searchIndex._options.fields).reduce(\n    (boosts, field) => ({\n      ...boosts,\n      [field]: getOwnProperty(options.boost, field) || 1,\n    }),\n    {},\n  );\n\n  const { boostDocument, weights, maxFuzzy, bm25: bm25params } = options;\n\n  const { fuzzy: fuzzyWeight, prefix: prefixWeight } = {\n    ...defaultSearchOptions.weights,\n    ...weights,\n  };\n\n  const data = searchIndex._index.get(query.term);\n  const results = termResults(\n    searchIndex,\n    query.term,\n    query.term,\n    1,\n    query.termBoost,\n    data,\n    boosts,\n    boostDocument,\n    bm25params,\n  );\n\n  let prefixMatches;\n  let fuzzyMatches;\n\n  if (query.prefix) prefixMatches = searchIndex._index.atPrefix(query.term);\n\n  if (query.fuzzy) {\n    const fuzzy = query.fuzzy === true ? 0.2 : query.fuzzy;\n    const maxDistance =\n      fuzzy < 1\n        ? Math.min(maxFuzzy, Math.round(query.term.length * fuzzy))\n        : fuzzy;\n\n    if (maxDistance)\n      fuzzyMatches = searchIndex._index.fuzzyGet(query.term, maxDistance);\n  }\n\n  if (prefixMatches)\n    for (const [term, data] of prefixMatches) {\n      const distance = term.length - query.term.length;\n\n      if (!distance) continue;\n      // Skip exact match.\n\n      // Delete the term from fuzzy results (if present) if it is also a\n      // prefix result. This entry will always be scored as a prefix result.\n      fuzzyMatches?.delete(term);\n\n      // Weight gradually approaches 0 as distance goes to infinity, with the\n      // weight for the hypothetical distance 0 being equal to prefixWeight.\n      // The rate of change is much lower than that of fuzzy matches to\n      // account for the fact that prefix matches stay more relevant than\n      // fuzzy matches for longer distances.\n      const weight =\n        (prefixWeight * term.length) / (term.length + 0.3 * distance);\n\n      termResults(\n        searchIndex,\n        query.term,\n        term,\n        weight,\n        query.termBoost,\n        data,\n        boosts,\n        boostDocument,\n        bm25params,\n        results,\n      );\n    }\n\n  if (fuzzyMatches)\n    for (const term of fuzzyMatches.keys()) {\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      const [data, distance] = fuzzyMatches.get(term)!;\n\n      if (!distance) continue;\n      // Skip exact match.\n\n      // Weight gradually approaches 0 as distance goes to infinity, with the\n      // weight for the hypothetical distance 0 being equal to fuzzyWeight.\n      const weight = (fuzzyWeight * term.length) / (term.length + distance);\n\n      termResults(\n        searchIndex,\n        query.term,\n        term,\n        weight,\n        query.termBoost,\n        data,\n        boosts,\n        boostDocument,\n        bm25params,\n        results,\n      );\n    }\n\n  return results;\n};\n\nexport const executeQuery = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  query: Query,\n  searchOptions: SearchOptions<ID, Index> = {},\n): RawResult => {\n  if (query === WILDCARD)\n    return executeWildcardQuery(searchIndex, searchOptions);\n\n  if (typeof query !== \"string\") {\n    const options = { ...searchOptions, ...query, queries: undefined };\n    const results = query.queries.map((subQuery) =>\n      executeQuery(searchIndex, subQuery, options),\n    );\n\n    return combineResults(results, options.combineWith);\n  }\n\n  const {\n    tokenize,\n    processTerm,\n    searchOptions: globalSearchOptions,\n  } = searchIndex._options;\n  const options = {\n    tokenize,\n    processTerm,\n    ...globalSearchOptions,\n    ...searchOptions,\n  };\n  const { tokenize: searchTokenize, processTerm: searchProcessTerm } = options;\n  // @ts-expect-error: type is not the same\n  const terms = searchTokenize(query)\n    // @ts-expect-error: type is not the same\n    .flatMap((term: string) => searchProcessTerm(term))\n    .filter((term) => !!term) as string[];\n  // @ts-expect-error: type is not the same\n  const queries: QuerySpec[] = terms.map(termToQuerySpec(options));\n  const results = queries.map((query) =>\n    // @ts-expect-error: type is not the same\n    executeQuerySpec(searchIndex, query, options),\n  );\n\n  return combineResults(results, options.combineWith);\n};\n","import type { SearchIndex } from \"./SearchIndex.js\";\nimport { executeQuery } from \"./results.js\";\nimport { WILDCARD } from \"./symbols.js\";\nimport type { Query, SearchOptions, SearchResult } from \"./typings.js\";\nimport { byScore } from \"./utils.js\";\n\n/**\n * Search for documents matching the given search query.\n *\n * The result is a list of scored document IDs matching the query, sorted by\n * descending score, and each including data about which terms were matched and\n * in which fields.\n *\n * ### Basic usage:\n *\n * ```js\n * // Search for \"zen art motorcycle\" with default options: terms have to match\n * // exactly, and individual terms are joined with OR\n * search(searchIndex, 'zen art motorcycle')\n * // => [ { id: 2, score: 2.77258, match: { ... } }, { id: 4, score: 1.38629, match: { ... } } ]\n * ```\n *\n * ### Restrict search to specific fields:\n *\n * ```js\n * // Search only in the 'title' field\n * search(searchIndex, 'zen', { fields: ['title'] })\n * ```\n *\n * ### Field boosting:\n *\n * ```js\n * // Boost a field\n * search(searchIndex, 'zen', { boost: { title: 2 } })\n * ```\n *\n * ### Prefix search:\n *\n * ```js\n * // Search for \"moto\" with prefix search (it will match documents\n * // containing terms that start with \"moto\" or \"neuro\")\n * search(searchIndex, 'moto neuro', { prefix: true })\n * ```\n *\n * ### Fuzzy search:\n *\n * ```js\n * // Search for \"ismael\" with fuzzy search (it will match documents containing\n * // terms similar to \"ismael\", with a maximum edit distance of 0.2 term.length\n * // (rounded to nearest integer)\n * search(searchIndex, 'ismael', { fuzzy: 0.2 })\n * ```\n *\n * ### Combining strategies:\n *\n * ```js\n * // Mix of exact match, prefix search, and fuzzy search\n * search(searchIndex, 'ismael mob', {\n *  prefix: true,\n *  fuzzy: 0.2\n * })\n * ```\n *\n * ### Advanced prefix and fuzzy search:\n *\n * ```js\n * // Perform fuzzy and prefix search depending on the search term. Here\n * // performing prefix and fuzzy search only on terms longer than 3 characters\n * search(searchIndex, 'ismael mob', {\n *  prefix: term => term.length > 3\n *  fuzzy: term => term.length > 3 ? 0.2 : null\n * })\n * ```\n *\n * ### Combine with AND:\n *\n * ```js\n * // Combine search terms with AND (to match only documents that contain both\n * // \"motorcycle\" and \"art\")\n * search(searchIndex, 'motorcycle art', { combineWith: 'AND' })\n * ```\n *\n * ### Combine with AND_NOT:\n *\n * There is also an AND_NOT combinator, that finds documents that match the\n * first term, but do not match any of the other terms. This combinator is\n * rarely useful with simple queries, and is meant to be used with advanced\n * query combinations (see later for more details).\n *\n * ### Filtering results:\n *\n * ```js\n * // Filter only results in the 'fiction' category (assuming that 'category'\n * // is a stored field)\n * search(searchIndex, 'motorcycle art', {\n *   filter: (result) => result.category === 'fiction'\n * })\n * ```\n *\n * ### Wildcard query\n *\n * Searching for an empty string (assuming the default tokenizer) returns no\n * results. Sometimes though, one needs to match all documents, like in a\n * \"wildcard\" search. This is possible by passing the special value\n * `wildcard` as the query:\n *\n * ```javascript\n * // Return search results for all documents\n * search(index, WILDCARD)\n * ```\n *\n * Note that search options such as `filter` and `boostDocument` are still\n * applied, influencing which results are returned, and their order:\n *\n * ```javascript\n * // Return search results for all documents in the 'fiction' category\n * search(index, WILDCARD, {\n *   filter: (result) => result.category === 'fiction'\n * })\n * ```\n *\n * ### Advanced combination of queries:\n *\n * It is possible to combine different subqueries with OR, AND, and AND_NOT,\n * and even with different search options, by passing a query expression\n * tree object as the first argument, instead of a string.\n *\n * ```js\n * // Search for documents that contain \"zen\" and (\"motorcycle\" or \"archery\")\n * search(searchIndex, {\n *   combineWith: 'AND',\n *   queries: [\n *     'zen',\n *     {\n *       combineWith: 'OR',\n *       queries: ['motorcycle', 'archery']\n *     }\n *   ]\n * })\n *\n * // Search for documents that contain (\"apple\" or \"pear\") but not \"juice\" and\n * // not \"tree\"\n * search(searchIndex, {\n *   combineWith: 'AND_NOT',\n *   queries: [\n *     {\n *       combineWith: 'OR',\n *       queries: ['apple', 'pear']\n *     },\n *     'juice',\n *     'tree'\n *   ]\n * })\n * ```\n *\n * Each node in the expression tree can be either a string, or an object that\n * supports all `SearchOptions` fields, plus a `queries` array field for\n * subqueries.\n *\n * Note that, while this can become complicated to do by hand for complex or\n * deeply nested queries, it provides a formalized expression tree API for\n * external libraries that implement a parser for custom query languages.\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex Search Index\n * @param query Search query\n * @param searchOptions Search options. Each option, if not given, defaults to the corresponding value of `searchOptions` given to the constructor, or to the library default.\n */\nexport const search = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Partial<Document>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  query: Query,\n  searchOptions: SearchOptions<ID, Index> = {},\n): SearchResult<ID, Index>[] => {\n  const { searchOptions: globalSearchOptions } = searchIndex._options;\n  const options = { ...globalSearchOptions, ...searchOptions };\n\n  const rawResults = executeQuery(searchIndex, query, searchOptions);\n\n  const results: SearchResult<ID, Index>[] = [];\n\n  for (const [docId, { score, terms, match }] of rawResults) {\n    // terms are the matched query terms, which will be returned to the user\n    // as queryTerms. The quality is calculated based on them, as opposed to\n    // the matched terms in the document (which can be different due to\n    // prefix and fuzzy match)\n    const quality = terms.length || 1;\n\n    const result = {\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      id: searchIndex._documentIds.get(docId)!,\n      score: score * quality,\n      terms: Object.keys(match),\n      queryTerms: terms,\n      match,\n    } as SearchResult<ID, Index>;\n\n    Object.assign(result, searchIndex._storedFields.get(docId));\n    if (options.filter == null || options.filter(result)) results.push(result);\n  }\n\n  // If it's a wildcard query, and no document boost is applied, skip sorting\n  // the results, as all results have the same score of 1\n  if (query === WILDCARD && options.boostDocument == null) return results;\n\n  results.sort(byScore);\n\n  return results;\n};\n","import type { SearchIndex } from \"./SearchIndex.js\";\nimport { search } from \"./search.js\";\nimport type { SearchOptions, Suggestion } from \"./typings.js\";\nimport { byScore } from \"./utils.js\";\n\n/**\n * Provide suggestions for the given search query\n *\n * The result is a list of suggested modified search queries, derived from the\n * given search query, each with a relevance score, sorted by descending score.\n *\n * By default, it uses the same options used for search, except that by\n * default it performs prefix search on the last term of the query, and\n * combine terms with `'AND'` (requiring all query terms to match). Custom\n * options can be passed as a second argument. Defaults can be changed by\n * passing an `autoSuggestOptions` option when initializing the index.\n *\n * ### Basic usage:\n *\n * ```js\n * // Get suggestions for 'neuro':\n * autoSuggest(searchIndex, 'neuro')\n * // => [ { suggestion: 'neuromancer', terms: [ 'neuromancer' ], score: 0.46240 } ]\n * ```\n *\n * ### Multiple words:\n *\n * ```js\n * // Get suggestions for 'zen ar':\n * autoSuggest(searchIndex, 'zen ar')\n * // => [\n * //  { suggestion: 'zen archery art', terms: [ 'zen', 'archery', 'art' ], score: 1.73332 },\n * //  { suggestion: 'zen art', terms: [ 'zen', 'art' ], score: 1.21313 }\n * // ]\n * ```\n *\n * ### Fuzzy suggestions:\n *\n * ```js\n * // Correct spelling mistakes using fuzzy search:\n * autoSuggest(searchIndex, 'neromancer', { fuzzy: 0.2 })\n * // => [ { suggestion: 'neuromancer', terms: [ 'neuromancer' ], score: 1.03998 } ]\n * ```\n *\n * ### Filtering:\n *\n * ```js\n * // Get suggestions for 'zen ar', but only within the 'fiction' category\n * // (assuming that 'category' is a stored field):\n * autoSuggest(searchIndex, 'zen ar', {\n *   filter: (result) => result.category === 'fiction'\n * })\n * // => [\n * //  { suggestion: 'zen archery art', terms: [ 'zen', 'archery', 'art' ], score: 1.73332 },\n * //  { suggestion: 'zen art', terms: [ 'zen', 'art' ], score: 1.21313 }\n * // ]\n * ```\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex The search index\n * @param queryString  Query string to be expanded into suggestions\n * @param options  Search options. The supported options and default values\n * are the same as for the `search` method, except that by default prefix\n * search is performed on the last term in the query, and terms are combined\n * with `'AND'`.\n * @return  A sorted array of suggestions sorted by relevance score.\n */\nexport const autoSuggest = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  queryString: string,\n  options: SearchOptions<ID, Index> = {},\n): Suggestion[] => {\n  options = { ...searchIndex._options.autoSuggestOptions, ...options };\n\n  const suggestions = new Map<\n    string,\n    Omit<Suggestion, \"suggestion\"> & { count: number }\n  >();\n\n  for (const { score, terms } of search(searchIndex, queryString, options)) {\n    const phrase = terms.join(\" \");\n    const suggestion = suggestions.get(phrase);\n\n    if (suggestion != null) {\n      suggestion.score += score;\n      suggestion.count += 1;\n    } else {\n      suggestions.set(phrase, { score, terms, count: 1 });\n    }\n  }\n\n  const results = [];\n\n  for (const [suggestion, { score, terms, count }] of suggestions)\n    results.push({ suggestion, terms, score: score / count });\n\n  results.sort(byScore);\n\n  return results;\n};\n","import { SearchableMap } from \"./SearchableMap/index.js\";\nimport {\n  defaultAutoSuggestOptions,\n  defaultAutoVacuumOptions,\n  defaultOptions,\n  defaultSearchOptions,\n  defaultVacuumConditions,\n} from \"./defaults.js\";\nimport type {\n  DocumentTermFrequencies,\n  SearchOptionsWithDefaults,\n} from \"./results.js\";\nimport type {\n  AutoVacuumOptions,\n  IndexObject,\n  LogLevel,\n  SearchIndexOptions,\n  SearchOptions,\n  SerializedIndexEntry,\n  VacuumConditions,\n} from \"./typings.js\";\n\ninterface OptionsWithDefaults<\n  ID = any,\n  Document = any,\n  Index extends Record<string, any> = Record<string, never>,\n> extends Omit<\n    SearchIndexOptions<ID, Document, Index>,\n    \"processTerm\" | \"tokenize\"\n  > {\n  storeFields: string[];\n\n  idField: string;\n\n  extractField: (document: Document, fieldName: string) => string;\n\n  tokenize: (text: string, fieldName: string) => string[];\n\n  processTerm: (\n    term: string,\n    fieldName: string,\n  ) => string | string[] | null | undefined | false;\n\n  logger: (level: LogLevel, message: string, code?: string) => void;\n\n  autoVacuum: false | AutoVacuumOptions;\n\n  searchOptions: SearchOptionsWithDefaults<ID, Index>;\n\n  autoSuggestOptions: SearchOptions<ID, Index>;\n}\n\nexport type FieldTermData = Map<number, DocumentTermFrequencies>;\n\n/**\n * A class to represent search index\n *\n * ### Basic example:\n *\n * ```js\n * const documents = [\n *   {\n *     id: 1,\n *     title: 'Moby Dick',\n *     text: 'Call me Ishmael. Some years ago...',\n *     category: 'fiction'\n *   },\n *   {\n *     id: 2,\n *     title: 'Zen and the Art of Motorcycle Maintenance',\n *     text: 'I can see by my watch...',\n *     category: 'fiction'\n *   },\n *   {\n *     id: 3,\n *     title: 'Neuromancer',\n *     text: 'The sky above the port was...',\n *     category: 'fiction'\n *   },\n *   {\n *     id: 4,\n *     title: 'Zen and the Art of Archery',\n *     text: 'At first sight it must seem...',\n *     category: 'non-fiction'\n *   },\n *   // ...and more\n * ]\n *\n * // Create a search engine that indexes the 'title' and 'text' fields for\n * // full-text search. Search results will include 'title' and 'category' (plus the\n * // id field, that is always stored and returned)\n * const searchIndex = createIndex({\n *   fields: ['title', 'text'],\n *   storeFields: ['title', 'category']\n * })\n *\n * // Add documents to the index\n * addAll(searchIndex, documents)\n *\n * // Search for documents:\n * const results = search(searchIndex, 'zen art motorcycle')\n * // => [\n * //   { id: 2, title: 'Zen and the Art of Motorcycle Maintenance', category: 'fiction', score: 2.77258 },\n * //   { id: 4, title: 'Zen and the Art of Archery', category: 'non-fiction', score: 1.38629 }\n * // ]\n * ```\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n */\nexport class SearchIndex<\n  ID = any,\n  Document = any,\n  Index extends Record<string, any> = Record<never, never>,\n> {\n  /**\n   * @ignore\n   */\n  _options: OptionsWithDefaults<ID, Document, Index>;\n  /**\n   * @ignore\n   */\n  _index: SearchableMap<FieldTermData>;\n  /**\n   * @ignore\n   */\n  _documentCount: number;\n  /**\n   * @ignore\n   */\n  _documentIds: Map<number, ID>;\n  /**\n   * @ignore\n   */\n  _idToShortId: Map<ID, number>;\n  /**\n   * @ignore\n   */\n  _fieldIds: Record<string, number>;\n  /**\n   * @ignore\n   */\n  _fieldLength: Map<number, number[]>;\n  /**\n   * @ignore\n   */\n  _avgFieldLength: number[];\n  /**\n   * @ignore\n   */\n  _nextId: number;\n  /**\n   * @ignore\n   */\n  _storedFields: Map<number, Index>;\n  /**\n   * @ignore\n   */\n  _dirtCount: number;\n  /**\n   * @ignore\n   */\n  _currentVacuum: Promise<void> | null;\n  /**\n   * @ignore\n   */\n  _enqueuedVacuum: Promise<void> | null;\n  /**\n   * @ignore\n   */\n  _enqueuedVacuumConditions: VacuumConditions | undefined;\n\n  /**\n   * @param options The options for the search index\n   */\n  constructor(options: SearchIndexOptions<ID, Document, Index>) {\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (!options?.fields)\n      throw new Error('SlimSearch: option \"fields\" must be provided');\n\n    const autoVacuum =\n      options.autoVacuum == null || options.autoVacuum === true\n        ? defaultAutoVacuumOptions\n        : options.autoVacuum;\n\n    this._options = {\n      ...defaultOptions,\n      ...options,\n      autoVacuum,\n      // @ts-expect-error: some option is optional\n      searchOptions: {\n        ...defaultSearchOptions,\n        ...options.searchOptions,\n      },\n      autoSuggestOptions: {\n        ...defaultAutoSuggestOptions,\n        ...options.autoSuggestOptions,\n      },\n    };\n\n    this._index = new SearchableMap();\n\n    this._documentCount = 0;\n\n    this._documentIds = new Map();\n\n    this._idToShortId = new Map();\n\n    // Fields are defined during initialization, don't change, are few in\n    // number, rarely need iterating over, and have string keys. Therefore in\n    // this case an object is a better candidate than a Map to store the mapping\n    // from field key to ID.\n    this._fieldIds = {};\n\n    this._fieldLength = new Map();\n\n    this._avgFieldLength = [];\n\n    this._nextId = 0;\n\n    this._storedFields = new Map();\n\n    this._dirtCount = 0;\n\n    this._currentVacuum = null;\n\n    this._enqueuedVacuum = null;\n    this._enqueuedVacuumConditions = defaultVacuumConditions;\n\n    this.addFields(this._options.fields);\n  }\n\n  /**\n   * Is `true` if a vacuuming operation is ongoing, `false` otherwise\n   */\n  get isVacuuming(): boolean {\n    return this._currentVacuum != null;\n  }\n\n  /**\n   * The number of documents discarded since the most recent vacuuming\n   */\n  get dirtCount(): number {\n    return this._dirtCount;\n  }\n\n  /**\n   * A number between 0 and 1 giving an indication about the proportion of\n   * documents that are discarded, and can therefore be cleaned up by vacuuming.\n   * A value close to 0 means that the index is relatively clean, while a higher\n   * value means that the index is relatively dirty, and vacuuming could release\n   * memory.\n   */\n  get dirtFactor(): number {\n    return this._dirtCount / (1 + this._documentCount + this._dirtCount);\n  }\n\n  /**\n   * Total number of documents available to search\n   */\n  get documentCount(): number {\n    return this._documentCount;\n  }\n\n  /**\n   * Number of terms in the index\n   */\n  get termCount(): number {\n    return this._index.size;\n  }\n\n  /**\n   * Allows serialization of the index to JSON, to possibly store it and later\n   * deserialize it with {@link loadJSONIndex} or {@link loadJSONIndexAsync}.\n   *\n   * Normally one does not directly call this method, but rather call the\n   * standard JavaScript `JSON.stringify()` passing the {@link SearchIndex} instance,\n   * and JavaScript will internally call this method. Upon deserialization, one\n   * must pass to {@link loadJSONIndex} or {@link loadJSONIndexAsync} the same options used to create the original\n   * instance that was serialized.\n   *\n   * ### Usage:\n   *\n   * ```js\n   * // Serialize the index:\n   * let searchIndex = createIndex({ fields: ['title', 'text'] })\n   * addAll(searchIndex, documents)\n   * const json = JSON.stringify(index)\n   *\n   * // Later, to deserialize it:\n   * searchIndex = loadJSONIndex(json, { fields: ['title', 'text'] })\n   * ```\n   *\n   * @return A plain-object serializable representation of the search index.\n   */\n  toJSON(): IndexObject<Index> {\n    const index: [string, Record<string, SerializedIndexEntry>][] = [];\n\n    for (const [term, fieldIndex] of this._index) {\n      const data: Record<string, SerializedIndexEntry> = {};\n\n      for (const [fieldId, frequencies] of fieldIndex)\n        data[fieldId] = Object.fromEntries(frequencies);\n\n      index.push([term, data]);\n    }\n\n    return {\n      documentCount: this._documentCount,\n      nextId: this._nextId,\n      documentIds: Object.fromEntries(this._documentIds),\n      fieldIds: this._fieldIds,\n      fieldLength: Object.fromEntries(this._fieldLength),\n      averageFieldLength: this._avgFieldLength,\n      storedFields: Object.fromEntries(this._storedFields),\n      dirtCount: this._dirtCount,\n      index,\n      version: 2,\n    };\n  }\n\n  /**\n   * @ignore\n   */\n  private addFields(fields: string[]): void {\n    for (let i = 0; i < fields.length; i++) this._fieldIds[fields[i]] = i;\n  }\n}\n","import type { FieldTermData } from \"./SearchIndex.js\";\nimport { SearchIndex } from \"./SearchIndex.js\";\nimport { SearchableMap } from \"./SearchableMap/index.js\";\nimport type { DocumentTermFrequencies } from \"./results.js\";\nimport type { IndexObject, SearchIndexOptions } from \"./typings.js\";\nimport { objectToNumericMap, objectToNumericMapAsync, wait } from \"./utils.js\";\n\nconst getMsg = (method: string): string =>\n  `SlimSearch: ${method} should be given the same options used when serializing the index`;\n\n/**\n * Create search index with given options\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param options Configuration options\n * @returns A instance of SearchIndex with given options\n *\n * ### Examples:\n *\n * ```js\n * // Create a search engine that indexes the 'title' and 'text' fields of your\n * // documents:\n * const searchIndex = createIndex({ fields: ['title', 'text'] })\n * ```\n *\n * ### ID Field:\n *\n * ```js\n * // Your documents are assumed to include a unique 'id' field, but if you want\n * // to use a different field for document identification, you can set the\n * // 'idField' option:\n * const searchIndex = createIndex({ idField: 'key', fields: ['title', 'text'] })\n * ```\n *\n * ### Options and defaults:\n *\n * ```js\n * // The full set of options (here with their default value) is:\n * const searchIndex = createIndex({\n *   // idField: field that uniquely identifies a document\n *   idField: 'id',\n *\n *   // extractField: function used to get the value of a field in a document.\n *   // By default, it assumes the document is a flat object with field names as\n *   // property keys and field values as string property values, but custom logic\n *   // can be implemented by setting this option to a custom extractor function.\n *   extractField: (document, fieldName) => document[fieldName],\n *\n *   // tokenize: function used to split fields into individual terms. By\n *   // default, it is also used to tokenize search queries, unless a specific\n *   // `tokenize` search option is supplied. When tokenizing an indexed field,\n *   // the field name is passed as the second argument.\n *   tokenize: (string, _fieldName) => string.split(SPACE_OR_PUNCTUATION),\n *\n *   // processTerm: function used to process each tokenized term before\n *   // indexing. It can be used for stemming and normalization. Return a falsy\n *   // value in order to discard a term. By default, it is also used to process\n *   // search queries, unless a specific `processTerm` option is supplied as a\n *   // search option. When processing a term from a indexed field, the field\n *   // name is passed as the second argument.\n *   processTerm: (term, _fieldName) => term.toLowerCase(),\n *\n *   // searchOptions: default search options, see the `search` method for\n *   // details\n *   searchOptions: undefined,\n *\n *   // fields: document fields to be indexed. Mandatory, but not set by default\n *   fields: undefined\n *\n *   // storeFields: document fields to be stored and returned as part of the\n *   // search results.\n *   storeFields: []\n * })\n * ```\n */\nexport const createIndex = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  options: SearchIndexOptions<ID, Document, Index>,\n): SearchIndex<ID, Document, Index> => new SearchIndex(options);\n\nconst instantiateIndex = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  {\n    documentCount,\n    nextId,\n    fieldIds,\n    averageFieldLength,\n    dirtCount,\n    version,\n  }: IndexObject<Index>,\n  options: SearchIndexOptions<ID, Document, Index>,\n): SearchIndex<ID, Document, Index> => {\n  if (version !== 2) {\n    throw new Error(\n      \"SlimSearch: cannot deserialize an index created with an incompatible version\",\n    );\n  }\n\n  const searchIndex = createIndex(options);\n\n  searchIndex._documentCount = documentCount;\n  searchIndex._nextId = nextId;\n  searchIndex._idToShortId = new Map<ID, number>();\n  searchIndex._fieldIds = fieldIds;\n  searchIndex._avgFieldLength = averageFieldLength;\n  searchIndex._dirtCount = dirtCount ?? 0;\n  searchIndex._index = new SearchableMap();\n\n  return searchIndex;\n};\n\n/**\n * Instantiates a SearchIndex instance from a JS Object.\n * It should be given the same options originally used when serializing the index.\n *\n * ### Usage:\n *\n * ```js\n * // If the index was serialized with:\n * let index = createIndex({ fields: ['title', 'text'] })\n *\n * addAll(index, documents)\n *\n * const json = index.toJSON()\n * // It can later be loaded like this:\n * index = loadJSON(json, { fields: ['title', 'text'] })\n * ```\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param indexObject index object\n * @param options  configuration options, same as the constructor\n * @return An instance of SearchIndex deserialized from the given JS object.\n */\nexport const loadIndex = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  indexObject: IndexObject<Index>,\n  options: SearchIndexOptions<ID, Document, Index>,\n): SearchIndex<ID, Document, Index> => {\n  const { index, documentIds, fieldLength, storedFields } = indexObject;\n\n  const searchIndex = instantiateIndex(indexObject, options);\n\n  searchIndex._documentIds = objectToNumericMap<ID>(documentIds);\n  searchIndex._fieldLength = objectToNumericMap(fieldLength);\n  searchIndex._storedFields = objectToNumericMap(storedFields);\n\n  for (const [shortId, id] of searchIndex._documentIds)\n    searchIndex._idToShortId.set(id, shortId);\n\n  for (const [term, data] of index) {\n    const dataMap = new Map() as FieldTermData;\n\n    for (const fieldId of Object.keys(data))\n      dataMap.set(\n        parseInt(fieldId, 10),\n        objectToNumericMap(data[fieldId]) as DocumentTermFrequencies,\n      );\n\n    searchIndex._index.set(term, dataMap);\n  }\n\n  return searchIndex;\n};\n\n/**\n * Async equivalent of {@link loadIndex}\n *\n * This function is an alternative to {@link loadIndex} that returns\n * a promise, and loads the index in batches, leaving pauses between them to avoid\n * blocking the main thread. It tends to be slower than the synchronous\n * version, but does not block the main thread, so it can be a better choice\n * when deserializing very large indexes.\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param indexObject index object\n * @param options  configuration options, same as the constructor\n * @return A Promise that will resolve to an instance of MiniSearch deserialized from the given JSON.\n */\nexport const loadIndexAsync = async <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  indexObject: IndexObject<Index>,\n  options: SearchIndexOptions<ID, Document, Index>,\n): Promise<SearchIndex<ID, Document, Index>> => {\n  const { index, documentIds, fieldLength, storedFields } = indexObject;\n  const searchIndex = instantiateIndex(indexObject, options);\n\n  searchIndex._documentIds = await objectToNumericMapAsync<ID>(documentIds);\n  searchIndex._fieldLength = await objectToNumericMapAsync(fieldLength);\n  searchIndex._storedFields = await objectToNumericMapAsync(storedFields);\n\n  for (const [shortId, id] of searchIndex._documentIds)\n    searchIndex._idToShortId.set(id, shortId);\n\n  let count = 0;\n\n  for (const [term, data] of index) {\n    const dataMap = new Map() as FieldTermData;\n\n    for (const fieldId of Object.keys(data))\n      dataMap.set(\n        parseInt(fieldId, 10),\n        (await objectToNumericMapAsync(\n          data[fieldId],\n        )) as DocumentTermFrequencies,\n      );\n\n    if (++count % 1000 === 0) await wait(0);\n\n    searchIndex._index.set(term, dataMap);\n  }\n\n  return searchIndex;\n};\n\n/**\n * Deserializes a JSON index (serialized with `JSON.stringify(index)`)\n * and instantiates a SearchIndex instance. It should be given the same options\n * originally used when serializing the index.\n *\n * ### Usage:\n *\n * ```js\n * // If the index was serialized with:\n * let index = createIndex({ fields: ['title', 'text'] })\n *\n * addAll(index, documents)\n *\n * const json = JSON.stringify(index)\n * // It can later be deserialized like this:\n * index = loadJSONIndex(json, { fields: ['title', 'text'] })\n * ```\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param json  JSON-serialized index\n * @param options  configuration options, same as the constructor\n * @return An instance of SearchIndex deserialized from the given JSON.\n */\nexport const loadJSONIndex = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  json: string,\n  options: SearchIndexOptions<ID, Document, Index>,\n): SearchIndex<ID, Document, Index> => {\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (!options) throw new Error(getMsg(\"loadJSONIndex\"));\n\n  return loadIndex(JSON.parse(json) as IndexObject<Index>, options);\n};\n\n/**\n * Async equivalent of {@link loadJSONIndex}\n *\n * This function is an alternative to {@link loadJSONIndex} that returns\n * a promise, and loads the index in batches, leaving pauses between them to avoid\n * blocking the main thread. It tends to be slower than the synchronous\n * version, but does not block the main thread, so it can be a better choice\n * when deserializing very large indexes.\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param json  JSON-serialized index\n * @param options  configuration options, same as the constructor\n * @return A Promise that will resolve to an instance of MiniSearch deserialized from the given JSON.\n */\nexport const loadJSONIndexAsync = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  json: string,\n  options: SearchIndexOptions<ID, Document, Index>,\n): Promise<SearchIndex<ID, Document, Index>> => {\n  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n  if (!options) throw new Error(getMsg(\"loadJSONIndexAsync\"));\n\n  return loadIndexAsync(JSON.parse(json) as IndexObject<Index>, options);\n};\n","import type { SearchIndex } from \"./SearchIndex.js\";\nimport {\n  defaultAutoVacuumOptions,\n  defaultVacuumConditions,\n  defaultVacuumOptions,\n} from \"./defaults.js\";\nimport type { VacuumConditions, VacuumOptions } from \"./typings.js\";\n\nconst shouldVacuum = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  conditions?: VacuumConditions,\n): boolean => {\n  if (conditions == null) return true;\n\n  const {\n    minDirtCount = defaultAutoVacuumOptions.minDirtCount,\n    minDirtFactor = defaultAutoVacuumOptions.minDirtFactor,\n  } = conditions;\n\n  return (\n    searchIndex.dirtCount >= minDirtCount &&\n    searchIndex.dirtFactor >= minDirtFactor\n  );\n};\n\nconst doVacuum = async <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  options: VacuumOptions,\n  conditions?: VacuumConditions,\n): Promise<void> => {\n  const initialDirtCount = searchIndex._dirtCount;\n\n  if (shouldVacuum(searchIndex, conditions)) {\n    const batchSize = options.batchSize ?? defaultVacuumOptions.batchSize;\n    const batchWait = options.batchWait ?? defaultVacuumOptions.batchWait;\n    let i = 1;\n\n    for (const [term, fieldsData] of searchIndex._index) {\n      for (const [fieldId, fieldIndex] of fieldsData)\n        for (const [shortId] of fieldIndex) {\n          if (searchIndex._documentIds.has(shortId)) continue;\n\n          if (fieldIndex.size <= 1) fieldsData.delete(fieldId);\n          else fieldIndex.delete(shortId);\n        }\n\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n      if (searchIndex._index.get(term)!.size === 0)\n        searchIndex._index.delete(term);\n\n      if (i % batchSize === 0)\n        await new Promise((resolve) => setTimeout(resolve, batchWait));\n\n      i += 1;\n    }\n\n    searchIndex._dirtCount -= initialDirtCount;\n  }\n\n  // Make the next lines always async, so they execute after this function returns\n  // eslint-disable-next-line @typescript-eslint/await-thenable\n  await null;\n\n  searchIndex._currentVacuum = searchIndex._enqueuedVacuum;\n  searchIndex._enqueuedVacuum = null;\n};\n\nconst conditionalVacuum = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  options: VacuumOptions,\n  conditions?: VacuumConditions,\n): Promise<void> => {\n  // If a vacuum is already ongoing, schedule another as soon as it finishes,\n  // unless there's already one enqueued. If one was already enqueued, do not\n  // enqueue another on top, but make sure that the conditions are the\n  // broadest.\n  if (searchIndex._currentVacuum) {\n    searchIndex._enqueuedVacuumConditions =\n      searchIndex._enqueuedVacuumConditions && conditions;\n    if (searchIndex._enqueuedVacuum != null) return searchIndex._enqueuedVacuum;\n\n    searchIndex._enqueuedVacuum = searchIndex._currentVacuum.then(() => {\n      const conditions = searchIndex._enqueuedVacuumConditions;\n\n      searchIndex._enqueuedVacuumConditions = defaultVacuumConditions;\n\n      return doVacuum(searchIndex, options, conditions);\n    });\n\n    return searchIndex._enqueuedVacuum;\n  }\n\n  if (!shouldVacuum(searchIndex, conditions)) return Promise.resolve();\n\n  searchIndex._currentVacuum = doVacuum(searchIndex, options);\n\n  return searchIndex._currentVacuum;\n};\n\nexport const maybeAutoVacuum = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n): void => {\n  if (searchIndex._options.autoVacuum === false) return;\n\n  const { minDirtFactor, minDirtCount, batchSize, batchWait } =\n    searchIndex._options.autoVacuum;\n\n  void conditionalVacuum(\n    searchIndex,\n    { batchSize, batchWait },\n    { minDirtCount, minDirtFactor },\n  );\n};\n\n/**\n * Triggers a manual vacuuming, cleaning up references to discarded documents\n * from the inverted index\n *\n * Vacuuming is only useful for applications that use the\n * {@link discard} or {@link replace} methods.\n *\n * By default, vacuuming is performed automatically when needed (controlled by\n * the `autoVacuum` field in {@link SearchOptions}), so there is usually no need to call\n * this method, unless one wants to make sure to perform vacuuming at a\n * specific moment.\n *\n * Vacuuming traverses all terms in the inverted index in batches, and cleans\n * up references to discarded documents from the posting list, allowing memory\n * to be released.\n *\n * The method takes an optional object as argument with the following keys:\n *\n *   - `batchSize`: the size of each batch (1000 by default)\n *\n *   - `batchWait`: the number of milliseconds to wait between batches (10 by\n *   default)\n *\n * On large indexes, vacuuming could have a non-negligible cost: batching\n * avoids blocking the thread for long, diluting this cost so that it is not\n * negatively affecting the application. Nonetheless, this method should only\n * be called when necessary, and relying on automatic vacuuming is usually\n * better.\n *\n * It returns a promise that resolves (to undefined) when the clean up is\n * completed. If vacuuming is already ongoing at the time this method is\n * called, a new one is enqueued immediately after the ongoing one, and a\n * corresponding promise is returned. However, no more than one vacuuming is\n * enqueued on top of the ongoing one, even if this method is called more\n * times (enqueuing multiple ones would be useless).\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex Search Index\n * @param options  Configuration options for the batch size and delay. See\n * {@link VacuumOptions}.\n */\nexport const vacuum = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  options: VacuumOptions = {},\n): Promise<void> => conditionalVacuum(searchIndex, options);\n","import type { SearchIndex } from \"./SearchIndex.js\";\nimport { SearchableMap } from \"./SearchableMap/index.js\";\nimport { removeTerm } from \"./term.js\";\nimport { maybeAutoVacuum } from \"./vacuum.js\";\n\nconst removeFieldLength = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  fieldId: number,\n  count: number,\n  length: number,\n): void => {\n  if (count === 1) {\n    searchIndex._avgFieldLength[fieldId] = 0;\n\n    return;\n  }\n\n  const totalFieldLength =\n    searchIndex._avgFieldLength[fieldId] * count - length;\n\n  searchIndex._avgFieldLength[fieldId] = totalFieldLength / (count - 1);\n};\n\n/**\n * Discards the document with the given ID, so it won't appear in search results\n *\n * It has the same visible effect of {@link remove} (both cause the\n * document to stop appearing in searches), but a different effect on the\n * internal data structures:\n *\n *   - {@link remove} requires passing the full document to be removed\n *   as argument, and removes it from the inverted index immediately.\n *\n *   - {@link discard} instead only needs the document ID, and works by\n *   marking the current version of the document as discarded, so it is\n *   immediately ignored by searches. This is faster and more convenient than\n *   `remove`, but the index is not immediately modified. To take care of\n *   that, vacuuming is performed after a certain number of documents are\n *   discarded, cleaning up the index and allowing memory to be released.\n *\n * After discarding a document, it is possible to re-add a new version, and\n * only the new version will appear in searches. In other words, discarding\n * and re-adding a document works exactly like removing and re-adding it. The\n * {@link replace} method can also be used to replace a document with a\n * new version.\n *\n * #### Details about vacuuming\n *\n * Repetitive calls to this method would leave obsolete document references in\n * the index, invisible to searches. Two mechanisms take care of cleaning up:\n * clean up during search, and vacuuming.\n *\n *   - Upon search, whenever a discarded ID is found (and ignored for the\n *   results), references to the discarded document are removed from the\n *   inverted index entries for the search terms. This ensures that subsequent\n *   searches for the same terms do not need to skip these obsolete references\n *   again.\n *\n *   - In addition, vacuuming is performed automatically by default (see the\n *   `autoVacuum` field in {@link SearchOptions}) after a certain number of documents\n *   are discarded. Vacuuming traverses all terms in the index, cleaning up\n *   all references to discarded documents. Vacuuming can also be triggered\n *   manually by calling {@link vacuum}.\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex The search index\n * @param id  The ID of the document to be discarded\n */\nexport const discard = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  id: ID,\n): void => {\n  const shortId = searchIndex._idToShortId.get(id);\n\n  if (shortId == null)\n    throw new Error(\n      `SlimSearch: cannot discard document with ID ${id as string}: it is not in the index`,\n    );\n\n  searchIndex._idToShortId.delete(id);\n  searchIndex._documentIds.delete(shortId);\n  searchIndex._storedFields.delete(shortId);\n  searchIndex._fieldLength.get(shortId)?.forEach((fieldLength, fieldId) => {\n    removeFieldLength(\n      searchIndex,\n      fieldId,\n      searchIndex._documentCount,\n      fieldLength,\n    );\n  });\n\n  searchIndex._fieldLength.delete(shortId);\n\n  searchIndex._documentCount -= 1;\n  searchIndex._dirtCount += 1;\n\n  maybeAutoVacuum(searchIndex);\n};\n\n/**\n * Discards the documents with the given IDs, so they won't appear in search\n * results\n *\n * It is equivalent to calling {@link discard} for all the given IDs,\n * but with the optimization of triggering at most one automatic vacuuming at\n * the end.\n *\n * Note: to remove all documents from the index, it is faster and more\n * convenient to call {@link removeAll} with no argument, instead of\n * passing all IDs to this method.\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex The search index\n * @param ids The IDs of the document to be discarded\n */\nexport const discardAll = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  ids: readonly ID[],\n): void => {\n  const autoVacuum = searchIndex._options.autoVacuum;\n\n  try {\n    searchIndex._options.autoVacuum = false;\n\n    for (const id of ids) discard(searchIndex, id);\n  } finally {\n    searchIndex._options.autoVacuum = autoVacuum;\n  }\n\n  maybeAutoVacuum(searchIndex);\n};\n\n/**\n * Removes the given document from the index.\n *\n * The document to remove must NOT have changed between indexing and removal,\n * otherwise the index will be corrupted.\n *\n * This method requires passing the full document to be removed (not just the\n * ID), and immediately removes the document from the inverted index, allowing\n * memory to be released. A convenient alternative is {@link discard},\n * which needs only the document ID, and has the same visible effect, but\n * delays cleaning up the index until the next vacuuming.\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex The search index\n * @param document  The document to be removed\n */\nexport const remove = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  document: Document,\n): void => {\n  const { tokenize, processTerm, extractField, fields, idField } =\n    searchIndex._options;\n  const id = extractField(document, idField) as ID;\n\n  if (id == null)\n    throw new Error(`SlimSearch: document does not have ID field \"${idField}\"`);\n\n  const shortId = searchIndex._idToShortId.get(id);\n\n  if (shortId == null)\n    throw new Error(\n      `SlimSearch: cannot remove document with ID ${id as string}: it is not in the index`,\n    );\n\n  for (const field of fields) {\n    const fieldValue = extractField(document, field);\n\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    if (fieldValue == null) continue;\n\n    const tokens = tokenize(fieldValue.toString(), field);\n    const fieldId = searchIndex._fieldIds[field];\n\n    const uniqueTerms = new Set(tokens).size;\n\n    removeFieldLength(\n      searchIndex,\n      fieldId,\n      searchIndex._documentCount,\n      uniqueTerms,\n    );\n\n    for (const term of tokens) {\n      const processedTerm = processTerm(term, field);\n\n      if (Array.isArray(processedTerm))\n        for (const t of processedTerm)\n          removeTerm(searchIndex, fieldId, shortId, t);\n      else if (processedTerm)\n        removeTerm(searchIndex, fieldId, shortId, processedTerm);\n    }\n  }\n\n  searchIndex._storedFields.delete(shortId);\n  searchIndex._documentIds.delete(shortId);\n  searchIndex._idToShortId.delete(id);\n  searchIndex._fieldLength.delete(shortId);\n  searchIndex._documentCount -= 1;\n};\n\n/**\n * Removes all the given documents from the index. If called with no arguments,\n * it removes _all_ documents from the index.\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex The search index\n * @param documents  The documents to be removed. If this argument is omitted,\n * all documents are removed. Note that, for removing all documents, it is\n * more efficient to call this method with no arguments than to pass all\n * documents.\n */\nexport const removeAll = function removeAll<\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  documents?: readonly Document[],\n): void {\n  if (documents) {\n    for (const document of documents) remove(searchIndex, document);\n  } else if (arguments.length > 1) {\n    throw new Error(\n      \"Expected documents to be present. Omit the argument to remove all documents.\",\n    );\n  } else {\n    searchIndex._index = new SearchableMap();\n    searchIndex._documentCount = 0;\n    searchIndex._documentIds = new Map();\n    searchIndex._idToShortId = new Map();\n    searchIndex._fieldLength = new Map();\n    searchIndex._avgFieldLength = [];\n    searchIndex._storedFields = new Map();\n    searchIndex._nextId = 0;\n  }\n};\n","import type { SearchIndex } from \"./SearchIndex.js\";\nimport { add } from \"./add.js\";\nimport { discard } from \"./remove.js\";\n\n/**\n * It replaces an existing document with the given updated version\n *\n * It works by discarding the current version and adding the updated one, so\n * it is functionally equivalent to calling {@link discard} followed by\n * {@link add}. The ID of the updated document should be the same as\n * the original one.\n *\n * Since it uses {@link discard} internally, this method relies on\n * vacuuming to clean up obsolete document references from the index, allowing\n * memory to be released (see {@link discard}).\n *\n * @typeParam ID  The id type of the documents being indexed.\n * @typeParam Document  The type of the documents being indexed.\n * @typeParam Index The type of the documents being indexed.\n *\n * @param searchIndex The search index\n * @param updatedDocument  The updated document to replace the old version\n * with\n */\nexport const replace = <\n  ID,\n  Document,\n  Index extends Record<string, any> = Record<never, never>,\n>(\n  searchIndex: SearchIndex<ID, Document, Index>,\n  updatedDocument: Document,\n): void => {\n  const { idField, extractField } = searchIndex._options;\n  const id = extractField(updatedDocument, idField) as ID;\n\n  discard(searchIndex, id);\n  add(searchIndex, updatedDocument);\n};\n"],"names":["ENTRIES","KEYS","VALUES","LEAF","TreeIterator","set","type","node","keys","value","last","child","key","array","fuzzySearch","query","maxDistance","results","n","m","matrix","j","i","recurse","prefix","offset","distance","pos","char","thisRowOffset","prevRowOffset","minDistance","jmin","jmax","different","rpl","del","ins","dist","SearchableMap","tree","path","trackDown","parentNode","k","remove","fn","maxEditDistance","lookup","createPath","iter","initial","entries","object","treeKey","keyLength","outer","len","intermediate","cleanup","merge","nodeKey","has","searchIndex","id","getStoredFields","shortId","SPACE_OR_PUNCTUATION","OR","AND","AND_NOT","wait","ms","resolve","assignUniqueTerm","target","term","assignUniqueTerms","source","byScore","a","b","createMap","objectToNumericMap","map","objectToNumericMapAsync","count","getOwnProperty","property","combinators","docId","existing","score","terms","match","combined","calcBM25Score","termFreq","matchingCount","totalCount","fieldLength","avgFieldLength","bm25params","u","d","termToQuerySpec","options","index","warnDocumentChanged","shortDocumentId","fieldId","fieldName","addTerm","documentId","indexData","fieldIndex","docs","removeTerm","amount","addFieldLength","length","fieldLengths","totalFieldLength","addDocumentId","saveStoredFields","doc","storeFields","extractField","documentFields","fieldValue","add","document","tokenize","processTerm","fields","idField","field","tokens","uniqueTerms","processedTerm","t","addAll","documents","addAllAsync","chunkSize","acc","chunk","promise","defaultBM25params","defaultOptions","text","level","message","defaultSearchOptions","defaultAutoSuggestOptions","_term","defaultVacuumOptions","defaultVacuumConditions","defaultAutoVacuumOptions","getDefaultValue","optionName","WILDCARD","executeWildcardQuery","searchOptions","combineResults","combineWith","operator","termResults","sourceTerm","derivedTerm","termWeight","termBoost","fieldTermData","fieldBoosts","boostDocumentFn","fieldBoost","fieldTermFrequencies","matchingFields","docBoost","rawScore","weightedScore","result","executeQuerySpec","boosts","boostDocument","weights","maxFuzzy","fuzzyWeight","prefixWeight","data","prefixMatches","fuzzyMatches","fuzzy","weight","executeQuery","subQuery","globalSearchOptions","searchTokenize","searchProcessTerm","search","rawResults","quality","autoSuggest","queryString","suggestions","phrase","suggestion","SearchIndex","autoVacuum","frequencies","getMsg","method","createIndex","instantiateIndex","documentCount","nextId","fieldIds","averageFieldLength","dirtCount","version","loadIndex","indexObject","documentIds","storedFields","dataMap","loadIndexAsync","loadJSONIndex","json","loadJSONIndexAsync","shouldVacuum","conditions","minDirtCount","minDirtFactor","doVacuum","initialDirtCount","batchSize","batchWait","fieldsData","conditionalVacuum","maybeAutoVacuum","vacuum","removeFieldLength","discard","discardAll","ids","removeAll","replace","updatedDocument"],"mappings":"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"}