cozy-dataproxy-lib
Version:
Library meant to be by Cozy Cloud's DataProxy apps for data manipulation
966 lines (782 loc) • 34.6 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SearchEngine = void 0;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _cozyClient = require("cozy-client");
var _cozyMinilog = _interopRequireDefault(require("cozy-minilog"));
var _cozyRealtime = require("cozy-realtime");
var _consts = require("./consts");
var _client = require("./helpers/client");
var _normalizeSearchResult = require("./helpers/normalizeSearchResult");
var _utils = require("./helpers/utils");
var _indexDocs = require("./indexDocs");
var _queries = require("./queries");
var _storage = require("./storage");
var _types = require("./types");
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
var log = (0, _cozyMinilog.default)('🗂️ [Indexing]');
var SearchEngine = /*#__PURE__*/function () {
function SearchEngine(client, storage, performanceApi) {
var _this = this;
var engineOptions = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
(0, _classCallCheck2.default)(this, SearchEngine);
(0, _defineProperty2.default)(this, "client", void 0);
(0, _defineProperty2.default)(this, "searchIndexes", void 0);
(0, _defineProperty2.default)(this, "debouncedReplication", void 0);
(0, _defineProperty2.default)(this, "isLocalSearch", void 0);
(0, _defineProperty2.default)(this, "storage", void 0);
(0, _defineProperty2.default)(this, "performanceApi", void 0);
(0, _defineProperty2.default)(this, "engineOptions", void 0);
this.client = client;
this.searchIndexes = {};
this.storage = storage;
this.performanceApi = performanceApi !== null && performanceApi !== void 0 ? performanceApi : _cozyClient.defaultPerformanceApi;
this.engineOptions = _objectSpread({
shouldInit: true
}, engineOptions);
this.isLocalSearch = !!(0, _client.getPouchLink)(this.client);
log.info('Use local data on trusted device: ', this.isLocalSearch);
this.debouncedReplication = function () {
var pouchLink = (0, _client.getPouchLink)(client);
if (pouchLink) {
pouchLink.startReplicationWithDebounce();
}
};
if (this.client.isLogged) {
this.afterLogin();
} else {
this.client.on('login', function () {
_this.afterLogin();
});
}
}
(0, _createClass2.default)(SearchEngine, [{
key: "afterLogin",
value: function afterLogin() {
if (this.engineOptions.shouldInit) {
void this.init();
} // Use replication events to have up-to-date search indexes, based on local data
this.handleReplicationEvents();
}
}, {
key: "indexDocumentsAtInit",
value: function () {
var _indexDocumentsAtInit = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
var markName, lastExportDate, _iterator, _step, doctype, searchIndex;
return _regenerator.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
if (this.client) {
_context.next = 2;
break;
}
return _context.abrupt("return");
case 2:
log.info('Initialize indexes...');
markName = this.performanceApi.mark('indexDocuments');
_context.next = 6;
return (0, _storage.getExportDate)(this.storage);
case 6:
lastExportDate = _context.sent;
if (!(!lastExportDate || !this.isLocalSearch)) {
_context.next = 29;
break;
}
// No persisted index: let's create them
_iterator = _createForOfIteratorHelper(_consts.SEARCHABLE_DOCTYPES);
_context.prev = 9;
_iterator.s();
case 11:
if ((_step = _iterator.n()).done) {
_context.next = 19;
break;
}
doctype = _step.value;
_context.next = 15;
return this.indexDocsForSearch(doctype);
case 15:
searchIndex = _context.sent;
if (searchIndex) {
this.searchIndexes[doctype] = searchIndex;
}
case 17:
_context.next = 11;
break;
case 19:
_context.next = 24;
break;
case 21:
_context.prev = 21;
_context.t0 = _context["catch"](9);
_iterator.e(_context.t0);
case 24:
_context.prev = 24;
_iterator.f();
return _context.finish(24);
case 27:
_context.next = 31;
break;
case 29:
_context.next = 31;
return this.initSearchWithIndexImport();
case 31:
this.performanceApi.measure({
markName: markName,
category: 'Search'
});
case 32:
case "end":
return _context.stop();
}
}
}, _callee, this, [[9, 21, 24, 27]]);
}));
function indexDocumentsAtInit() {
return _indexDocumentsAtInit.apply(this, arguments);
}
return indexDocumentsAtInit;
}()
}, {
key: "handleReplicationEvents",
value: function handleReplicationEvents() {
var _this2 = this;
if (!this.isLocalSearch) {
// Nothing to do here, we do not want to replicate for non-local search
return;
}
var startReplicationTime = 0,
endReplicationTime = 0;
this.client.on('pouchlink:doctypesync:end', /*#__PURE__*/function () {
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(doctype) {
var searchIndex;
return _regenerator.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
if (!((0, _types.isSearchedDoctype)(doctype) && _this2.searchIndexes[doctype])) {
_context2.next = 5;
break;
}
_context2.next = 3;
return _this2.indexDocsForSearch(doctype);
case 3:
searchIndex = _context2.sent;
if (searchIndex) {
_this2.searchIndexes[doctype] = searchIndex;
}
case 5:
case "end":
return _context2.stop();
}
}
}, _callee2);
}));
return function (_x) {
return _ref.apply(this, arguments);
};
}());
this.client.on('pouchlink:sync:start', function () {
log.debug('Started pouch replication');
startReplicationTime = performance.now();
});
this.client.on('pouchlink:sync:end', /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3() {
return _regenerator.default.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
log.debug('Ended pouch replication');
endReplicationTime = performance.now();
if (startReplicationTime > 0) {
// Log only if the start replication event was correctly get
log.debug("Replication took ".concat((endReplicationTime - startReplicationTime).toFixed(2), " ms"));
}
if (!(Object.keys(_this2.searchIndexes).length < 1)) {
_context3.next = 7;
break;
}
log.info('No search index found: start indexing'); // This happens at first replication, so init the indexes
_context3.next = 7;
return _this2.indexDocumentsAtInit();
case 7:
// Save up-to-date index on storage to be later imported
void (0, _storage.exportSearchIndexes)(_this2.storage, _this2.searchIndexes);
case 8:
case "end":
return _context3.stop();
}
}
}, _callee3);
})));
}
}, {
key: "init",
value: function () {
var _init = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4() {
return _regenerator.default.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
// Ensure login is done before plugin register
if (!this.client.plugins[_cozyRealtime.RealtimePlugin.pluginName]) {
this.client.registerPlugin(_cozyRealtime.RealtimePlugin, {});
} // Realtime subscription
this.handleUpdatedOrCreatedDoc = this.handleUpdatedOrCreatedDoc.bind(this);
this.handleDeletedDoc = this.handleDeletedDoc.bind(this);
this.subscribeDoctype(this.client, _consts.FILES_DOCTYPE);
this.subscribeDoctype(this.client, _consts.CONTACTS_DOCTYPE);
this.subscribeDoctype(this.client, _consts.APPS_DOCTYPE); // The document indexing should be performed once everything is setup
_context4.next = 8;
return this.indexDocumentsAtInit();
case 8:
case "end":
return _context4.stop();
}
}
}, _callee4, this);
}));
function init() {
return _init.apply(this, arguments);
}
return init;
}()
}, {
key: "subscribeDoctype",
value: function subscribeDoctype(client, doctype) {
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/unbound-method */
var realtime = client.plugins.realtime;
realtime.subscribe('created', doctype, this.handleUpdatedOrCreatedDoc);
realtime.subscribe('updated', doctype, this.handleUpdatedOrCreatedDoc);
realtime.subscribe('deleted', doctype, this.handleDeletedDoc);
/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/unbound-method */
}
}, {
key: "handleUpdatedOrCreatedDoc",
value: function handleUpdatedOrCreatedDoc(doc) {
var _this$searchIndexes;
var doctype = doc._type;
if (!(0, _types.isSearchedDoctype)(doctype)) {
return;
}
var searchIndex = (_this$searchIndexes = this.searchIndexes) === null || _this$searchIndexes === void 0 ? void 0 : _this$searchIndexes[doctype];
if (!searchIndex) {
// No index yet: it will be done by querying the local db after first replication
return;
}
log.debug('[REALTIME] Update doc from index after update : ', doc._id);
void (0, _indexDocs.indexSingleDoc)(this.client, searchIndex.index, doc);
if (this.isLocalSearch) {
this.debouncedReplication();
}
}
}, {
key: "handleDeletedDoc",
value: function handleDeletedDoc(doc) {
var _this$searchIndexes2;
var doctype = doc._type;
if (!(0, _types.isSearchedDoctype)(doctype)) {
return;
}
var searchIndex = (_this$searchIndexes2 = this.searchIndexes) === null || _this$searchIndexes2 === void 0 ? void 0 : _this$searchIndexes2[doctype];
if (!searchIndex) {
// No index yet: it will be done by querying the local db after first replication
return;
}
log.debug('[REALTIME] Remove doc from index after update : ', doc._id);
this.searchIndexes[doctype].index.remove(doc._id);
if (this.isLocalSearch) {
this.debouncedReplication();
}
}
/**
* Initialize indexes by:
* - importing persisted indexes
* - Requesting remote changes if the indexes are stale
* - Query the local database to load the redux store
*/
}, {
key: "initSearchWithIndexImport",
value: function () {
var _initSearchWithIndexImport = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5() {
var startImport, endImport, _iterator2, _step2, doctype, searchIndex, newSearchIndex;
return _regenerator.default.wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
startImport = performance.now();
_context5.next = 3;
return (0, _storage.importSearchIndexes)(this.storage);
case 3:
this.searchIndexes = _context5.sent;
endImport = performance.now();
if ((0, _utils.isDebug)()) {
log.debug("Index import took ".concat((endImport - startImport).toFixed(2), " ms"));
}
_iterator2 = _createForOfIteratorHelper(_consts.SEARCHABLE_DOCTYPES);
_context5.prev = 7;
_iterator2.s();
case 9:
if ((_step2 = _iterator2.n()).done) {
_context5.next = 22;
break;
}
doctype = _step2.value;
searchIndex = this.searchIndexes[doctype];
if (searchIndex) {
_context5.next = 17;
break;
}
_context5.next = 15;
return this.indexDocsForSearch(doctype);
case 15:
newSearchIndex = _context5.sent;
if (newSearchIndex) {
this.searchIndexes[doctype] = newSearchIndex;
}
case 17:
_context5.next = 19;
return (0, _indexDocs.indexOnChanges)(this, this.searchIndexes[doctype], doctype);
case 19:
// The doctype needs some extra treatment to initialize everything correctly
void (0, _indexDocs.initDoctypeAfterIndexImport)(this.client, doctype);
case 20:
_context5.next = 9;
break;
case 22:
_context5.next = 27;
break;
case 24:
_context5.prev = 24;
_context5.t0 = _context5["catch"](7);
_iterator2.e(_context5.t0);
case 27:
_context5.prev = 27;
_iterator2.f();
return _context5.finish(27);
case 30:
case "end":
return _context5.stop();
}
}
}, _callee5, this, [[7, 24, 27, 30]]);
}));
function initSearchWithIndexImport() {
return _initSearchWithIndexImport.apply(this, arguments);
}
return initSearchWithIndexImport;
}()
}, {
key: "buildSearchIndex",
value: function buildSearchIndex(doctype, docs) {
var startTimeIndex = performance.now();
var flexsearchIndex = (0, _indexDocs.initSearchIndex)(doctype);
(0, _indexDocs.indexAllDocs)(flexsearchIndex, docs, this.isLocalSearch);
var endTimeIndex = performance.now();
log.debug("Create ".concat(doctype, " index took ").concat((endTimeIndex - startTimeIndex).toFixed(2), " ms"));
return flexsearchIndex;
}
}, {
key: "getLocalLastSeq",
value: function () {
var _getLocalLastSeq = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee6(doctype) {
var pouchLink, info;
return _regenerator.default.wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
if (!this.isLocalSearch) {
_context6.next = 11;
break;
}
pouchLink = (0, _client.getPouchLink)(this.client);
if (!pouchLink) {
_context6.next = 8;
break;
}
_context6.next = 5;
return pouchLink.getDbInfo(doctype);
case 5:
_context6.t0 = _context6.sent;
_context6.next = 9;
break;
case 8:
_context6.t0 = null;
case 9:
info = _context6.t0;
return _context6.abrupt("return", (info === null || info === void 0 ? void 0 : info.update_seq) || 0);
case 11:
return _context6.abrupt("return", -1);
case 12:
case "end":
return _context6.stop();
}
}
}, _callee6, this);
}));
function getLocalLastSeq(_x2) {
return _getLocalLastSeq.apply(this, arguments);
}
return getLocalLastSeq;
}()
}, {
key: "initialIndexation",
value: function () {
var _initialIndexation = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee7(doctype) {
var docs, index, lastSeq;
return _regenerator.default.wrap(function _callee7$(_context7) {
while (1) {
switch (_context7.prev = _context7.next) {
case 0:
_context7.next = 2;
return (0, _queries.queryLocalOrRemoteDocs)(this.client, doctype, {
isLocalSearch: this.isLocalSearch
});
case 2:
docs = _context7.sent;
if (!(docs.length < 1)) {
_context7.next = 5;
break;
}
return _context7.abrupt("return", null);
case 5:
index = this.buildSearchIndex(doctype, docs);
_context7.next = 8;
return this.getLocalLastSeq(doctype);
case 8:
lastSeq = _context7.sent;
this.searchIndexes[doctype] = {
index: index,
lastSeq: lastSeq,
lastUpdated: new Date().toISOString()
};
return _context7.abrupt("return", this.searchIndexes[doctype]);
case 11:
case "end":
return _context7.stop();
}
}
}, _callee7, this);
}));
function initialIndexation(_x3) {
return _initialIndexation.apply(this, arguments);
}
return initialIndexation;
}()
}, {
key: "incrementalIndexation",
value: function () {
var _incrementalIndexation = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee8(doctype, searchIndex) {
return _regenerator.default.wrap(function _callee8$(_context8) {
while (1) {
switch (_context8.prev = _context8.next) {
case 0:
return _context8.abrupt("return", (0, _indexDocs.indexOnChanges)(this, searchIndex, doctype));
case 1:
case "end":
return _context8.stop();
}
}
}, _callee8, this);
}));
function incrementalIndexation(_x4, _x5) {
return _incrementalIndexation.apply(this, arguments);
}
return incrementalIndexation;
}()
}, {
key: "indexDocsForSearch",
value: function () {
var _indexDocsForSearch = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee9(doctype) {
var markeNameIndex, searchIndex, startIndexing, index, endIndexing;
return _regenerator.default.wrap(function _callee9$(_context9) {
while (1) {
switch (_context9.prev = _context9.next) {
case 0:
markeNameIndex = this.performanceApi.mark("indexDocsForSearch ".concat(doctype));
searchIndex = this.searchIndexes[doctype];
startIndexing = performance.now();
if (searchIndex) {
_context9.next = 12;
break;
}
_context9.next = 6;
return this.initialIndexation(doctype);
case 6:
index = _context9.sent;
if (index) {
_context9.next = 10;
break;
}
this.performanceApi.measure({
markName: markeNameIndex,
measureName: "".concat(markeNameIndex, " initial indexation"),
category: 'Search'
});
return _context9.abrupt("return", null);
case 10:
_context9.next = 15;
break;
case 12:
_context9.next = 14;
return this.incrementalIndexation(doctype, searchIndex);
case 14:
index = _context9.sent;
case 15:
endIndexing = performance.now();
if ((0, _utils.isDebug)()) {
log.debug("Indexing ".concat(doctype, " took ").concat((endIndexing - startIndexing).toFixed(2), " ms"));
}
this.performanceApi.measure({
markName: markeNameIndex,
measureName: "".concat(markeNameIndex, " incremental indexation"),
category: 'Search'
});
return _context9.abrupt("return", index);
case 19:
case "end":
return _context9.stop();
}
}
}, _callee9, this);
}));
function indexDocsForSearch(_x6) {
return _indexDocsForSearch.apply(this, arguments);
}
return indexDocsForSearch;
}()
}, {
key: "search",
value: function () {
var _search = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee10(query, options) {
var markeNameIndex, allResults, dedupResults, enrichedResults, sortedResults, results, normResults, _iterator3, _step3, res, normalizedRes, output;
return _regenerator.default.wrap(function _callee10$(_context10) {
while (1) {
switch (_context10.prev = _context10.next) {
case 0:
if (!(!this.searchIndexes || Object.keys(this.searchIndexes).length < 1)) {
_context10.next = 3;
break;
}
// The indexing might be running but not finished yet
log.warn('[SEARCH] No search index available');
return _context10.abrupt("return", null);
case 3:
markeNameIndex = this.performanceApi.mark('search');
allResults = this.searchOnIndexes(query, options === null || options === void 0 ? void 0 : options.doctypes);
dedupResults = this.deduplicateAndFlatten(allResults);
_context10.next = 8;
return (0, _normalizeSearchResult.enrichResultsWithDocs)(this.client, dedupResults);
case 8:
enrichedResults = _context10.sent;
sortedResults = this.sortSearchResults(enrichedResults, options === null || options === void 0 ? void 0 : options.doctypes);
results = this.limitSearchResults(sortedResults);
normResults = [];
_iterator3 = _createForOfIteratorHelper(results);
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
res = _step3.value;
normalizedRes = (0, _normalizeSearchResult.normalizeSearchResult)(this.client, res, query);
normResults.push(normalizedRes);
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
output = normResults.filter(function (res) {
return res.title;
});
this.performanceApi.measure({
markName: markeNameIndex,
category: 'Search'
});
return _context10.abrupt("return", output);
case 17:
case "end":
return _context10.stop();
}
}
}, _callee10, this);
}));
function search(_x7, _x8) {
return _search.apply(this, arguments);
}
return search;
}()
}, {
key: "searchOnIndexes",
value: function searchOnIndexes(query, searchOnDoctypes) {
var _this3 = this;
var searchResults = [];
var _loop = function _loop(key) {
var doctype = key; // XXX - Should not be necessary
if (searchOnDoctypes && !searchOnDoctypes.includes(doctype)) {
// Search only on specified doctypes
return "continue";
}
var index = _this3.searchIndexes[doctype];
if (!index) {
log.warn('[SEARCH] No search index available for ', doctype);
return "continue";
} // XXX - The limit is specified twice because of a flexsearch inconstency
// that does not enforce the limit if only given in second argument, and
// does not return the correct type is only given in third options
//
// XXX - The given limit here is arbitrary because flexsearch enforce it on matching
// field, which can cause issue related to the sort: if we search on name+path for files,
// and limit on 100, the 101th result on name will be skipped, but might appear on path,
// which will make it appear in the search results, but in incorrect order.
//
// Search result example:
// [
// {
// "field": "displayName",
// "result": [
// "604627c6bafee013ec5f27f7f72029f6"
// ]
// },
// {
// "field": "fullname",
// "result": [
// "604627c6bafee013ec5f27f7f72029f6", "604627c6bafee013ec5f27f3f714568"
// ]
// }
// ]
var FLEXSEARCH_LIMIT = 10000;
var indexResults = index.index.search(query, FLEXSEARCH_LIMIT, {
limit: FLEXSEARCH_LIMIT,
enrich: false
});
var newResults = indexResults.map(function (res) {
return _objectSpread(_objectSpread({}, res), {}, {
doctype: doctype
});
});
searchResults = searchResults.concat(newResults);
};
for (var key in this.searchIndexes) {
var _ret = _loop(key);
if (_ret === "continue") continue;
}
return searchResults;
}
}, {
key: "deduplicateAndFlatten",
value: function deduplicateAndFlatten(searchResults) {
var combinedResults = searchResults.flatMap(function (item) {
return item.result.map(function (id) {
return {
id: id.toString(),
// Because of flexsearch Id typing
doctype: item.doctype,
field: item.field
};
});
});
var resultMap = new Map();
combinedResults.forEach(function (_ref3) {
var id = _ref3.id,
field = _ref3.field,
doctype = _ref3.doctype;
if (resultMap.has(id)) {
var _resultMap$get;
(_resultMap$get = resultMap.get(id)) === null || _resultMap$get === void 0 ? void 0 : _resultMap$get.fields.push(field);
} else {
resultMap.set(id, {
id: id,
fields: [field],
doctype: doctype
});
}
});
return (0, _toConsumableArray2.default)(resultMap.values());
}
}, {
key: "compareStrings",
value: function compareStrings(str1, str2) {
if (!str1 && !str2) {
return 0;
} else if (!str1) {
return 1;
} else if (!str2) {
return -1;
}
return str1.localeCompare(str2, undefined, {
numeric: true
});
}
}, {
key: "sortSearchResults",
value: function sortSearchResults(searchResults, doctypesOrder) {
var _this4 = this;
return searchResults.sort(function (a, b) {
var doctypeComparison;
if (doctypesOrder) {
doctypeComparison = doctypesOrder.findIndex(function (dt) {
return dt === a.doctype;
}) - doctypesOrder.findIndex(function (dt) {
return dt === b.doctype;
});
} else {
doctypeComparison = _consts.DOCTYPE_DEFAULT_ORDER[a.doctype] - _consts.DOCTYPE_DEFAULT_ORDER[b.doctype];
}
if (doctypeComparison !== 0) return doctypeComparison;
if (a.doctype === _consts.APPS_DOCTYPE && (0, _types.isIOCozyApp)(a.doc) && (0, _types.isIOCozyApp)(b.doc)) {
var _a$doc, _b$doc;
return _this4.compareStrings((_a$doc = a.doc) === null || _a$doc === void 0 ? void 0 : _a$doc.slug, (_b$doc = b.doc) === null || _b$doc === void 0 ? void 0 : _b$doc.slug);
} else if (a.doctype === _consts.CONTACTS_DOCTYPE && (0, _types.isIOCozyContact)(a.doc) && (0, _types.isIOCozyContact)(b.doc)) {
var _a$doc2, _b$doc2;
return _this4.compareStrings((_a$doc2 = a.doc) === null || _a$doc2 === void 0 ? void 0 : _a$doc2.displayName, (_b$doc2 = b.doc) === null || _b$doc2 === void 0 ? void 0 : _b$doc2.displayName);
} else if (a.doctype === _consts.FILES_DOCTYPE && (0, _types.isIOCozyFile)(a.doc) && (0, _types.isIOCozyFile)(b.doc)) {
return _this4.sortFiles(a, b);
}
return 0;
});
}
}, {
key: "sortFiles",
value: function sortFiles(aRes, bRes) {
var _aRes$doc, _bRes$doc;
if (!(0, _types.isIOCozyFile)(aRes.doc) || !(0, _types.isIOCozyFile)(bRes.doc)) {
return 0;
}
if (!aRes.fields.includes('name') || !bRes.fields.includes('name')) {
// First, sort docs with a match on the name field
return aRes.fields.includes('name') ? -1 : 1;
}
if (aRes.doc.type !== bRes.doc.type) {
// Then, directories
return aRes.doc.type === 'directory' ? -1 : 1;
} // Then name
return this.compareStrings((_aRes$doc = aRes.doc) === null || _aRes$doc === void 0 ? void 0 : _aRes$doc.name, (_bRes$doc = bRes.doc) === null || _bRes$doc === void 0 ? void 0 : _bRes$doc.name);
}
}, {
key: "limitSearchResults",
value: function limitSearchResults(searchResults) {
var doctypesCount = {};
return searchResults.filter(function (result) {
var doctype = result.doctype;
if (doctypesCount[doctype]) {
doctypesCount[doctype] += 1;
} else {
doctypesCount[doctype] = 1;
}
return doctypesCount[doctype] <= _consts.LIMIT_DOCTYPE_SEARCH;
});
}
}]);
return SearchEngine;
}();
exports.SearchEngine = SearchEngine;