slimsearch
Version:
Tiny but powerful full-text search engine for browser and Node
1 lines • 29.3 kB
Source Map (JSON)
{"version":3,"file":"SearchableMap.mjs","sources":["../src/SearchableMap/TreeIterator.ts","../src/SearchableMap/fuzzySearch.ts","../src/SearchableMap/SearchableMap.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"],"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"],"mappings":"AAEa,MAAAA,EAAU,UAEVC,EAAO,OAEPC,EAAS,SAETC,EAAO,GAwBb,MAAMC,CAEb,CACE,IACA,MACA,MAEA,YAAYC,EAAqBC,EAAS,CACxC,MAAMC,EAAOF,EAAI,MACXG,EAAO,MAAM,KAAKD,EAAK,KAAM,CAAA,EAEnC,KAAK,IAAMF,EACX,KAAK,MAAQC,EACb,KAAK,MAAQE,EAAK,OAAS,EAAI,CAAC,CAAE,KAAAD,EAAM,KAAAC,CAAK,CAAC,EAAI,CACpD,CAAA,CAEA,MAAqC,CACnC,MAAMC,EAAQ,KAAK,KAAA,EAEnB,OAAA,KAAK,YAEEA,CACT,CAEA,MAAqC,CACnC,GAAI,KAAK,MAAM,SAAW,EAAG,MAAO,CAAE,KAAM,GAAM,MAAO,MAAU,EAGnE,KAAM,CAAE,KAAAF,EAAM,KAAAC,CAAK,EAAIE,EAAK,KAAK,KAAK,EAEtC,GAAIA,EAAKF,CAAI,IAAML,EAAM,MAAO,CAAE,KAAM,GAAO,MAAO,KAAK,OAAS,CAAA,EAGpE,MAAMQ,EAAQJ,EAAK,IAAIG,EAAKF,CAAI,CAAE,EAElC,OAAA,KAAK,MAAM,KAAK,CAAE,KAAMG,EAAO,KAAM,MAAM,KAAKA,EAAM,KAAM,CAAA,CAAE,CAAC,EAExD,KAAK,MACd,CAEA,WAAkB,CAChB,GAAI,KAAK,MAAM,SAAW,EAAG,OAG7B,MAAMH,EAAOE,EAAK,KAAK,KAAK,EAAG,KAE/BF,EAAK,IAAA,EACD,EAAAA,EAAK,OAAS,KAElB,KAAK,MAAM,IAAI,EACf,KAAK,UACP,EAAA,CAEA,KAAc,CACZ,OACE,KAAK,IAAI,QACT,KAAK,MACF,IAAI,CAAC,CAAE,KAAAA,CAAK,IAAME,EAAKF,CAAI,CAAC,EAC5B,OAAQI,GAAQA,IAAQT,CAAI,EAC5B,KAAK,EAAE,CAEd,CAEA,OAAW,CAET,OAAOO,EAAK,KAAK,KAAK,EAAG,KAAK,IAAIP,CAAI,CACxC,CAEA,QAAuB,CACrB,OAAQ,KAAK,MACX,CAAA,KAAKD,EACH,OAAO,KAAK,MAAM,EACpB,KAAKD,EACH,OAAO,KAAK,MACd,QACE,MAAO,CAAC,KAAK,IAAO,EAAA,KAAK,MAAO,CAAA,CACpC,CACF,CAEA,CAAC,OAAO,QAAQ,GAAU,CACxB,OAAO,IACT,CACF,CAEA,MAAMS,EAAWG,GACRA,EAAMA,EAAM,OAAS,CAAC,EClHlBC,EAAc,CACzBP,EACAQ,EACAC,IACwB,CACxB,MAAMC,EAA+B,IAAI,IAEzC,GAAI,OAAOF,GAAU,SAAU,OAAOE,EAGtC,MAAMC,EAAIH,EAAM,OAAS,EAGnBI,EAAID,EAAIF,EAGRI,EAAS,IAAI,WAAWD,EAAID,CAAC,EAAE,KAAKF,EAAc,CAAC,EAEzD,QAASK,EAAI,EAAGA,EAAIH,EAAG,EAAEG,EAAGD,EAAOC,CAAC,EAAIA,EACxC,QAASC,EAAI,EAAGA,EAAIH,EAAG,EAAEG,EAAGF,EAAOE,EAAIJ,CAAC,EAAII,EAE5C,OAAAC,EAAQhB,EAAMQ,EAAOC,EAAaC,EAASG,EAAQ,EAAGF,EAAG,EAAE,EAEpDD,CACT,EAiBMM,EAAU,CACdhB,EACAQ,EACAC,EACAC,EACAG,EACAD,EACAD,EACAM,IACS,CACT,MAAMC,EAASN,EAAID,EAEnBN,EAAK,UAAWA,KAAOL,EAAK,KAC1B,EAAA,GAAIK,IAAQT,EAAM,CAGhB,MAAMuB,EAAWN,EAAOK,EAAS,CAAC,EAE9BC,GAAYV,GAEdC,EAAQ,IAAIO,EAAQ,CAACjB,EAAK,IAAIK,CAAG,EAAIc,CAAQ,CAAC,CAClD,KAAO,CAIL,IAAIJ,EAAIH,EAER,QAASQ,EAAM,EAAGA,EAAMf,EAAI,OAAQ,EAAEe,EAAK,EAAEL,EAAG,CAC9C,MAAMM,EAAOhB,EAAIe,CAAG,EACdE,EAAgBX,EAAII,EACpBQ,EAAgBD,EAAgBX,EAItC,IAAIa,EAAcX,EAAOS,CAAa,EAEtC,MAAMG,EAAO,KAAK,IAAI,EAAGV,EAAIN,EAAc,CAAC,EACtCiB,EAAO,KAAK,IAAIf,EAAI,EAAGI,EAAIN,CAAW,EAG5C,QAASK,EAAIW,EAAMX,EAAIY,EAAM,EAAEZ,EAAG,CAChC,MAAMa,EAAYN,IAASb,EAAMM,CAAC,EAK5Bc,EAAMf,EAAOU,EAAgBT,CAAC,GAAI,CAACa,EACnCE,EAAMhB,EAAOU,EAAgBT,EAAI,CAAC,EAAI,EACtCgB,EAAMjB,EAAOS,EAAgBR,CAAC,EAAI,EAElCiB,EAAQlB,EAAOS,EAAgBR,EAAI,CAAC,EAAI,KAAK,IACjDc,EACAC,EACAC,CACF,EAEIC,EAAOP,IAAaA,EAAcO,EACxC,CAIA,GAAIP,EAAcf,EAAa,SAASJ,CAC1C,CAEAW,EAEEhB,EAAK,IAAIK,CAAG,EACZG,EACAC,EACAC,EACAG,EACAE,EACAJ,EACAM,EAASZ,CACX,CACF,CACJ,ECtGO,MAAM2B,CAA2B,CAItC,MAKA,QAEQ,MAA4B,OAUpC,YAAYC,EAAyB,IAAI,IAAOhB,EAAS,GAAI,CAC3D,KAAK,MAAQgB,EACb,KAAK,QAAUhB,CACjB,CA8BA,SAASA,EAAsC,CAC7C,GAAI,CAACA,EAAO,WAAW,KAAK,OAAO,EAAG,MAAM,IAAI,MAAM,mBAAmB,EAEzE,KAAM,CAACjB,EAAMkC,CAAI,EAAIC,EACnB,KAAK,MACLlB,EAAO,MAAM,KAAK,QAAQ,MAAM,CAClC,EAEA,GAAIjB,IAAS,OAAW,CACtB,KAAM,CAACoC,EAAY/B,CAAG,EAAIF,EAAK+B,CAAI,EAEnC,UAAWG,KAAKD,EAAW,KAAK,EAC9B,GAAIC,IAAMzC,GAAQyC,EAAE,WAAWhC,CAAG,EAAG,CACnC,MAAML,EAAO,IAAI,IAGjB,OAAAA,EAAK,IAAIqC,EAAE,MAAMhC,EAAI,MAAM,EAAG+B,EAAW,IAAIC,CAAC,CAAE,EAEzC,IAAIL,EAAqBhC,EAAMiB,CAAM,CAC9C,CACJ,CAEA,OAAO,IAAIe,EAAqBhC,EAAMiB,CAAM,CAC9C,CAKA,OAAc,CACZ,KAAK,MAAQ,OACb,KAAK,MAAM,OACb,CAMA,OAAOZ,EAAmB,CACxB,YAAK,MAAQ,OAENiC,EAAO,KAAK,MAAOjC,CAAG,CAC/B,CAMA,SAA0C,CACxC,OAAO,IAAIR,EAAa,KAAMJ,CAAO,CACvC,CAMA,QAAQ8C,EAAmE,CACzE,SAAW,CAAClC,EAAKH,CAAK,IAAK,KAAMqC,EAAGlC,EAAKH,EAAO,IAAI,CACtD,CA8BA,SAASG,EAAamC,EAA8C,CAClE,OAAOjC,EAAmB,KAAK,MAAOF,EAAKmC,CAAe,CAC5D,CAQA,IAAInC,EAAgC,CAClC,MAAML,EAAOyC,EAAc,KAAK,MAAOpC,CAAG,EAE1C,OAAOL,IAAS,OAAYA,EAAK,IAAIJ,CAAI,EAAI,MAC/C,CAOA,IAAIS,EAAsB,CAGxB,OAFaoC,EAAO,KAAK,MAAOpC,CAAG,GAEtB,IAAIT,CAAI,GAAK,EAC5B,CAMA,MAAoC,CAClC,OAAO,IAAIC,EAAa,KAAMH,CAAI,CACpC,CAQA,IAAIW,EAAaH,EAAoB,CACnC,GAAI,OAAOG,GAAQ,SAAU,MAAM,IAAI,MAAM,sBAAsB,EAEnE,OAAA,KAAK,MAAQ,OACAqC,EAAW,KAAK,MAAOrC,CAAG,EAElC,IAAIT,EAAMM,CAAK,EAEb,IACT,CAKA,IAAI,MAAe,CACjB,GAAI,KAAK,MAAO,OAAO,KAAK,MAG5B,KAAK,MAAQ,EAEb,MAAMyC,EAAO,KAAK,QAElB,EAAA,KAAO,CAACA,EAAK,OAAO,MAAM,KAAK,OAAS,EAExC,OAAO,KAAK,KACd,CAsBA,OAAOtC,EAAakC,EAA+C,CACjE,GAAI,OAAOlC,GAAQ,SAAU,MAAM,IAAI,MAAM,sBAAsB,EAEnE,KAAK,MAAQ,OACb,MAAML,EAAO0C,EAAW,KAAK,MAAOrC,CAAG,EAEvC,OAAAL,EAAK,IAAIJ,EAAM2C,EAAGvC,EAAK,IAAIJ,CAAI,CAAC,CAAC,EAE1B,IACT,CAkBA,MAAMS,EAAauC,EAA6B,CAC9C,GAAI,OAAOvC,GAAQ,SAAU,MAAM,IAAI,MAAM,sBAAsB,EAEnE,KAAK,MAAQ,OACb,MAAML,EAAO0C,EAAW,KAAK,MAAOrC,CAAG,EAEvC,IAAIH,EAAQF,EAAK,IAAIJ,CAAI,EAEzB,OAAIM,IAAU,QAAWF,EAAK,IAAIJ,EAAOM,EAAQ0C,EAAU,CAAA,EAEpD1C,CACT,CAMA,QAAwC,CACtC,OAAO,IAAIL,EAAa,KAAMF,CAAM,CACtC,CAKA,CAAC,OAAO,QAAQ,GAAoC,CAClD,OAAO,KAAK,QAAQ,CACtB,CAQA,OAAO,KACLkD,EACkB,CAClB,MAAMZ,EAAO,IAAID,EAEjB,SAAW,CAAC3B,EAAKH,CAAK,IAAK2C,EAASZ,EAAK,IAAI5B,EAAKH,CAAK,EAEvD,OAAO+B,CACT,CAQA,OAAO,WAAoBa,EAA6C,CACtE,OAAOd,EAAc,KAAQ,OAAO,QAAQc,CAAM,CAAC,CACrD,CACF,CAEA,MAAMX,EAAY,CAChBF,EACA5B,EACA6B,EAAgB,CACwB,IAAA,CACxC,GAAI7B,EAAI,SAAW,GAAK4B,GAAQ,KAAM,MAAO,CAACA,EAAMC,CAAI,EAExD,UAAWa,KAAWd,EAAK,OACzB,GAAIc,IAAYnD,GAAQS,EAAI,WAAW0C,CAAO,EAC5C,OAAAb,EAAK,KAAK,CAACD,EAAMc,CAAO,CAAC,EAElBZ,EAAUF,EAAK,IAAIc,CAAO,EAAG1C,EAAI,MAAM0C,EAAQ,MAAM,EAAGb,CAAI,EAGvE,OAAAA,EAAK,KAAK,CAACD,EAAM5B,CAAG,CAAC,EAEd8B,EAAU,OAAW,GAAID,CAAI,CACtC,EAEMO,EAAS,CACbR,EACA5B,IAC6B,CAE7B,GAAIA,EAAI,SAAW,GAAK,CAAC4B,EAAM,OAAOA,EAEtC,UAAWc,KAAWd,EAAK,KAAA,EACzB,GAAIc,IAAYnD,GAAQS,EAAI,WAAW0C,CAAO,EAE5C,OAAON,EAAOR,EAAK,IAAIc,CAAO,EAAI1C,EAAI,MAAM0C,EAAQ,MAAM,CAAC,CACjE,EAKML,EAAa,CAAU1C,EAAoBK,IAA8B,CAC7E,MAAM2C,EAAY3C,EAAI,OAGtB4C,EAAO,QAAS7B,EAAM,EAAGpB,GAAQoB,EAAM4B,GAAa,CAElD,UAAWX,KAAKrC,EAAK,KAAK,EACxB,GAAIqC,IAAMzC,GAAQS,EAAIe,CAAG,IAAMiB,EAAE,CAAC,EAAG,CACnC,MAAMa,EAAM,KAAK,IAAIF,EAAY5B,EAAKiB,EAAE,MAAM,EAG9C,IAAInB,EAAS,EAEb,KAAOA,EAASgC,GAAO7C,EAAIe,EAAMF,CAAM,IAAMmB,EAAEnB,CAAM,GAAG,EAAEA,EAG1D,MAAMd,EAAQJ,EAAK,IAAIqC,CAAC,EAExB,GAAInB,IAAWmB,EAAE,OAEfrC,EAAOI,MACF,CAGL,MAAM+C,EAAe,IAAI,IAEzBA,EAAa,IAAId,EAAE,MAAMnB,CAAM,EAAGd,CAAK,EACvCJ,EAAK,IAAIK,EAAI,MAAMe,EAAKA,EAAMF,CAAM,EAAGiC,CAAY,EACnDnD,EAAK,OAAOqC,CAAC,EACbrC,EAAOmD,CACT,CAEA/B,GAAOF,EACP,SAAS+B,CACX,CAGF,MAAM7C,EAAQ,IAAI,IAElB,OAAAJ,EAAK,IAAIK,EAAI,MAAMe,CAAG,EAAGhB,CAAK,EAEvBA,CACT,CAEA,OAAOJ,CACT,EAEMsC,EAAS,CAAUL,EAAoB5B,IAAsB,CACjE,KAAM,CAACL,EAAMkC,CAAI,EAAIC,EAAUF,EAAM5B,CAAG,EAExC,GAAIL,IAAS,QAIb,GAFAA,EAAK,OAAOJ,CAAI,EAEZI,EAAK,OAAS,EAChBoD,EAAQlB,CAAI,UACHlC,EAAK,OAAS,EAAG,CAC1B,KAAM,CAACK,EAAKH,CAAK,EACfF,EAAK,UAAU,KAAA,EAIf,MAEFqD,EAAMnB,EAAM7B,EAAKH,CAAK,CACxB,EACF,EAEMkD,EAAoBlB,GAAwB,CAChD,GAAIA,EAAK,SAAW,EAAG,OAEvB,KAAM,CAAClC,EAAMK,CAAG,EAAIF,EAAK+B,CAAI,EAI7B,GAFAlC,EAAK,OAAOK,CAAG,EAEXL,EAAK,OAAS,EAChBoD,EAAQlB,EAAK,MAAM,EAAG,EAAE,CAAC,UAChBlC,EAAK,OAAS,EAAG,CAC1B,KAAM,CAACK,EAAKH,CAAK,EACfF,EAAK,UAAU,KAAK,EAIpB,MAEEK,IAAQT,GAAMyD,EAAMnB,EAAK,MAAM,EAAG,EAAE,EAAG7B,EAAKH,CAAK,CACvD,CACF,EAEMmD,EAAQ,CACZnB,EACA7B,EACAH,IACS,CACT,GAAIgC,EAAK,SAAW,EAAG,OAEvB,KAAM,CAAClC,EAAMsD,CAAO,EAAInD,EAAK+B,CAAI,EAEjClC,EAAK,IAAIsD,EAAUjD,EAAKH,CAAK,EAC7BF,EAAK,OAAOsD,CAAO,CACrB,EAEMnD,EAAiBG,GACdA,EAAMA,EAAM,OAAS,CAAC"}