UNPKG

@akryum/flexsearch-es

Version:

Next-Generation full text search library with zero dependencies.

1,563 lines (1,546 loc) 41.8 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.js var src_exports = {}; __export(src_exports, { Document: () => document_default, Worker: () => worker_default, create: () => create2, default: () => src_default, registerCharset: () => registerCharset, registerLanguage: () => registerLanguage }); module.exports = __toCommonJS(src_exports); // src/config.js var DEBUG = false; var SUPPORT_WORKER = true; var SUPPORT_ENCODER = true; var SUPPORT_CACHE = true; var SUPPORT_ASYNC = true; var SUPPORT_STORE = true; var SUPPORT_TAGS = true; var SUPPORT_SUGGESTION = true; var SUPPORT_SERIALIZE = true; // src/type.js function IndexInterface() { this.cache = null; this.matcher = null; this.stemmer = null; this.filter = null; } IndexInterface.prototype.add; IndexInterface.prototype.append; IndexInterface.prototype.search; IndexInterface.prototype.update; IndexInterface.prototype.remove; // src/common.js function parse_option(value, default_value) { return typeof value !== "undefined" ? value : default_value; } function create_object_array(count) { const array = new Array(count); for (let i = 0; i < count; i++) { array[i] = create_object(); } return array; } function get_keys(obj) { return Object.keys(obj); } function create_object() { return /* @__PURE__ */ Object.create(null); } function concat(arrays) { return [].concat.apply([], arrays); } function sort_by_length_down(a, b) { return b.length - a.length; } function is_array(val) { return val.constructor === Array; } function is_string(val) { return typeof val === "string"; } function is_object(val) { return typeof val === "object"; } function is_function(val) { return typeof val === "function"; } // src/lang.js function pipeline(str, normalize2, split, _collapse) { if (str) { if (normalize2) { str = replace( str, /** @type {Array<string|RegExp>} */ normalize2 ); } if (this.matcher) { str = replace(str, this.matcher); } if (this.stemmer && str.length > 1) { str = replace(str, this.stemmer); } if (_collapse && str.length > 1) { str = collapse(str); } if (split || split === "") { const words = str.split( /** @type {string|RegExp} */ split ); return this.filter ? filter(words, this.filter) : words; } } return str; } var regex_whitespace = /[\p{Z}\p{S}\p{P}\p{C}]+/u; function init_filter(words) { const filter2 = create_object(); for (let i = 0, length = words.length; i < length; i++) { filter2[words[i]] = 1; } return filter2; } function init_stemmer_or_matcher(obj, is_stemmer) { const keys = get_keys(obj); const length = keys.length; const final = []; let removal = "", count = 0; for (let i = 0, key, tmp; i < length; i++) { key = keys[i]; tmp = obj[key]; if (tmp) { final[count++] = regex(is_stemmer ? "(?!\\b)" + key + "(\\b|_)" : key); final[count++] = tmp; } else { removal += (removal ? "|" : "") + key; } } if (removal) { final[count++] = regex(is_stemmer ? "(?!\\b)(" + removal + ")(\\b|_)" : "(" + removal + ")"); final[count] = ""; } return final; } function replace(str, regexp) { for (let i = 0, len = regexp.length; i < len; i += 2) { str = str.replace(regexp[i], regexp[i + 1]); if (!str) { break; } } return str; } function regex(str) { return new RegExp(str, "g"); } function collapse(string) { let final = "", prev = ""; for (let i = 0, len = string.length, char; i < len; i++) { if ((char = string[i]) !== prev) { final += prev = char; } } return final; } function filter(words, map) { const length = words.length; const filtered = []; for (let i = 0, count = 0; i < length; i++) { const word = words[i]; if (word && !map[word]) { filtered[count++] = word; } } return filtered; } // src/lang/latin/default.js function encode(str) { return pipeline.call( this, /* string: */ ("" + str).toLowerCase(), /* normalize: */ false, /* split: */ regex_whitespace, /* collapse: */ false ); } // src/global.js var global_lang = {}; var global_charset = {}; function registerCharset(name, charset) { global_charset[name] = charset; } function registerLanguage(name, lang) { global_lang[name] = lang; } // src/async.js function async_default(prototype) { register(prototype, "add"); register(prototype, "append"); register(prototype, "search"); register(prototype, "update"); register(prototype, "remove"); } function register(prototype, key) { prototype[key + "Async"] = function() { const self2 = this; const args = ( /*[].slice.call*/ arguments ); const arg = args[args.length - 1]; let callback; if (is_function(arg)) { callback = arg; delete args[args.length - 1]; } const promise = new Promise(function(resolve) { setTimeout(function() { self2.async = true; const res = self2[key].apply(self2, args); self2.async = false; resolve(res); }); }); if (callback) { promise.then(callback); return this; } else { return promise; } }; } // src/intersect.js function intersect(arrays, limit, offset, suggest) { const length = arrays.length; let result = []; let check; let check_suggest; let size = 0; if (suggest) { suggest = []; } for (let x = length - 1; x >= 0; x--) { const word_arr = arrays[x]; const word_arr_len = word_arr.length; const check_new = create_object(); let found = !check; for (let y = 0; y < word_arr_len; y++) { const arr = word_arr[y]; const arr_len = arr.length; if (arr_len) { for (let z = 0, check_idx, id; z < arr_len; z++) { id = arr[z]; if (check) { if (check[id]) { if (!x) { if (offset) { offset--; } else { result[size++] = id; if (size === limit) { return result; } } } if (x || suggest) { check_new[id] = 1; } found = true; } if (suggest) { check_idx = (check_suggest[id] || 0) + 1; check_suggest[id] = check_idx; if (check_idx < length) { const tmp = suggest[check_idx - 2] || (suggest[check_idx - 2] = []); tmp[tmp.length] = id; } } } else { check_new[id] = 1; } } } } if (suggest) { check || (check_suggest = check_new); } else if (!found) { return []; } check = check_new; } if (suggest) { for (let x = suggest.length - 1, arr, len; x >= 0; x--) { arr = suggest[x]; len = arr.length; for (let y = 0, id; y < len; y++) { id = arr[y]; if (!check[id]) { if (offset) { offset--; } else { result[size++] = id; if (size === limit) { return result; } } check[id] = 1; } } } } return result; } function intersect_union(mandatory, arrays) { const check = create_object(); const union = create_object(); const result = []; for (let x = 0; x < mandatory.length; x++) { check[mandatory[x]] = 1; } for (let x = 0, arr; x < arrays.length; x++) { arr = arrays[x]; for (let y = 0, id; y < arr.length; y++) { id = arr[y]; if (check[id]) { if (!union[id]) { union[id] = 1; result[result.length] = id; } } } } return result; } // src/cache.js function CacheClass(limit) { this.limit = limit !== true && limit; this.cache = create_object(); this.queue = []; } var cache_default = CacheClass; function searchCache(query, limit, options) { if (is_object(query)) { query = query["query"]; } let cache = this.cache.get(query); if (!cache) { cache = this.search(query, limit, options); this.cache.set(query, cache); } return cache; } CacheClass.prototype.set = function(key, value) { if (!this.cache[key]) { let length = this.queue.length; if (length === this.limit) { delete this.cache[this.queue[length - 1]]; } else { length++; } for (let x = length - 1; x > 0; x--) { this.queue[x] = this.queue[x - 1]; } this.queue[0] = key; } this.cache[key] = value; }; CacheClass.prototype.get = function(key) { const cache = this.cache[key]; if (this.limit && cache) { const pos = this.queue.indexOf(key); if (pos) { const tmp = this.queue[pos - 1]; this.queue[pos - 1] = this.queue[pos]; this.queue[pos] = tmp; } } return cache; }; CacheClass.prototype.del = function(id) { for (let i = 0, item, key; i < this.queue.length; i++) { key = this.queue[i]; item = this.cache[key]; if (item.includes(id)) { this.queue.splice(i--, 1); delete this.cache[key]; } } }; // src/preset.js var preset = { "memory": { charset: "latin:extra", //tokenize: "strict", resolution: 3, //threshold: 0, minlength: 4, fastupdate: false }, "performance": { //charset: "latin", //tokenize: "strict", resolution: 3, minlength: 3, //fastupdate: true, optimize: false, //fastupdate: true, context: { depth: 2, resolution: 1 //bidirectional: false } }, "match": { charset: "latin:extra", tokenize: "reverse" //resolution: 9, //threshold: 0 }, "score": { charset: "latin:advanced", //tokenize: "strict", resolution: 20, minlength: 3, context: { depth: 3, resolution: 9 //bidirectional: true } }, "default": { // charset: "latin:default", // tokenize: "strict", // resolution: 3, // threshold: 0, // depth: 3 } // "fast": { // //charset: "latin", // //tokenize: "strict", // threshold: 8, // resolution: 9, // depth: 1 // } }; function apply_preset(options) { if (is_string(options)) { if (DEBUG && !preset[options]) { console.warn("Preset not found: " + options); } options = preset[options]; } else { const preset2 = options["preset"]; if (preset2) { if (DEBUG && !preset2[preset2]) { console.warn("Preset not found: " + preset2); } options = Object.assign( {}, preset2[preset2], /** @type {Object} */ options ); } } return options; } // src/serialize.js function async(callback, self2, field, key, index_doc, index, data) { setTimeout(function() { const res = callback(field ? field + "." + key : key, JSON.stringify(data)); if (res && res["then"]) { res["then"](function() { self2.export(callback, self2, field, index_doc, index + 1); }); } else { self2.export(callback, self2, field, index_doc, index + 1); } }); } function exportIndex(callback, self2, field, index_doc, index) { let key, data; switch (index || (index = 0)) { case 0: key = "reg"; if (this.fastupdate) { data = create_object(); for (let key2 in this.register) { data[key2] = 1; } } else { data = this.register; } break; case 1: key = "cfg"; data = { "doc": 0, "opt": this.optimize ? 1 : 0 }; break; case 2: key = "map"; data = this.map; break; case 3: key = "ctx"; data = this.ctx; break; default: return; } async(callback, self2 || this, field, key, index_doc, index, data); return true; } function importIndex(key, data) { if (!data) { return; } if (is_string(data)) { data = JSON.parse(data); } switch (key) { case "cfg": this.optimize = !!data["opt"]; break; case "reg": this.fastupdate = false; this.register = data; break; case "map": this.map = data; break; case "ctx": this.ctx = data; break; } } function exportDocument(callback, self2, field, index_doc, index) { index || (index = 0); index_doc || (index_doc = 0); if (index_doc < this.field.length) { const field2 = this.field[index_doc]; const idx = this.index[field2]; self2 = this; setTimeout(function() { if (!idx.export(callback, self2, index ? field2 : "", index_doc, index++)) { index_doc++; index = 1; self2.export(callback, self2, field2, index_doc, index); } }); } else { let key, data; switch (index) { case 1: key = "tag"; data = this.tagindex; break; case 2: key = "store"; data = this.store; break; default: return; } async(callback, this, field, key, index_doc, index, data); } } function importDocument(key, data) { if (!data) { return; } if (is_string(data)) { data = JSON.parse(data); } switch (key) { case "tag": this.tagindex = data; break; case "reg": this.fastupdate = false; this.register = data; for (let i = 0, index; i < this.field.length; i++) { index = this.index[this.field[i]]; index.register = data; index.fastupdate = false; } break; case "store": this.store = data; break; default: key = key.split("."); const field = key[0]; key = key[1]; if (field && key) { this.index[field].import(key, data); } } } // src/worker/handler.js function handler_default(data) { data = data["data"]; const index = self["_index"]; const args = data["args"]; const task = data["task"]; switch (task) { case "init": const options = data["options"] || {}; const factory2 = data["factory"]; const encode2 = options["encode"]; options["cache"] = false; if (encode2 && encode2.indexOf("function") === 0) { options["encode"] = Function("return " + encode2)(); } if (factory2) { Function("return " + factory2)()(self); self["_index"] = new self["FlexSearch"]["Index"](options); delete self["FlexSearch"]; } else { self["_index"] = new src_default(options); } break; default: const id = data["id"]; const message = index[task].apply(index, args); postMessage(task === "search" ? { "id": id, "msg": message } : { "id": id }); } } // src/worker/index.js var pid = 0; function WorkerIndex(options) { if (!(this instanceof WorkerIndex)) { return new WorkerIndex(options); } let opt; if (options) { if (is_function(opt = options["encode"])) { options["encode"] = opt.toString(); } } else { options = {}; } let factory2 = (self || window)["_factory"]; if (factory2) { factory2 = factory2.toString(); } const is_node_js2 = typeof window === "undefined" && self["exports"]; const _self = this; this.worker = create(factory2, is_node_js2, options["worker"]); this.resolver = create_object(); if (!this.worker) { return; } if (is_node_js2) { this.worker["on"]("message", function(msg) { _self.resolver[msg["id"]](msg["msg"]); delete _self.resolver[msg["id"]]; }); } else { this.worker.onmessage = function(msg) { msg = msg["data"]; _self.resolver[msg["id"]](msg["msg"]); delete _self.resolver[msg["id"]]; }; } this.worker.postMessage({ "task": "init", "factory": factory2, "options": options }); } var worker_default = WorkerIndex; register2("add"); register2("append"); register2("search"); register2("update"); register2("remove"); function register2(key) { WorkerIndex.prototype[key] = WorkerIndex.prototype[key + "Async"] = function() { const self2 = this; const args = [].slice.call(arguments); const arg = args[args.length - 1]; let callback; if (is_function(arg)) { callback = arg; args.splice(args.length - 1, 1); } const promise = new Promise(function(resolve) { setTimeout(function() { self2.resolver[++pid] = resolve; self2.worker.postMessage({ "task": key, "id": pid, "args": args }); }); }); if (callback) { promise.then(callback); return this; } else { return promise; } }; } function create(factory, is_node_js, worker_path) { let worker; try { worker = is_node_js ? eval('new (require("worker_threads")["Worker"])("../dist/node/node.js")') : factory ? new Worker(URL.createObjectURL( new Blob([ "onmessage=" + handler_default.toString() ], { "type": "text/javascript" }) )) : new Worker(is_string(worker_path) ? worker_path : "worker/worker.js", { type: "module" }); } catch (e) { } return worker; } // src/document.js function Document(options) { if (!(this instanceof Document)) { return new Document(options); } const document = options["document"] || options["doc"] || options; let opt; this.tree = []; this.field = []; this.marker = []; this.register = create_object(); this.key = (opt = document["key"] || document["id"]) && parse_tree(opt, this.marker) || "id"; this.fastupdate = parse_option(options["fastupdate"], true); if (SUPPORT_STORE) { this.storetree = (opt = document["store"]) && opt !== true && []; this.store = opt && create_object(); } if (SUPPORT_TAGS) { this.tag = (opt = document["tag"]) && parse_tree(opt, this.marker); this.tagindex = opt && create_object(); } if (SUPPORT_CACHE) { this.cache = (opt = options["cache"]) && new cache_default(opt); options["cache"] = false; } if (SUPPORT_WORKER) { this.worker = options["worker"]; } if (SUPPORT_ASYNC) { this.async = false; } this.index = parse_descriptor.call(this, options, document); } var document_default = Document; function parse_descriptor(options, document) { const index = create_object(); let field = document["index"] || document["field"] || document; if (is_string(field)) { field = [field]; } for (let i = 0, key, opt; i < field.length; i++) { key = field[i]; if (!is_string(key)) { opt = key; key = key["field"]; } opt = is_object(opt) ? Object.assign({}, options, opt) : options; if (SUPPORT_WORKER && this.worker) { index[key] = new worker_default(opt); if (!index[key].worker) { this.worker = false; } } if (!this.worker) { index[key] = new src_default(opt, this.register); } this.tree[i] = parse_tree(key, this.marker); this.field[i] = key; } if (SUPPORT_STORE && this.storetree) { let store = document["store"]; if (is_string(store)) { store = [store]; } for (let i = 0; i < store.length; i++) { this.storetree[i] = parse_tree(store[i], this.marker); } } return index; } function parse_tree(key, marker) { const tree = key.split(":"); let count = 0; for (let i = 0; i < tree.length; i++) { key = tree[i]; if (key.indexOf("[]") >= 0) { key = key.substring(0, key.length - 2); if (key) { marker[count] = true; } } if (key) { tree[count++] = key; } } if (count < tree.length) { tree.length = count; } return count > 1 ? tree : tree[0]; } function parse_simple(obj, tree) { if (is_string(tree)) { obj = obj[tree]; } else { for (let i = 0; obj && i < tree.length; i++) { obj = obj[tree[i]]; } } return obj; } function store_value(obj, store, tree, pos, key) { obj = obj[key]; if (pos === tree.length - 1) { store[key] = obj; } else if (obj) { if (is_array(obj)) { store = store[key] = new Array(obj.length); for (let i = 0; i < obj.length; i++) { store_value(obj, store, tree, pos, i); } } else { store = store[key] || (store[key] = create_object()); key = tree[++pos]; store_value(obj, store, tree, pos, key); } } } function add_index(obj, tree, marker, pos, index, id, key, _append) { obj = obj[key]; if (obj) { if (pos === tree.length - 1) { if (is_array(obj)) { if (marker[pos]) { for (let i = 0; i < obj.length; i++) { index.add( id, obj[i], /* append: */ true, /* skip update: */ true ); } return; } obj = obj.join(" "); } index.add( id, obj, _append, /* skip_update: */ true ); } else { if (is_array(obj)) { for (let i = 0; i < obj.length; i++) { add_index(obj, tree, marker, pos, index, id, i, _append); } } else { key = tree[++pos]; add_index(obj, tree, marker, pos, index, id, key, _append); } } } } Document.prototype.add = function(id, content, _append) { if (is_object(id)) { content = id; id = parse_simple(content, this.key); } if (content && (id || id === 0)) { if (!_append && this.register[id]) { return this.update(id, content); } for (let i = 0, tree, field; i < this.field.length; i++) { field = this.field[i]; tree = this.tree[i]; if (is_string(tree)) { tree = [tree]; } add_index(content, tree, this.marker, 0, this.index[field], id, tree[0], _append); } if (SUPPORT_TAGS && this.tag) { let tag = parse_simple(content, this.tag); let dupes = create_object(); if (is_string(tag)) { tag = [tag]; } for (let i = 0, key, arr; i < tag.length; i++) { key = tag[i]; if (!dupes[key]) { dupes[key] = 1; arr = this.tagindex[key] || (this.tagindex[key] = []); if (!_append || !arr.includes(id)) { arr[arr.length] = id; if (this.fastupdate) { const tmp = this.register[id] || (this.register[id] = []); tmp[tmp.length] = arr; } } } } } if (SUPPORT_STORE && this.store && (!_append || !this.store[id])) { let store; if (this.storetree) { store = create_object(); for (let i = 0, tree; i < this.storetree.length; i++) { tree = this.storetree[i]; if (is_string(tree)) { store[tree] = content[tree]; } else { store_value(content, store, tree, 0, tree[0]); } } } this.store[id] = store || content; } } return this; }; Document.prototype.append = function(id, content) { return this.add(id, content, true); }; Document.prototype.update = function(id, content) { return this.remove(id).add(id, content); }; Document.prototype.remove = function(id) { if (is_object(id)) { id = parse_simple(id, this.key); } if (this.register[id]) { for (let i = 0; i < this.field.length; i++) { this.index[this.field[i]].remove(id, !this.worker); if (this.fastupdate) { break; } } if (SUPPORT_TAGS && this.tag) { if (!this.fastupdate) { for (let key in this.tagindex) { const tag = this.tagindex[key]; const pos = tag.indexOf(id); if (pos !== -1) { if (tag.length > 1) { tag.splice(pos, 1); } else { delete this.tagindex[key]; } } } } } if (SUPPORT_STORE && this.store) { delete this.store[id]; } delete this.register[id]; } return this; }; Document.prototype.search = function(query, limit, options, _resolve) { if (!options) { if (!limit && is_object(query)) { options = /** @type {Object} */ query; query = ""; } else if (is_object(limit)) { options = /** @type {Object} */ limit; limit = 0; } } let result = [], result_field = []; let pluck, enrich; let field, tag, bool, offset, count = 0; if (options) { if (is_array(options)) { field = options; options = null; } else { query = options["query"] || query; pluck = options["pluck"]; field = pluck || options["index"] || options["field"]; tag = SUPPORT_TAGS && options["tag"]; enrich = SUPPORT_STORE && this.store && options["enrich"]; bool = options["bool"] === "and"; limit = options["limit"] || limit || 100; offset = options["offset"] || 0; if (tag) { if (is_string(tag)) { tag = [tag]; } if (!query) { for (let i = 0, res; i < tag.length; i++) { res = get_tag.call(this, tag[i], limit, offset, enrich); if (res) { result[result.length] = res; count++; } } return count ? result : []; } } if (is_string(field)) { field = [field]; } } } field || (field = this.field); bool = bool && (field.length > 1 || tag && tag.length > 1); const promises = !_resolve && (this.worker || this.async) && []; for (let i = 0, res, key, len; i < field.length; i++) { let field_options; key = field[i]; if (!is_string(key)) { field_options = key; key = field_options["field"]; query = field_options["query"] || query; limit = field_options["limit"] || limit; } if (promises) { promises[i] = this.index[key].searchAsync(query, limit, field_options || options); continue; } else if (_resolve) { res = _resolve[i]; } else { res = this.index[key].search(query, limit, field_options || options); } len = res && res.length; if (tag && len) { const arr = []; let count2 = 0; if (bool) { arr[0] = [res]; } for (let y = 0, key2, res2; y < tag.length; y++) { key2 = tag[y]; res2 = this.tagindex[key2]; len = res2 && res2.length; if (len) { count2++; arr[arr.length] = bool ? [res2] : res2; } } if (count2) { if (bool) { res = intersect(arr, limit || 100, offset || 0); } else { res = intersect_union(res, arr); } len = res.length; } } if (len) { result_field[count] = key; result[count++] = res; } else if (bool) { return []; } } if (promises) { const self2 = this; return new Promise(function(resolve) { Promise.all(promises).then(function(result2) { resolve(self2.search(query, limit, options, result2)); }); }); } if (!count) { return []; } if (pluck && (!enrich || !this.store)) { return result[0]; } for (let i = 0, res; i < result_field.length; i++) { res = result[i]; if (res.length) { if (enrich) { res = apply_enrich.call(this, res); } } if (pluck) { return res; } result[i] = { "field": result_field[i], "result": res }; } return result; }; function get_tag(key, limit, offset, enrich) { let res = this.tagindex[key]; let len = res && res.length - offset; if (len && len > 0) { if (len > limit || offset) { res = res.slice(offset, offset + limit); } if (enrich) { res = apply_enrich.call(this, res); } return { "tag": key, "result": res }; } } function apply_enrich(res) { const arr = new Array(res.length); for (let x = 0, id; x < res.length; x++) { id = res[x]; arr[x] = { "id": id, "doc": this.store[id] }; } return arr; } Document.prototype.contain = function(id) { return !!this.register[id]; }; if (SUPPORT_STORE) { Document.prototype.get = function(id) { return this.store[id]; }; Document.prototype.set = function(id, data) { this.store[id] = data; return this; }; } if (SUPPORT_CACHE) { Document.prototype.searchCache = searchCache; } if (SUPPORT_SERIALIZE) { Document.prototype.export = exportDocument; Document.prototype.import = importDocument; } if (SUPPORT_ASYNC) { async_default(Document.prototype); } // src/index.js function create2(options) { return new Index(options); } function Index(options, _register) { if (!(this instanceof Index)) { return new Index(options); } let charset, lang, tmp; if (options) { if (SUPPORT_ENCODER) { options = apply_preset(options); } charset = options["charset"]; lang = options["lang"]; if (is_string(charset)) { if (charset.indexOf(":") === -1) { charset += ":default"; } charset = global_charset[charset]; } if (is_string(lang)) { lang = global_lang[lang]; } } else { options = {}; } let resolution, optimize, context = options["context"] || {}; this.encode = options["encode"] || charset && charset.encode || encode; this.register = _register || create_object(); this.resolution = resolution = options["resolution"] || 9; this.tokenize = tmp = charset && charset.tokenize || options["tokenize"] || "strict"; this.depth = tmp === "strict" && context["depth"]; this.bidirectional = parse_option(context["bidirectional"], true); this.optimize = optimize = parse_option(options["optimize"], true); this.fastupdate = parse_option(options["fastupdate"], true); this.minlength = options["minlength"] || 1; this.boost = options["boost"]; this.map = optimize ? create_object_array(resolution) : create_object(); this.resolution_ctx = resolution = context["resolution"] || 1; this.ctx = optimize ? create_object_array(resolution) : create_object(); this.rtl = charset && charset.rtl || options["rtl"]; this.matcher = (tmp = options["matcher"] || lang && lang.matcher) && init_stemmer_or_matcher(tmp, false); this.stemmer = (tmp = options["stemmer"] || lang && lang.stemmer) && init_stemmer_or_matcher(tmp, true); this.filter = (tmp = options["filter"] || lang && lang.filter) && init_filter(tmp); if (SUPPORT_CACHE) { this.cache = (tmp = options["cache"]) && new cache_default(tmp); } } var src_default = Index; Index.prototype.append = function(id, content) { return this.add(id, content, true); }; Index.prototype.add = function(id, content, _append, _skip_update) { if (content && (id || id === 0)) { if (!_skip_update && !_append && this.register[id]) { return this.update(id, content); } content = this.encode("" + content); const length = content.length; if (length) { const dupes_ctx = create_object(); const dupes = create_object(); const depth = this.depth; const resolution = this.resolution; for (let i = 0; i < length; i++) { let term = content[this.rtl ? length - 1 - i : i]; let term_length = term.length; if (term && term_length >= this.minlength && (depth || !dupes[term])) { let score = get_score(resolution, length, i); let token = ""; switch (this.tokenize) { case "full": if (term_length > 2) { for (let x = 0; x < term_length; x++) { for (let y = term_length; y > x; y--) { if (y - x >= this.minlength) { const partial_score = get_score(resolution, length, i, term_length, x); token = term.substring(x, y); this.push_index(dupes, token, partial_score, id, _append); } } } break; } case "reverse": if (term_length > 1) { for (let x = term_length - 1; x > 0; x--) { token = term[x] + token; if (token.length >= this.minlength) { const partial_score = get_score(resolution, length, i, term_length, x); this.push_index(dupes, token, partial_score, id, _append); } } token = ""; } case "forward": if (term_length > 1) { for (let x = 0; x < term_length; x++) { token += term[x]; if (token.length >= this.minlength) { this.push_index(dupes, token, score, id, _append); } } break; } default: if (this.boost) { score = Math.min(score / this.boost(content, term, i) | 0, resolution - 1); } this.push_index(dupes, term, score, id, _append); if (depth) { if (length > 1 && i < length - 1) { const dupes_inner = create_object(); const resolution2 = this.resolution_ctx; const keyword = term; const size = Math.min(depth + 1, length - i); dupes_inner[keyword] = 1; for (let x = 1; x < size; x++) { term = content[this.rtl ? length - 1 - i - x : i + x]; if (term && term.length >= this.minlength && !dupes_inner[term]) { dupes_inner[term] = 1; const context_score = get_score(resolution2 + (length / 2 > resolution2 ? 0 : 1), length, i, size - 1, x - 1); const swap = this.bidirectional && term > keyword; this.push_index(dupes_ctx, swap ? keyword : term, context_score, id, _append, swap ? term : keyword); } } } } } } } this.fastupdate || (this.register[id] = 1); } } return this; }; function get_score(resolution, length, i, term_length, x) { return i && resolution > 1 ? length + (term_length || 0) <= resolution ? i + (x || 0) : (resolution - 1) / (length + (term_length || 0)) * (i + (x || 0)) + 1 | 0 : 0; } Index.prototype.push_index = function(dupes, value, score, id, append, keyword) { let arr = keyword ? this.ctx : this.map; if (!dupes[value] || keyword && !dupes[value][keyword]) { if (this.optimize) { arr = arr[score]; } if (keyword) { dupes = dupes[value] || (dupes[value] = create_object()); dupes[keyword] = 1; arr = arr[keyword] || (arr[keyword] = create_object()); } else { dupes[value] = 1; } arr = arr[value] || (arr[value] = []); if (!this.optimize) { arr = arr[score] || (arr[score] = []); } if (!append || !arr.includes(id)) { arr[arr.length] = id; if (this.fastupdate) { const tmp = this.register[id] || (this.register[id] = []); tmp[tmp.length] = arr; } } } }; Index.prototype.search = function(query, limit, options) { if (!options) { if (!limit && is_object(query)) { options = /** @type {Object} */ query; query = options["query"]; } else if (is_object(limit)) { options = /** @type {Object} */ limit; } } let result = []; let length; let context, suggest, offset = 0; if (options) { query = options["query"] || query; limit = options["limit"]; offset = options["offset"] || 0; context = options["context"]; suggest = SUPPORT_SUGGESTION && options["suggest"]; } if (query) { query = /** @type {Array} */ this.encode("" + query); length = query.length; if (length > 1) { const dupes = create_object(); const query_new = []; for (let i = 0, count = 0, term; i < length; i++) { term = query[i]; if (term && term.length >= this.minlength && !dupes[term]) { if (!this.optimize && !suggest && !this.map[term]) { return result; } else { query_new[count++] = term; dupes[term] = 1; } } } query = query_new; length = query.length; } } if (!length) { return result; } limit || (limit = 100); let depth = this.depth && length > 1 && context !== false; let index = 0, keyword; if (depth) { keyword = query[0]; index = 1; } else { if (length > 1) { query.sort(sort_by_length_down); } } for (let arr, term; index < length; index++) { term = query[index]; if (depth) { arr = this.add_result(result, suggest, limit, offset, length === 2, term, keyword); if (!suggest || arr !== false || !result.length) { keyword = term; } } else { arr = this.add_result(result, suggest, limit, offset, length === 1, term); } if (arr) { return ( /** @type {Array<number|string>} */ arr ); } if (suggest && index === length - 1) { let length2 = result.length; if (!length2) { if (depth) { depth = 0; index = -1; continue; } return result; } else if (length2 === 1) { return single_result(result[0], limit, offset); } } } return intersect(result, limit, offset, suggest); }; Index.prototype.add_result = function(result, suggest, limit, offset, single_term, term, keyword) { let word_arr = []; let arr = keyword ? this.ctx : this.map; if (!this.optimize) { arr = get_array(arr, term, keyword, this.bidirectional); } if (arr) { let count = 0; const arr_len = Math.min(arr.length, keyword ? this.resolution_ctx : this.resolution); for (let x = 0, size = 0, tmp, len; x < arr_len; x++) { tmp = arr[x]; if (tmp) { if (this.optimize) { tmp = get_array(tmp, term, keyword, this.bidirectional); } if (offset) { if (tmp && single_term) { len = tmp.length; if (len <= offset) { offset -= len; tmp = null; } else { tmp = tmp.slice(offset); offset = 0; } } } if (tmp) { word_arr[count++] = tmp; if (single_term) { size += tmp.length; if (size >= limit) { break; } } } } } if (count) { if (single_term) { return single_result(word_arr, limit, 0); } result[result.length] = word_arr; return; } } return !suggest && word_arr; }; function single_result(result, limit, offset) { if (result.length === 1) { result = result[0]; } else { result = concat(result); } return offset || result.length > limit ? result.slice(offset, offset + limit) : result; } function get_array(arr, term, keyword, bidirectional) { if (keyword) { const swap = bidirectional && term > keyword; arr = arr[swap ? term : keyword]; arr = arr && arr[swap ? keyword : term]; } else { arr = arr[term]; } return arr; } Index.prototype.contain = function(id) { return !!this.register[id]; }; Index.prototype.update = function(id, content) { return this.remove(id).add(id, content); }; Index.prototype.remove = function(id, _skip_deletion) { const refs = this.register[id]; if (refs) { if (this.fastupdate) { for (let i = 0, tmp; i < refs.length; i++) { tmp = refs[i]; tmp.splice(tmp.indexOf(id), 1); } } else { remove_index(this.map, id, this.resolution, this.optimize); if (this.depth) { remove_index(this.ctx, id, this.resolution_ctx, this.optimize); } } _skip_deletion || delete this.register[id]; if (SUPPORT_CACHE && this.cache) { this.cache.del(id); } } return this; }; function remove_index(map, id, res, optimize, resolution) { let count = 0; if (is_array(map)) { if (!resolution) { resolution = Math.min(map.length, res); for (let x = 0, arr; x < resolution; x++) { arr = map[x]; if (arr) { count = remove_index(arr, id, res, optimize, resolution); if (!optimize && !count) { delete map[x]; } } } } else { const pos = map.indexOf(id); if (pos !== -1) { if (map.length > 1) { map.splice(pos, 1); count++; } } else { count++; } } } else { for (let key in map) { count = remove_index(map[key], id, res, optimize, resolution); if (!count) { delete map[key]; } } } return count; } if (SUPPORT_CACHE) { Index.prototype.searchCache = searchCache; } if (SUPPORT_SERIALIZE) { Index.prototype.export = exportIndex; Index.prototype.import = importIndex; } if (SUPPORT_ASYNC) { async_default(Index.prototype); }