// node_modules/.pnpm/minisearch@7.1.1/node_modules/minisearch/dist/es/index.js function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function(resolve) { resolve(value); }); } return new (P || (P = Promise))(function(resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } var ENTRIES = "ENTRIES"; var KEYS = "KEYS"; var VALUES = "VALUES"; var LEAF = ""; var TreeIterator = class { constructor(set, type) { const node = set._tree; const keys = Array.from(node.keys()); this.set = set; this._type = type; this._path = keys.length > 0 ? [{ node, keys }] : []; } next() { const value = this.dive(); this.backtrack(); return value; } dive() { if (this._path.length === 0) { return { done: true, value: void 0 }; } const { node, keys } = last$1(this._path); if (last$1(keys) === LEAF) { return { done: false, value: this.result() }; } const child = node.get(last$1(keys)); this._path.push({ node: child, keys: Array.from(child.keys()) }); return this.dive(); } backtrack() { if (this._path.length === 0) { return; } const keys = last$1(this._path).keys; keys.pop(); if (keys.length > 0) { return; } this._path.pop(); this.backtrack(); } key() { return this.set._prefix + this._path.map(({ keys }) => last$1(keys)).filter((key) => key !== LEAF).join(""); } value() { return last$1(this._path).node.get(LEAF); } result() { switch (this._type) { case VALUES: return this.value(); case KEYS: return this.key(); default: return [this.key(), this.value()]; } } [Symbol.iterator]() { return this; } }; var last$1 = (array) => { return array[array.length - 1]; }; var fuzzySearch = (node, query, maxDistance) => { const results = /* @__PURE__ */ new Map(); if (query === void 0) return results; const n = query.length + 1; const m = n + maxDistance; const matrix = new Uint8Array(m * n).fill(maxDistance + 1); for (let j = 0; j < n; ++j) matrix[j] = j; for (let i = 1; i < m; ++i) matrix[i * n] = i; recurse(node, query, maxDistance, results, matrix, 1, n, ""); return results; }; var recurse = (node, query, maxDistance, results, matrix, m, n, prefix) => { const offset = m * n; key: for (const key of node.keys()) { if (key === LEAF) { const distance = matrix[offset - 1]; if (distance <= maxDistance) { results.set(prefix, [node.get(key), distance]); } } else { let i = m; for (let pos = 0; pos < key.length; ++pos, ++i) { const char = key[pos]; const thisRowOffset = n * i; const prevRowOffset = thisRowOffset - n; let minDistance = matrix[thisRowOffset]; const jmin = Math.max(0, i - maxDistance - 1); const jmax = Math.min(n - 1, i + maxDistance); for (let j = jmin; j < jmax; ++j) { const different = char !== query[j]; const rpl = matrix[prevRowOffset + j] + +different; const del = matrix[prevRowOffset + j + 1] + 1; const ins = matrix[thisRowOffset + j] + 1; const dist = matrix[thisRowOffset + j + 1] = Math.min(rpl, del, ins); if (dist < minDistance) minDistance = dist; } if (minDistance > maxDistance) { continue key; } } recurse(node.get(key), query, maxDistance, results, matrix, i, n, prefix + key); } } }; var SearchableMap = class _SearchableMap { /** * The constructor is normally called without arguments, creating an empty * map. In order to create a {@link SearchableMap} from an iterable or from an * object, check {@link SearchableMap.from} and {@link * SearchableMap.fromObject}. * * The constructor arguments are for internal use, when creating derived * mutable views of a map at a prefix. */ constructor(tree = /* @__PURE__ */ new Map(), prefix = "") { this._size = void 0; this._tree = tree; this._prefix = prefix; } /** * Creates and returns a mutable view of this {@link SearchableMap}, * containing only entries that share the given prefix. * * ### Usage: * * ```javascript * let map = new SearchableMap() * map.set("unicorn", 1) * map.set("universe", 2) * map.set("university", 3) * map.set("unique", 4) * map.set("hello", 5) * * let uni = map.atPrefix("uni") * uni.get("unique") // => 4 * uni.get("unicorn") // => 1 * uni.get("hello") // => undefined * * let univer = map.atPrefix("univer") * univer.get("unique") // => undefined * univer.get("universe") // => 2 * univer.get("university") // => 3 * ``` * * @param prefix The prefix * @return A {@link SearchableMap} representing a mutable view of the original * Map at the given prefix */ atPrefix(prefix) { if (!prefix.startsWith(this._prefix)) { throw new Error("Mismatched prefix"); } const [node, path] = trackDown(this._tree, prefix.slice(this._prefix.length)); if (node === void 0) { const [parentNode, key] = last(path); for (const k of parentNode.keys()) { if (k !== LEAF && k.startsWith(key)) { const node2 = /* @__PURE__ */ new Map(); node2.set(k.slice(key.length), parentNode.get(k)); return new _SearchableMap(node2, prefix); } } } return new _SearchableMap(node, prefix); } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear */ clear() { this._size = void 0; this._tree.clear(); } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete * @param key Key to delete */ delete(key) { this._size = void 0; return remove(this._tree, key); } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries * @return An iterator iterating through `[key, value]` entries. */ entries() { return new TreeIterator(this, ENTRIES); } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach * @param fn Iteration function */ forEach(fn) { for (const [key, value] of this) { fn(key, value, this); } } /** * Returns a Map of all the entries that have a key within the given edit * distance from the search key. The keys of the returned Map are the matching * keys, while the values are two-element arrays where the first element is * the value associated to the key, and the second is the edit distance of the * key to the search key. * * ### Usage: * * ```javascript * let map = new SearchableMap() * map.set('hello', 'world') * map.set('hell', 'yeah') * map.set('ciao', 'mondo') * * // Get all entries that match the key 'hallo' with a maximum edit distance of 2 * map.fuzzyGet('hallo', 2) * // => Map(2) { 'hello' => ['world', 1], 'hell' => ['yeah', 2] } * * // In the example, the "hello" key has value "world" and edit distance of 1 * // (change "e" to "a"), the key "hell" has value "yeah" and edit distance of 2 * // (change "e" to "a", delete "o") * ``` * * @param key The search key * @param maxEditDistance The maximum edit distance (Levenshtein) * @return A Map of the matching keys to their value and edit distance */ fuzzyGet(key, maxEditDistance) { return fuzzySearch(this._tree, key, maxEditDistance); } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get * @param key Key to get * @return Value associated to the key, or `undefined` if the key is not * found. */ get(key) { const node = lookup(this._tree, key); return node !== void 0 ? node.get(LEAF) : void 0; } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has * @param key Key * @return True if the key is in the map, false otherwise */ has(key) { const node = lookup(this._tree, key); return node !== void 0 && node.has(LEAF); } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys * @return An `Iterable` iterating through keys */ keys() { return new TreeIterator(this, KEYS); } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set * @param key Key to set * @param value Value to associate to the key * @return The {@link SearchableMap} itself, to allow chaining */ set(key, value) { if (typeof key !== "string") { throw new Error("key must be a string"); } this._size = void 0; const node = createPath(this._tree, key); node.set(LEAF, value); return this; } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size */ get size() { if (this._size) { return this._size; } this._size = 0; const iter = this.entries(); while (!iter.next().done) this._size += 1; return this._size; } /** * Updates the value at the given key using the provided function. The function * is called with the current value at the key, and its return value is used as * the new value to be set. * * ### Example: * * ```javascript * // Increment the current value by one * searchableMap.update('somekey', (currentValue) => currentValue == null ? 0 : currentValue + 1) * ``` * * If the value at the given key is or will be an object, it might not require * re-assignment. In that case it is better to use `fetch()`, because it is * faster. * * @param key The key to update * @param fn The function used to compute the new value from the current one * @return The {@link SearchableMap} itself, to allow chaining */ update(key, fn) { if (typeof key !== "string") { throw new Error("key must be a string"); } this._size = void 0; const node = createPath(this._tree, key); node.set(LEAF, fn(node.get(LEAF))); return this; } /** * Fetches the value of the given key. If the value does not exist, calls the * given function to create a new value, which is inserted at the given key * and subsequently returned. * * ### Example: * * ```javascript * const map = searchableMap.fetch('somekey', () => new Map()) * map.set('foo', 'bar') * ``` * * @param key The key to update * @param initial A function that creates a new value if the key does not exist * @return The existing or new value at the given key */ fetch(key, initial) { if (typeof key !== "string") { throw new Error("key must be a string"); } this._size = void 0; const node = createPath(this._tree, key); let value = node.get(LEAF); if (value === void 0) { node.set(LEAF, value = initial()); } return value; } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values * @return An `Iterable` iterating through values. */ values() { return new TreeIterator(this, VALUES); } /** * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/@@iterator */ [Symbol.iterator]() { return this.entries(); } /** * Creates a {@link SearchableMap} from an `Iterable` of entries * * @param entries Entries to be inserted in the {@link SearchableMap} * @return A new {@link SearchableMap} with the given entries */ static from(entries) { const tree = new _SearchableMap(); for (const [key, value] of entries) { tree.set(key, value); } return tree; } /** * Creates a {@link SearchableMap} from the iterable properties of a JavaScript object * * @param object Object of entries for the {@link SearchableMap} * @return A new {@link SearchableMap} with the given entries */ static fromObject(object) { return _SearchableMap.from(Object.entries(object)); } }; var trackDown = (tree, key, path = []) => { if (key.length === 0 || tree == null) { return [tree, path]; } for (const k of tree.keys()) { if (k !== LEAF && key.startsWith(k)) { path.push([tree, k]); return trackDown(tree.get(k), key.slice(k.length), path); } } path.push([tree, key]); return trackDown(void 0, "", path); }; var lookup = (tree, key) => { if (key.length === 0 || tree == null) { return tree; } for (const k of tree.keys()) { if (k !== LEAF && key.startsWith(k)) { return lookup(tree.get(k), key.slice(k.length)); } } }; var createPath = (node, key) => { const keyLength = key.length; outer: for (let pos = 0; node && pos < keyLength; ) { for (const k of node.keys()) { if (k !== LEAF && key[pos] === k[0]) { const len = Math.min(keyLength - pos, k.length); let offset = 1; while (offset < len && key[pos + offset] === k[offset]) ++offset; const child2 = node.get(k); if (offset === k.length) { node = child2; } else { const intermediate = /* @__PURE__ */ new Map(); intermediate.set(k.slice(offset), child2); node.set(key.slice(pos, pos + offset), intermediate); node.delete(k); node = intermediate; } pos += offset; continue outer; } } const child = /* @__PURE__ */ new Map(); node.set(key.slice(pos), child); return child; } return node; }; var remove = (tree, key) => { const [node, path] = trackDown(tree, key); if (node === void 0) { return; } node.delete(LEAF); if (node.size === 0) { cleanup(path); } else if (node.size === 1) { const [key2, value] = node.entries().next().value; merge(path, key2, value); } }; var cleanup = (path) => { if (path.length === 0) { return; } const [node, key] = last(path); node.delete(key); if (node.size === 0) { cleanup(path.slice(0, -1)); } else if (node.size === 1) { const [key2, value] = node.entries().next().value; if (key2 !== LEAF) { merge(path.slice(0, -1), key2, value); } } }; var merge = (path, key, value) => { if (path.length === 0) { return; } const [node, nodeKey] = last(path); node.set(nodeKey + key, value); node.delete(nodeKey); }; var last = (array) => { return array[array.length - 1]; }; var OR = "or"; var AND = "and"; var AND_NOT = "and_not"; var MiniSearch = class _MiniSearch { /** * @param options Configuration options * * ### Examples: * * ```javascript * // Create a search engine that indexes the 'title' and 'text' fields of your * // documents: * const miniSearch = new MiniSearch({ fields: ['title', 'text'] }) * ``` * * ### ID Field: * * ```javascript * // Your documents are assumed to include a unique 'id' field, but if you want * // to use a different field for document identification, you can set the * // 'idField' option: * const miniSearch = new MiniSearch({ idField: 'key', fields: ['title', 'text'] }) * ``` * * ### Options and defaults: * * ```javascript * // The full set of options (here with their default value) is: * const miniSearch = new MiniSearch({ * // idField: field that uniquely identifies a document * idField: 'id', * * // extractField: function used to get the value of a field in a document. * // By default, it assumes the document is a flat object with field names as * // property keys and field values as string property values, but custom logic * // can be implemented by setting this option to a custom extractor function. * extractField: (document, fieldName) => document[fieldName], * * // tokenize: function used to split fields into individual terms. By * // default, it is also used to tokenize search queries, unless a specific * // `tokenize` search option is supplied. When tokenizing an indexed field, * // the field name is passed as the second argument. * tokenize: (string, _fieldName) => string.split(SPACE_OR_PUNCTUATION), * * // processTerm: function used to process each tokenized term before * // indexing. It can be used for stemming and normalization. Return a falsy * // value in order to discard a term. By default, it is also used to process * // search queries, unless a specific `processTerm` option is supplied as a * // search option. When processing a term from a indexed field, the field * // name is passed as the second argument. * processTerm: (term, _fieldName) => term.toLowerCase(), * * // searchOptions: default search options, see the `search` method for * // details * searchOptions: undefined, * * // fields: document fields to be indexed. Mandatory, but not set by default * fields: undefined * * // storeFields: document fields to be stored and returned as part of the * // search results. * storeFields: [] * }) * ``` */ constructor(options) { if ((options === null || options === void 0 ? void 0 : options.fields) == null) { throw new Error('MiniSearch: option "fields" must be provided'); } const autoVacuum = options.autoVacuum == null || options.autoVacuum === true ? defaultAutoVacuumOptions : options.autoVacuum; this._options = Object.assign(Object.assign(Object.assign({}, defaultOptions), options), { autoVacuum, searchOptions: Object.assign(Object.assign({}, defaultSearchOptions), options.searchOptions || {}), autoSuggestOptions: Object.assign(Object.assign({}, defaultAutoSuggestOptions), options.autoSuggestOptions || {}) }); this._index = new SearchableMap(); this._documentCount = 0; this._documentIds = /* @__PURE__ */ new Map(); this._idToShortId = /* @__PURE__ */ new Map(); this._fieldIds = {}; this._fieldLength = /* @__PURE__ */ new Map(); this._avgFieldLength = []; this._nextId = 0; this._storedFields = /* @__PURE__ */ new Map(); this._dirtCount = 0; this._currentVacuum = null; this._enqueuedVacuum = null; this._enqueuedVacuumConditions = defaultVacuumConditions; this.addFields(this._options.fields); } /** * Adds a document to the index * * @param document The document to be indexed */ add(document) { const { extractField, tokenize, processTerm, fields, idField } = this._options; const id = extractField(document, idField); if (id == null) { throw new Error(`MiniSearch: document does not have ID field "${idField}"`); } if (this._idToShortId.has(id)) { throw new Error(`MiniSearch: duplicate ID ${id}`); } const shortDocumentId = this.addDocumentId(id); this.saveStoredFields(shortDocumentId, document); for (const field of fields) { const fieldValue = extractField(document, field); if (fieldValue == null) continue; const tokens = tokenize(fieldValue.toString(), field); const fieldId = this._fieldIds[field]; const uniqueTerms = new Set(tokens).size; this.addFieldLength(shortDocumentId, fieldId, this._documentCount - 1, uniqueTerms); for (const term of tokens) { const processedTerm = processTerm(term, field); if (Array.isArray(processedTerm)) { for (const t of processedTerm) { this.addTerm(fieldId, shortDocumentId, t); } } else if (processedTerm) { this.addTerm(fieldId, shortDocumentId, processedTerm); } } } } /** * Adds all the given documents to the index * * @param documents An array of documents to be indexed */ addAll(documents) { for (const document of documents) this.add(document); } /** * Adds all the given documents to the index asynchronously. * * Returns a promise that resolves (to `undefined`) when the indexing is done. * This method is useful when index many documents, to avoid blocking the main * thread. The indexing is performed asynchronously and in chunks. * * @param documents An array of documents to be indexed * @param options Configuration options * @return A promise resolving to `undefined` when the indexing is done */ addAllAsync(documents, options = {}) { const { chunkSize = 10 } = options; const acc = { chunk: [], promise: Promise.resolve() }; const { chunk, promise } = documents.reduce(({ chunk: chunk2, promise: promise2 }, document, i) => { chunk2.push(document); if ((i + 1) % chunkSize === 0) { return { chunk: [], promise: promise2.then(() => new Promise((resolve) => setTimeout(resolve, 0))).then(() => this.addAll(chunk2)) }; } else { return { chunk: chunk2, promise: promise2 }; } }, acc); return promise.then(() => this.addAll(chunk)); } /** * Removes the given document from the index. * * The document to remove must NOT have changed between indexing and removal, * otherwise the index will be corrupted. * * This method requires passing the full document to be removed (not just the * ID), and immediately removes the document from the inverted index, allowing * memory to be released. A convenient alternative is {@link * MiniSearch#discard}, which needs only the document ID, and has the same * visible effect, but delays cleaning up the index until the next vacuuming. * * @param document The document to be removed */ remove(document) { const { tokenize, processTerm, extractField, fields, idField } = this._options; const id = extractField(document, idField); if (id == null) { throw new Error(`MiniSearch: document does not have ID field "${idField}"`); } const shortId = this._idToShortId.get(id); if (shortId == null) { throw new Error(`MiniSearch: cannot remove document with ID ${id}: it is not in the index`); } for (const field of fields) { const fieldValue = extractField(document, field); if (fieldValue == null) continue; const tokens = tokenize(fieldValue.toString(), field); const fieldId = this._fieldIds[field]; const uniqueTerms = new Set(tokens).size; this.removeFieldLength(shortId, fieldId, this._documentCount, uniqueTerms); for (const term of tokens) { const processedTerm = processTerm(term, field); if (Array.isArray(processedTerm)) { for (const t of processedTerm) { this.removeTerm(fieldId, shortId, t); } } else if (processedTerm) { this.removeTerm(fieldId, shortId, processedTerm); } } } this._storedFields.delete(shortId); this._documentIds.delete(shortId); this._idToShortId.delete(id); this._fieldLength.delete(shortId); this._documentCount -= 1; } /** * Removes all the given documents from the index. If called with no arguments, * it removes _all_ documents from the index. * * @param documents The documents to be removed. If this argument is omitted, * all documents are removed. Note that, for removing all documents, it is * more efficient to call this method with no arguments than to pass all * documents. */ removeAll(documents) { if (documents) { for (const document of documents) this.remove(document); } else if (arguments.length > 0) { throw new Error("Expected documents to be present. Omit the argument to remove all documents."); } else { this._index = new SearchableMap(); this._documentCount = 0; this._documentIds = /* @__PURE__ */ new Map(); this._idToShortId = /* @__PURE__ */ new Map(); this._fieldLength = /* @__PURE__ */ new Map(); this._avgFieldLength = []; this._storedFields = /* @__PURE__ */ new Map(); this._nextId = 0; } } /** * Discards the document with the given ID, so it won't appear in search results * * It has the same visible effect of {@link MiniSearch.remove} (both cause the * document to stop appearing in searches), but a different effect on the * internal data structures: * * - {@link MiniSearch#remove} requires passing the full document to be * removed as argument, and removes it from the inverted index immediately. * * - {@link MiniSearch#discard} instead only needs the document ID, and * works by marking the current version of the document as discarded, so it * is immediately ignored by searches. This is faster and more convenient * than {@link MiniSearch#remove}, but the index is not immediately * modified. To take care of that, vacuuming is performed after a certain * number of documents are discarded, cleaning up the index and allowing * memory to be released. * * After discarding a document, it is possible to re-add a new version, and * only the new version will appear in searches. In other words, discarding * and re-adding a document works exactly like removing and re-adding it. The * {@link MiniSearch.replace} method can also be used to replace a document * with a new version. * * #### Details about vacuuming * * Repetite calls to this method would leave obsolete document references in * the index, invisible to searches. Two mechanisms take care of cleaning up: * clean up during search, and vacuuming. * * - Upon search, whenever a discarded ID is found (and ignored for the * results), references to the discarded document are removed from the * inverted index entries for the search terms. This ensures that subsequent * searches for the same terms do not need to skip these obsolete references * again. * * - In addition, vacuuming is performed automatically by default (see the * `autoVacuum` field in {@link Options}) after a certain number of * documents are discarded. Vacuuming traverses all terms in the index, * cleaning up all references to discarded documents. Vacuuming can also be * triggered manually by calling {@link MiniSearch#vacuum}. * * @param id The ID of the document to be discarded */ discard(id) { const shortId = this._idToShortId.get(id); if (shortId == null) { throw new Error(`MiniSearch: cannot discard document with ID ${id}: it is not in the index`); } this._idToShortId.delete(id); this._documentIds.delete(shortId); this._storedFields.delete(shortId); (this._fieldLength.get(shortId) || []).forEach((fieldLength, fieldId) => { this.removeFieldLength(shortId, fieldId, this._documentCount, fieldLength); }); this._fieldLength.delete(shortId); this._documentCount -= 1; this._dirtCount += 1; this.maybeAutoVacuum(); } maybeAutoVacuum() { if (this._options.autoVacuum === false) { return; } const { minDirtFactor, minDirtCount, batchSize, batchWait } = this._options.autoVacuum; this.conditionalVacuum({ batchSize, batchWait }, { minDirtCount, minDirtFactor }); } /** * Discards the documents with the given IDs, so they won't appear in search * results * * It is equivalent to calling {@link MiniSearch#discard} for all the given * IDs, but with the optimization of triggering at most one automatic * vacuuming at the end. * * Note: to remove all documents from the index, it is faster and more * convenient to call {@link MiniSearch.removeAll} with no argument, instead * of passing all IDs to this method. */ discardAll(ids) { const autoVacuum = this._options.autoVacuum; try { this._options.autoVacuum = false; for (const id of ids) { this.discard(id); } } finally { this._options.autoVacuum = autoVacuum; } this.maybeAutoVacuum(); } /** * It replaces an existing document with the given updated version * * It works by discarding the current version and adding the updated one, so * it is functionally equivalent to calling {@link MiniSearch#discard} * followed by {@link MiniSearch#add}. The ID of the updated document should * be the same as the original one. * * Since it uses {@link MiniSearch#discard} internally, this method relies on * vacuuming to clean up obsolete document references from the index, allowing * memory to be released (see {@link MiniSearch#discard}). * * @param updatedDocument The updated document to replace the old version * with */ replace(updatedDocument) { const { idField, extractField } = this._options; const id = extractField(updatedDocument, idField); this.discard(id); this.add(updatedDocument); } /** * Triggers a manual vacuuming, cleaning up references to discarded documents * from the inverted index * * Vacuuming is only useful for applications that use the {@link * MiniSearch#discard} or {@link MiniSearch#replace} methods. * * By default, vacuuming is performed automatically when needed (controlled by * the `autoVacuum` field in {@link Options}), so there is usually no need to * call this method, unless one wants to make sure to perform vacuuming at a * specific moment. * * Vacuuming traverses all terms in the inverted index in batches, and cleans * up references to discarded documents from the posting list, allowing memory * to be released. * * The method takes an optional object as argument with the following keys: * * - `batchSize`: the size of each batch (1000 by default) * * - `batchWait`: the number of milliseconds to wait between batches (10 by * default) * * On large indexes, vacuuming could have a non-negligible cost: batching * avoids blocking the thread for long, diluting this cost so that it is not * negatively affecting the application. Nonetheless, this method should only * be called when necessary, and relying on automatic vacuuming is usually * better. * * It returns a promise that resolves (to undefined) when the clean up is * completed. If vacuuming is already ongoing at the time this method is * called, a new one is enqueued immediately after the ongoing one, and a * corresponding promise is returned. However, no more than one vacuuming is * enqueued on top of the ongoing one, even if this method is called more * times (enqueuing multiple ones would be useless). * * @param options Configuration options for the batch size and delay. See * {@link VacuumOptions}. */ vacuum(options = {}) { return this.conditionalVacuum(options); } conditionalVacuum(options, conditions) { if (this._currentVacuum) { this._enqueuedVacuumConditions = this._enqueuedVacuumConditions && conditions; if (this._enqueuedVacuum != null) { return this._enqueuedVacuum; } this._enqueuedVacuum = this._currentVacuum.then(() => { const conditions2 = this._enqueuedVacuumConditions; this._enqueuedVacuumConditions = defaultVacuumConditions; return this.performVacuuming(options, conditions2); }); return this._enqueuedVacuum; } if (this.vacuumConditionsMet(conditions) === false) { return Promise.resolve(); } this._currentVacuum = this.performVacuuming(options); return this._currentVacuum; } performVacuuming(options, conditions) { return __awaiter(this, void 0, void 0, function* () { const initialDirtCount = this._dirtCount; if (this.vacuumConditionsMet(conditions)) { const batchSize = options.batchSize || defaultVacuumOptions.batchSize; const batchWait = options.batchWait || defaultVacuumOptions.batchWait; let i = 1; for (const [term, fieldsData] of this._index) { for (const [fieldId, fieldIndex] of fieldsData) { for (const [shortId] of fieldIndex) { if (this._documentIds.has(shortId)) { continue; } if (fieldIndex.size <= 1) { fieldsData.delete(fieldId); } else { fieldIndex.delete(shortId); } } } if (this._index.get(term).size === 0) { this._index.delete(term); } if (i % batchSize === 0) { yield new Promise((resolve) => setTimeout(resolve, batchWait)); } i += 1; } this._dirtCount -= initialDirtCount; } yield null; this._currentVacuum = this._enqueuedVacuum; this._enqueuedVacuum = null; }); } vacuumConditionsMet(conditions) { if (conditions == null) { return true; } let { minDirtCount, minDirtFactor } = conditions; minDirtCount = minDirtCount || defaultAutoVacuumOptions.minDirtCount; minDirtFactor = minDirtFactor || defaultAutoVacuumOptions.minDirtFactor; return this.dirtCount >= minDirtCount && this.dirtFactor >= minDirtFactor; } /** * Is `true` if a vacuuming operation is ongoing, `false` otherwise */ get isVacuuming() { return this._currentVacuum != null; } /** * The number of documents discarded since the most recent vacuuming */ get dirtCount() { return this._dirtCount; } /** * A number between 0 and 1 giving an indication about the proportion of * documents that are discarded, and can therefore be cleaned up by vacuuming. * A value close to 0 means that the index is relatively clean, while a higher * value means that the index is relatively dirty, and vacuuming could release * memory. */ get dirtFactor() { return this._dirtCount / (1 + this._documentCount + this._dirtCount); } /** * Returns `true` if a document with the given ID is present in the index and * available for search, `false` otherwise * * @param id The document ID */ has(id) { return this._idToShortId.has(id); } /** * Returns the stored fields (as configured in the `storeFields` constructor * option) for the given document ID. Returns `undefined` if the document is * not present in the index. * * @param id The document ID */ getStoredFields(id) { const shortId = this._idToShortId.get(id); if (shortId == null) { return void 0; } return this._storedFields.get(shortId); } /** * Search for documents matching the given search query. * * The result is a list of scored document IDs matching the query, sorted by * descending score, and each including data about which terms were matched and * in which fields. * * ### Basic usage: * * ```javascript * // Search for "zen art motorcycle" with default options: terms have to match * // exactly, and individual terms are joined with OR * miniSearch.search('zen art motorcycle') * // => [ { id: 2, score: 2.77258, match: { ... } }, { id: 4, score: 1.38629, match: { ... } } ] * ``` * * ### Restrict search to specific fields: * * ```javascript * // Search only in the 'title' field * miniSearch.search('zen', { fields: ['title'] }) * ``` * * ### Field boosting: * * ```javascript * // Boost a field * miniSearch.search('zen', { boost: { title: 2 } }) * ``` * * ### Prefix search: * * ```javascript * // Search for "moto" with prefix search (it will match documents * // containing terms that start with "moto" or "neuro") * miniSearch.search('moto neuro', { prefix: true }) * ``` * * ### Fuzzy search: * * ```javascript * // Search for "ismael" with fuzzy search (it will match documents containing * // terms similar to "ismael", with a maximum edit distance of 0.2 term.length * // (rounded to nearest integer) * miniSearch.search('ismael', { fuzzy: 0.2 }) * ``` * * ### Combining strategies: * * ```javascript * // Mix of exact match, prefix search, and fuzzy search * miniSearch.search('ismael mob', { * prefix: true, * fuzzy: 0.2 * }) * ``` * * ### Advanced prefix and fuzzy search: * * ```javascript * // Perform fuzzy and prefix search depending on the search term. Here * // performing prefix and fuzzy search only on terms longer than 3 characters * miniSearch.search('ismael mob', { * prefix: term => term.length > 3 * fuzzy: term => term.length > 3 ? 0.2 : null * }) * ``` * * ### Combine with AND: * * ```javascript * // Combine search terms with AND (to match only documents that contain both * // "motorcycle" and "art") * miniSearch.search('motorcycle art', { combineWith: 'AND' }) * ``` * * ### Combine with AND_NOT: * * There is also an AND_NOT combinator, that finds documents that match the * first term, but do not match any of the other terms. This combinator is * rarely useful with simple queries, and is meant to be used with advanced * query combinations (see later for more details). * * ### Filtering results: * * ```javascript * // Filter only results in the 'fiction' category (assuming that 'category' * // is a stored field) * miniSearch.search('motorcycle art', { * filter: (result) => result.category === 'fiction' * }) * ``` * * ### Wildcard query * * Searching for an empty string (assuming the default tokenizer) returns no * results. Sometimes though, one needs to match all documents, like in a * "wildcard" search. This is possible by passing the special value * {@link MiniSearch.wildcard} as the query: * * ```javascript * // Return search results for all documents * miniSearch.search(MiniSearch.wildcard) * ``` * * Note that search options such as `filter` and `boostDocument` are still * applied, influencing which results are returned, and their order: * * ```javascript * // Return search results for all documents in the 'fiction' category * miniSearch.search(MiniSearch.wildcard, { * filter: (result) => result.category === 'fiction' * }) * ``` * * ### Advanced combination of queries: * * It is possible to combine different subqueries with OR, AND, and AND_NOT, * and even with different search options, by passing a query expression * tree object as the first argument, instead of a string. * * ```javascript * // Search for documents that contain "zen" and ("motorcycle" or "archery") * miniSearch.search({ * combineWith: 'AND', * queries: [ * 'zen', * { * combineWith: 'OR', * queries: ['motorcycle', 'archery'] * } * ] * }) * * // Search for documents that contain ("apple" or "pear") but not "juice" and * // not "tree" * miniSearch.search({ * combineWith: 'AND_NOT', * queries: [ * { * combineWith: 'OR', * queries: ['apple', 'pear'] * }, * 'juice', * 'tree' * ] * }) * ``` * * Each node in the expression tree can be either a string, or an object that * supports all {@link SearchOptions} fields, plus a `queries` array field for * subqueries. * * Note that, while this can become complicated to do by hand for complex or * deeply nested queries, it provides a formalized expression tree API for * external libraries that implement a parser for custom query languages. * * @param query Search query * @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. */ search(query, searchOptions = {}) { const { searchOptions: globalSearchOptions } = this._options; const searchOptionsWithDefaults = Object.assign(Object.assign({}, globalSearchOptions), searchOptions); const rawResults = this.executeQuery(query, searchOptions); const results = []; for (const [docId, { score, terms, match }] of rawResults) { const quality = terms.length || 1; const result = { id: this._documentIds.get(docId), score: score * quality, terms: Object.keys(match), queryTerms: terms, match }; Object.assign(result, this._storedFields.get(docId)); if (searchOptionsWithDefaults.filter == null || searchOptionsWithDefaults.filter(result)) { results.push(result); } } if (query === _MiniSearch.wildcard && searchOptionsWithDefaults.boostDocument == null) { return results; } results.sort(byScore); return results; } /** * Provide suggestions for the given search query * * The result is a list of suggested modified search queries, derived from the * given search query, each with a relevance score, sorted by descending score. * * By default, it uses the same options used for search, except that by * default it performs prefix search on the last term of the query, and * combine terms with `'AND'` (requiring all query terms to match). Custom * options can be passed as a second argument. Defaults can be changed upon * calling the {@link MiniSearch} constructor, by passing a * `autoSuggestOptions` option. * * ### Basic usage: * * ```javascript * // Get suggestions for 'neuro': * miniSearch.autoSuggest('neuro') * // => [ { suggestion: 'neuromancer', terms: [ 'neuromancer' ], score: 0.46240 } ] * ``` * * ### Multiple words: * * ```javascript * // Get suggestions for 'zen ar': * miniSearch.autoSuggest('zen ar') * // => [ * // { suggestion: 'zen archery art', terms: [ 'zen', 'archery', 'art' ], score: 1.73332 }, * // { suggestion: 'zen art', terms: [ 'zen', 'art' ], score: 1.21313 } * // ] * ``` * * ### Fuzzy suggestions: * * ```javascript * // Correct spelling mistakes using fuzzy search: * miniSearch.autoSuggest('neromancer', { fuzzy: 0.2 }) * // => [ { suggestion: 'neuromancer', terms: [ 'neuromancer' ], score: 1.03998 } ] * ``` * * ### Filtering: * * ```javascript * // Get suggestions for 'zen ar', but only within the 'fiction' category * // (assuming that 'category' is a stored field): * miniSearch.autoSuggest('zen ar', { * filter: (result) => result.category === 'fiction' * }) * // => [ * // { suggestion: 'zen archery art', terms: [ 'zen', 'archery', 'art' ], score: 1.73332 }, * // { suggestion: 'zen art', terms: [ 'zen', 'art' ], score: 1.21313 } * // ] * ``` * * @param queryString Query string to be expanded into suggestions * @param options Search options. The supported options and default values * are the same as for the {@link MiniSearch#search} method, except that by * default prefix search is performed on the last term in the query, and terms * are combined with `'AND'`. * @return A sorted array of suggestions sorted by relevance score. */ autoSuggest(queryString, options = {}) { options = Object.assign(Object.assign({}, this._options.autoSuggestOptions), options); const suggestions = /* @__PURE__ */ new Map(); for (const { score, terms } of this.search(queryString, options)) { const phrase = terms.join(" "); const suggestion = suggestions.get(phrase); if (suggestion != null) { suggestion.score += score; suggestion.count += 1; } else { suggestions.set(phrase, { score, terms, count: 1 }); } } const results = []; for (const [suggestion, { score, terms, count }] of suggestions) { results.push({ suggestion, terms, score: score / count }); } results.sort(byScore); return results; } /** * Total number of documents available to search */ get documentCount() { return this._documentCount; } /** * Number of terms in the index */ get termCount() { return this._index.size; } /** * Deserializes a JSON index (serialized with `JSON.stringify(miniSearch)`) * and instantiates a MiniSearch instance. It should be given the same options * originally used when serializing the index. * * ### Usage: * * ```javascript * // If the index was serialized with: * let miniSearch = new MiniSearch({ fields: ['title', 'text'] }) * miniSearch.addAll(documents) * * const json = JSON.stringify(miniSearch) * // It can later be deserialized like this: * miniSearch = MiniSearch.loadJSON(json, { fields: ['title', 'text'] }) * ``` * * @param json JSON-serialized index * @param options configuration options, same as the constructor * @return An instance of MiniSearch deserialized from the given JSON. */ static loadJSON(json, options) { if (options == null) { throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index"); } return this.loadJS(JSON.parse(json), options); } /** * Async equivalent of {@link MiniSearch.loadJSON} * * This function is an alternative to {@link MiniSearch.loadJSON} that returns * a promise, and loads the index in batches, leaving pauses between them to avoid * blocking the main thread. It tends to be slower than the synchronous * version, but does not block the main thread, so it can be a better choice * when deserializing very large indexes. * * @param json JSON-serialized index * @param options configuration options, same as the constructor * @return A Promise that will resolve to an instance of MiniSearch deserialized from the given JSON. */ static loadJSONAsync(json, options) { return __awaiter(this, void 0, void 0, function* () { if (options == null) { throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index"); } return this.loadJSAsync(JSON.parse(json), options); }); } /** * Returns the default value of an option. It will throw an error if no option * with the given name exists. * * @param optionName Name of the option * @return The default value of the given option * * ### Usage: * * ```javascript * // Get default tokenizer * MiniSearch.getDefault('tokenize') * * // Get default term processor * MiniSearch.getDefault('processTerm') * * // Unknown options will throw an error * MiniSearch.getDefault('notExisting') * // => throws 'MiniSearch: unknown option "notExisting"' * ``` */ static getDefault(optionName) { if (defaultOptions.hasOwnProperty(optionName)) { return getOwnProperty(defaultOptions, optionName); } else { throw new Error(`MiniSearch: unknown option "${optionName}"`); } } /** * @ignore */ static loadJS(js, options) { const { index, documentIds, fieldLength, storedFields, serializationVersion } = js; const miniSearch = this.instantiateMiniSearch(js, options); miniSearch._documentIds = objectToNumericMap(documentIds); miniSearch._fieldLength = objectToNumericMap(fieldLength); miniSearch._storedFields = objectToNumericMap(storedFields); for (const [shortId, id] of miniSearch._documentIds) { miniSearch._idToShortId.set(id, shortId); } for (const [term, data] of index) { const dataMap = /* @__PURE__ */ new Map(); for (const fieldId of Object.keys(data)) { let indexEntry = data[fieldId]; if (serializationVersion === 1) { indexEntry = indexEntry.ds; } dataMap.set(parseInt(fieldId, 10), objectToNumericMap(indexEntry)); } miniSearch._index.set(term, dataMap); } return miniSearch; } /** * @ignore */ static loadJSAsync(js, options) { return __awaiter(this, void 0, void 0, function* () { const { index, documentIds, fieldLength, storedFields, serializationVersion } = js; const miniSearch = this.instantiateMiniSearch(js, options); miniSearch._documentIds = yield objectToNumericMapAsync(documentIds); miniSearch._fieldLength = yield objectToNumericMapAsync(fieldLength); miniSearch._storedFields = yield objectToNumericMapAsync(storedFields); for (const [shortId, id] of miniSearch._documentIds) { miniSearch._idToShortId.set(id, shortId); } let count = 0; for (const [term, data] of index) { const dataMap = /* @__PURE__ */ new Map(); for (const fieldId of Object.keys(data)) { let indexEntry = data[fieldId]; if (serializationVersion === 1) { indexEntry = indexEntry.ds; } dataMap.set(parseInt(fieldId, 10), yield objectToNumericMapAsync(indexEntry)); } if (++count % 1e3 === 0) yield wait(0); miniSearch._index.set(term, dataMap); } return miniSearch; }); } /** * @ignore */ static instantiateMiniSearch(js, options) { const { documentCount, nextId, fieldIds, averageFieldLength, dirtCount, serializationVersion } = js; if (serializationVersion !== 1 && serializationVersion !== 2) { throw new Error("MiniSearch: cannot deserialize an index created with an incompatible version"); } const miniSearch = new _MiniSearch(options); miniSearch._documentCount = documentCount; miniSearch._nextId = nextId; miniSearch._idToShortId = /* @__PURE__ */ new Map(); miniSearch._fieldIds = fieldIds; miniSearch._avgFieldLength = averageFieldLength; miniSearch._dirtCount = dirtCount || 0; miniSearch._index = new SearchableMap(); return miniSearch; } /** * @ignore */ executeQuery(query, searchOptions = {}) { if (query === _MiniSearch.wildcard) { return this.executeWildcardQuery(searchOptions); } if (typeof query !== "string") { const options2 = Object.assign(Object.assign(Object.assign({}, searchOptions), query), { queries: void 0 }); const results2 = query.queries.map((subquery) => this.executeQuery(subquery, options2)); return this.combineResults(results2, options2.combineWith); } const { tokenize, processTerm, searchOptions: globalSearchOptions } = this._options; const options = Object.assign(Object.assign({ tokenize, processTerm }, globalSearchOptions), searchOptions); const { tokenize: searchTokenize, processTerm: searchProcessTerm } = options; const terms = searchTokenize(query).flatMap((term) => searchProcessTerm(term)).filter((term) => !!term); const queries = terms.map(termToQuerySpec(options)); const results = queries.map((query2) => this.executeQuerySpec(query2, options)); return this.combineResults(results, options.combineWith); } /** * @ignore */ executeQuerySpec(query, searchOptions) { const options = Object.assign(Object.assign({}, this._options.searchOptions), searchOptions); const boosts = (options.fields || this._options.fields).reduce((boosts2, field) => Object.assign(Object.assign({}, boosts2), { [field]: getOwnProperty(options.boost, field) || 1 }), {}); const { boostDocument, weights, maxFuzzy, bm25: bm25params } = options; const { fuzzy: fuzzyWeight, prefix: prefixWeight } = Object.assign(Object.assign({}, defaultSearchOptions.weights), weights); const data = this._index.get(query.term); const results = this.termResults(query.term, query.term, 1, query.termBoost, data, boosts, boostDocument, bm25params); let prefixMatches; let fuzzyMatches; if (query.prefix) { prefixMatches = this._index.atPrefix(query.term); } if (query.fuzzy) { const fuzzy = query.fuzzy === true ? 0.2 : query.fuzzy; const maxDistance = fuzzy < 1 ? Math.min(maxFuzzy, Math.round(query.term.length * fuzzy)) : fuzzy; if (maxDistance) fuzzyMatches = this._index.fuzzyGet(query.term, maxDistance); } if (prefixMatches) { for (const [term, data2] of prefixMatches) { const distance = term.length - query.term.length; if (!distance) { continue; } fuzzyMatches === null || fuzzyMatches === void 0 ? void 0 : fuzzyMatches.delete(term); const weight = prefixWeight * term.length / (term.length + 0.3 * distance); this.termResults(query.term, term, weight, query.termBoost, data2, boosts, boostDocument, bm25params, results); } } if (fuzzyMatches) { for (const term of fuzzyMatches.keys()) { const [data2, distance] = fuzzyMatches.get(term); if (!distance) { continue; } const weight = fuzzyWeight * term.length / (term.length + distance); this.termResults(query.term, term, weight, query.termBoost, data2, boosts, boostDocument, bm25params, results); } } return results; } /** * @ignore */ executeWildcardQuery(searchOptions) { const results = /* @__PURE__ */ new Map(); const options = Object.assign(Object.assign({}, this._options.searchOptions), searchOptions); for (const [shortId, id] of this._documentIds) { const score = options.boostDocument ? options.boostDocument(id, "", this._storedFields.get(shortId)) : 1; results.set(shortId, { score, terms: [], match: {} }); } return results; } /** * @ignore */ combineResults(results, combineWith = OR) { if (results.length === 0) { return /* @__PURE__ */ new Map(); } const operator = combineWith.toLowerCase(); const combinator = combinators[operator]; if (!combinator) { throw new Error(`Invalid combination operator: ${combineWith}`); } return results.reduce(combinator) || /* @__PURE__ */ new Map(); } /** * Allows serialization of the index to JSON, to possibly store it and later * deserialize it with {@link MiniSearch.loadJSON}. * * Normally one does not directly call this method, but rather call the * standard JavaScript `JSON.stringify()` passing the {@link MiniSearch} * instance, and JavaScript will internally call this method. Upon * deserialization, one must pass to {@link MiniSearch.loadJSON} the same * options used to create the original instance that was serialized. * * ### Usage: * * ```javascript * // Serialize the index: * let miniSearch = new MiniSearch({ fields: ['title', 'text'] }) * miniSearch.addAll(documents) * const json = JSON.stringify(miniSearch) * * // Later, to deserialize it: * miniSearch = MiniSearch.loadJSON(json, { fields: ['title', 'text'] }) * ``` * * @return A plain-object serializable representation of the search index. */ toJSON() { const index = []; for (const [term, fieldIndex] of this._index) { const data = {}; for (const [fieldId, freqs] of fieldIndex) { data[fieldId] = Object.fromEntries(freqs); } index.push([term, data]); } return { documentCount: this._documentCount, nextId: this._nextId, documentIds: Object.fromEntries(this._documentIds), fieldIds: this._fieldIds, fieldLength: Object.fromEntries(this._fieldLength), averageFieldLength: this._avgFieldLength, storedFields: Object.fromEntries(this._storedFields), dirtCount: this._dirtCount, index, serializationVersion: 2 }; } /** * @ignore */ termResults(sourceTerm, derivedTerm, termWeight, termBoost, fieldTermData, fieldBoosts, boostDocumentFn, bm25params, results = /* @__PURE__ */ new Map()) { if (fieldTermData == null) return results; for (const field of Object.keys(fieldBoosts)) { const fieldBoost = fieldBoosts[field]; const fieldId = this._fieldIds[field]; const fieldTermFreqs = fieldTermData.get(fieldId); if (fieldTermFreqs == null) continue; let matchingFields = fieldTermFreqs.size; const avgFieldLength = this._avgFieldLength[fieldId]; for (const docId of fieldTermFreqs.keys()) { if (!this._documentIds.has(docId)) { this.removeTerm(fieldId, docId, derivedTerm); matchingFields -= 1; continue; } const docBoost = boostDocumentFn ? boostDocumentFn(this._documentIds.get(docId), derivedTerm, this._storedFields.get(docId)) : 1; if (!docBoost) continue; const termFreq = fieldTermFreqs.get(docId); const fieldLength = this._fieldLength.get(docId)[fieldId]; const rawScore = calcBM25Score(termFreq, matchingFields, this._documentCount, fieldLength, avgFieldLength, bm25params); const weightedScore = termWeight * termBoost * fieldBoost * docBoost * rawScore; const result = results.get(docId); if (result) { result.score += weightedScore; assignUniqueTerm(result.terms, sourceTerm); const match = getOwnProperty(result.match, derivedTerm); if (match) { match.push(field); } else { result.match[derivedTerm] = [field]; } } else { results.set(docId, { score: weightedScore, terms: [sourceTerm], match: { [derivedTerm]: [field] } }); } } } return results; } /** * @ignore */ addTerm(fieldId, documentId, term) { const indexData = this._index.fetch(term, createMap); let fieldIndex = indexData.get(fieldId); if (fieldIndex == null) { fieldIndex = /* @__PURE__ */ new Map(); fieldIndex.set(documentId, 1); indexData.set(fieldId, fieldIndex); } else { const docs = fieldIndex.get(documentId); fieldIndex.set(documentId, (docs || 0) + 1); } } /** * @ignore */ removeTerm(fieldId, documentId, term) { if (!this._index.has(term)) { this.warnDocumentChanged(documentId, fieldId, term); return; } const indexData = this._index.fetch(term, createMap); const fieldIndex = indexData.get(fieldId); if (fieldIndex == null || fieldIndex.get(documentId) == null) { this.warnDocumentChanged(documentId, fieldId, term); } else if (fieldIndex.get(documentId) <= 1) { if (fieldIndex.size <= 1) { indexData.delete(fieldId); } else { fieldIndex.delete(documentId); } } else { fieldIndex.set(documentId, fieldIndex.get(documentId) - 1); } if (this._index.get(term).size === 0) { this._index.delete(term); } } /** * @ignore */ warnDocumentChanged(shortDocumentId, fieldId, term) { for (const fieldName of Object.keys(this._fieldIds)) { if (this._fieldIds[fieldName] === fieldId) { this._options.logger("warn", `MiniSearch: document with ID ${this._documentIds.get(shortDocumentId)} has changed before removal: term "${term}" was not present in field "${fieldName}". Removing a document after it has changed can corrupt the index!`, "version_conflict"); return; } } } /** * @ignore */ addDocumentId(documentId) { const shortDocumentId = this._nextId; this._idToShortId.set(documentId, shortDocumentId); this._documentIds.set(shortDocumentId, documentId); this._documentCount += 1; this._nextId += 1; return shortDocumentId; } /** * @ignore */ addFields(fields) { for (let i = 0; i < fields.length; i++) { this._fieldIds[fields[i]] = i; } } /** * @ignore */ addFieldLength(documentId, fieldId, count, length) { let fieldLengths = this._fieldLength.get(documentId); if (fieldLengths == null) this._fieldLength.set(documentId, fieldLengths = []); fieldLengths[fieldId] = length; const averageFieldLength = this._avgFieldLength[fieldId] || 0; const totalFieldLength = averageFieldLength * count + length; this._avgFieldLength[fieldId] = totalFieldLength / (count + 1); } /** * @ignore */ removeFieldLength(documentId, fieldId, count, length) { if (count === 1) { this._avgFieldLength[fieldId] = 0; return; } const totalFieldLength = this._avgFieldLength[fieldId] * count - length; this._avgFieldLength[fieldId] = totalFieldLength / (count - 1); } /** * @ignore */ saveStoredFields(documentId, doc) { const { storeFields, extractField } = this._options; if (storeFields == null || storeFields.length === 0) { return; } let documentFields = this._storedFields.get(documentId); if (documentFields == null) this._storedFields.set(documentId, documentFields = {}); for (const fieldName of storeFields) { const fieldValue = extractField(doc, fieldName); if (fieldValue !== void 0) documentFields[fieldName] = fieldValue; } } }; MiniSearch.wildcard = Symbol("*"); var getOwnProperty = (object, property) => Object.prototype.hasOwnProperty.call(object, property) ? object[property] : void 0; var combinators = { [OR]: (a, b) => { for (const docId of b.keys()) { const existing = a.get(docId); if (existing == null) { a.set(docId, b.get(docId)); } else { const { score, terms, match } = b.get(docId); existing.score = existing.score + score; existing.match = Object.assign(existing.match, match); assignUniqueTerms(existing.terms, terms); } } return a; }, [AND]: (a, b) => { const combined = /* @__PURE__ */ new Map(); for (const docId of b.keys()) { const existing = a.get(docId); if (existing == null) continue; const { score, terms, match } = b.get(docId); assignUniqueTerms(existing.terms, terms); combined.set(docId, { score: existing.score + score, terms: existing.terms, match: Object.assign(existing.match, match) }); } return combined; }, [AND_NOT]: (a, b) => { for (const docId of b.keys()) a.delete(docId); return a; } }; var defaultBM25params = { k: 1.2, b: 0.7, d: 0.5 }; var calcBM25Score = (termFreq, matchingCount, totalCount, fieldLength, avgFieldLength, bm25params) => { const { k, b, d } = bm25params; const invDocFreq = Math.log(1 + (totalCount - matchingCount + 0.5) / (matchingCount + 0.5)); return invDocFreq * (d + termFreq * (k + 1) / (termFreq + k * (1 - b + b * fieldLength / avgFieldLength))); }; var termToQuerySpec = (options) => (term, i, terms) => { const fuzzy = typeof options.fuzzy === "function" ? options.fuzzy(term, i, terms) : options.fuzzy || false; const prefix = typeof options.prefix === "function" ? options.prefix(term, i, terms) : options.prefix === true; const termBoost = typeof options.boostTerm === "function" ? options.boostTerm(term, i, terms) : 1; return { term, fuzzy, prefix, termBoost }; }; var defaultOptions = { idField: "id", extractField: (document, fieldName) => document[fieldName], tokenize: (text) => text.split(SPACE_OR_PUNCTUATION), processTerm: (term) => term.toLowerCase(), fields: void 0, searchOptions: void 0, storeFields: [], logger: (level, message) => { if (typeof (console === null || console === void 0 ? void 0 : console[level]) === "function") console[level](message); }, autoVacuum: true }; var defaultSearchOptions = { combineWith: OR, prefix: false, fuzzy: false, maxFuzzy: 6, boost: {}, weights: { fuzzy: 0.45, prefix: 0.375 }, bm25: defaultBM25params }; var defaultAutoSuggestOptions = { combineWith: AND, prefix: (term, i, terms) => i === terms.length - 1 }; var defaultVacuumOptions = { batchSize: 1e3, batchWait: 10 }; var defaultVacuumConditions = { minDirtFactor: 0.1, minDirtCount: 20 }; var defaultAutoVacuumOptions = Object.assign(Object.assign({}, defaultVacuumOptions), defaultVacuumConditions); var assignUniqueTerm = (target, term) => { if (!target.includes(term)) target.push(term); }; var assignUniqueTerms = (target, source) => { for (const term of source) { if (!target.includes(term)) target.push(term); } }; var byScore = ({ score: a }, { score: b }) => b - a; var createMap = () => /* @__PURE__ */ new Map(); var objectToNumericMap = (object) => { const map = /* @__PURE__ */ new Map(); for (const key of Object.keys(object)) { map.set(parseInt(key, 10), object[key]); } return map; }; var objectToNumericMapAsync = (object) => __awaiter(void 0, void 0, void 0, function* () { const map = /* @__PURE__ */ new Map(); let count = 0; for (const key of Object.keys(object)) { map.set(parseInt(key, 10), object[key]); if (++count % 1e3 === 0) { yield wait(0); } } return map; }); var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); var SPACE_OR_PUNCTUATION = /[\n\r\p{Z}\p{P}]+/u; export { MiniSearch as default }; //# sourceMappingURL=vitepress___minisearch.js.map