diff --git a/src/db/indexeddb/index.js b/src/db/indexeddb/index.js index c1e3f837..95917d51 100644 --- a/src/db/indexeddb/index.js +++ b/src/db/indexeddb/index.js @@ -12,21 +12,23 @@ import { } from "../../type.js"; const VERSION = 1; -const IndexedDB = typeof window !== "undefined" && ( - window.indexedDB || - window.mozIndexedDB || - window.webkitIndexedDB || - window.msIndexedDB +// use globalThis instead of window so this also resolves inside Web Workers, +// where IndexedDB is available but there is no window object +const IndexedDB = typeof globalThis !== "undefined" && ( + globalThis.indexedDB || + globalThis.mozIndexedDB || + globalThis.webkitIndexedDB || + globalThis.msIndexedDB ); -const IDBTransaction = typeof window !== "undefined" && ( - window.IDBTransaction || - window.webkitIDBTransaction || - window.msIDBTransaction +const IDBTransaction = typeof globalThis !== "undefined" && ( + globalThis.IDBTransaction || + globalThis.webkitIDBTransaction || + globalThis.msIDBTransaction ); -const IDBKeyRange = typeof window !== "undefined" && ( - window.IDBKeyRange || - window.webkitIDBKeyRange || - window.msIDBKeyRange +const IDBKeyRange = typeof globalThis !== "undefined" && ( + globalThis.IDBKeyRange || + globalThis.webkitIDBKeyRange || + globalThis.msIDBKeyRange ); const fields = ["map", "ctx", "tag", "reg", "cfg"]; import StorageInterface from "../interface.js"; diff --git a/src/document/search.js b/src/document/search.js index d1db2ec1..86a48077 100644 --- a/src/document/search.js +++ b/src/document/search.js @@ -89,7 +89,7 @@ Document.prototype.search = function(query, limit, options, _promises){ let result = []; let result_field = []; let pluck, enrich, merge, suggest, boost, cache; - let field, tag, offset, count = 0, resolve = true, highlight; + let field, tag, offset, count = 0, resolve = true, highlight, merge_limit = 0; if(options){ @@ -123,6 +123,10 @@ Document.prototype.search = function(query, limit, options, _promises){ limit = options.limit || limit; offset = options.offset || 0; limit || (limit = (resolve ? 100 : 0)); + // per-field options below may override "limit" for individual fields, + // so the overall limit requested by the caller has to be captured here + // to cap the merged result correctly. + merge_limit = limit; if(tag && (!SUPPORT_PERSISTENT || !this.db || !_promises)){ @@ -550,7 +554,7 @@ Document.prototype.search = function(query, limit, options, _promises){ result = highlight_fields(/** @type {string} */ (query), result, self.index, pluck, highlight); } return merge - ? merge_fields(result) + ? merge_fields(result, merge_limit) : /** @type {DocumentSearchResults} */ (result); }); } @@ -559,7 +563,7 @@ Document.prototype.search = function(query, limit, options, _promises){ result = highlight_fields(/** @type {string} */ (query), result, this.index, pluck, highlight); } return merge - ? merge_fields(result) + ? merge_fields(result, merge_limit) : /** @type {DocumentSearchResults} */ (result); } @@ -570,14 +574,15 @@ Document.prototype.search = function(query, limit, options, _promises){ /** * @param {DocumentSearchResults} fields + * @param {number=} limit * @return {MergedDocumentSearchResults} */ -function merge_fields(fields){ +function merge_fields(fields, limit){ /** @type {MergedDocumentSearchResults} */ const final = []; const group_field = create_object(); const group_highlight = create_object(); - for(let i = 0, field, key, res, id, entry, tmp, highlight; i < fields.length; i++){ + outer: for(let i = 0, field, key, res, id, entry, tmp, highlight; i < fields.length; i++){ field = fields[i]; key = field.field; res = field.result; @@ -589,6 +594,12 @@ function merge_fields(fields){ : id = entry["id"]; tmp = group_field[id]; if(!tmp){ + // results are merged across fields, so the per-field limit + // applied earlier does not cap the merged total; stop once + // the overall requested limit has been reached + if(limit && final.length === limit){ + break outer; + } entry["field"] = group_field[id] = [key]; final.push(/** @type {!MergedDocumentSearchEntry} */ (entry)); } diff --git a/test/db.indexeddb.js b/test/db.indexeddb.js new file mode 100644 index 00000000..abdf7da5 --- /dev/null +++ b/test/db.indexeddb.js @@ -0,0 +1,37 @@ +import { expect } from "chai"; + +describe("Persistent: IndexedDB", function(){ + + it("Should resolve IndexedDB from globalThis (e.g. inside a Web Worker, where there is no window)", async function(){ + + // Node has no "window" global, which is exactly the condition that + // broke this adapter inside Web Workers (see #546): "window" is not + // defined there either, even though "indexedDB" is available via + // "globalThis"/"self". + expect(typeof window).to.equal("undefined"); + + let openCalledWith = null; + + globalThis.indexedDB = { + open(name, version){ + openCalledWith = [name, version]; + // return a minimal request-like object; the adapter only + // assigns handlers to it, it doesn't need to ever resolve + // for this test + return {}; + } + }; + + try{ + const { default: IdxDB } = await import("../src/db/indexeddb/index.js?t=" + Date.now()); + const db = new IdxDB("worker-test"); + + db.open(); + + expect(openCalledWith).to.eql(["flexsearch:worker-test", 1]); + } + finally{ + delete globalThis.indexedDB; + } + }); +}); diff --git a/test/document.js b/test/document.js index 0bbc696d..83723066 100644 --- a/test/document.js +++ b/test/document.js @@ -458,6 +458,42 @@ if(!build_light) describe("Document (Multi-Field Search)", function(){ ]); }); + it("Should cap merged results at the requested limit", function(){ + + const document = new Document({ + document: { + id: "id", + field: [ + "name_1", + "name_2", + "name_3" + ] + } + }); + + const names = ["foo", "bar"]; + + // each field matches a different, non-overlapping half of the ids, + // so merging the per-field results (each already capped at "limit") + // produces more unique ids than "limit" unless the merge step + // re-applies the cap itself + for(let i = 0; i < 30; i++){ + document.add({ + id: i, + name_1: names[i % 2], + name_2: names[(i + 1) % 2], + name_3: names[i % 2] + }); + } + + const result = document.search("foo", { + merge: true, + limit: 10 + }); + + expect(result.length).to.equal(10); + }); + it("Using BigInt", function(){ const document = new Document({