Implement Couch Search and Request Batching (#3798)
* Implemented search in couch provider * Promises, not await * Batch requests * Only batch if > 1 * Remove legacy Couch adapter * Cleaned up couch request batching code * Added test cases * Code cleanup * Changes to new and legacy objects API to remove redundant persists due to mutation of modified and persisted timestamps * Cleaned up couch unit tests Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import CouchObjectQueue from "./CouchObjectQueue";
|
||||
const REV = "_rev";
|
||||
const ID = "_id";
|
||||
const HEARTBEAT = 50000;
|
||||
const ALL_DOCS = "_all_docs?include_docs=true";
|
||||
|
||||
export default class CouchObjectProvider {
|
||||
// options {
|
||||
@@ -41,6 +42,8 @@ export default class CouchObjectProvider {
|
||||
this.objectQueue = {};
|
||||
this.observeEnabled = options.disableObserve !== true;
|
||||
this.observers = {};
|
||||
this.batchIds = [];
|
||||
|
||||
if (this.observeEnabled) {
|
||||
this.observeObjectChanges(options.filter);
|
||||
}
|
||||
@@ -67,6 +70,9 @@ export default class CouchObjectProvider {
|
||||
// stringify body if needed
|
||||
if (fetchOptions.body) {
|
||||
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
||||
fetchOptions.headers = {
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
}
|
||||
|
||||
return fetch(this.url + '/' + subPath, fetchOptions)
|
||||
@@ -78,14 +84,18 @@ export default class CouchObjectProvider {
|
||||
});
|
||||
}
|
||||
|
||||
// Check the response to a create/update/delete request;
|
||||
// track the rev if it's valid, otherwise return false to
|
||||
// indicate that the request failed.
|
||||
// persist any queued objects
|
||||
/**
|
||||
* Check the response to a create/update/delete request;
|
||||
* track the rev if it's valid, otherwise return false to
|
||||
* indicate that the request failed.
|
||||
* persist any queued objects
|
||||
* @private
|
||||
*/
|
||||
checkResponse(response, intermediateResponse) {
|
||||
let requestSuccess = false;
|
||||
const id = response ? response.id : undefined;
|
||||
let rev;
|
||||
|
||||
if (response && response.ok) {
|
||||
rev = response.rev;
|
||||
requestSuccess = true;
|
||||
@@ -106,6 +116,9 @@ export default class CouchObjectProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getModel(response) {
|
||||
if (response && response.model) {
|
||||
let key = response[ID];
|
||||
@@ -131,10 +144,118 @@ export default class CouchObjectProvider {
|
||||
}
|
||||
|
||||
get(identifier, abortSignal) {
|
||||
return this.request(identifier.key, "GET", undefined, abortSignal).then(this.getModel.bind(this));
|
||||
this.batchIds.push(identifier.key);
|
||||
|
||||
if (this.bulkPromise === undefined) {
|
||||
this.bulkPromise = this.deferBatchedGet(abortSignal);
|
||||
}
|
||||
|
||||
return this.bulkPromise
|
||||
.then((domainObjectMap) => {
|
||||
return domainObjectMap[identifier.key];
|
||||
});
|
||||
}
|
||||
|
||||
async getObjectsByFilter(filter) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
deferBatchedGet(abortSignal) {
|
||||
// We until the next event loop cycle to "collect" all of the get
|
||||
// requests triggered in this iteration of the event loop
|
||||
|
||||
return this.waitOneEventCycle().then(() => {
|
||||
let batchIds = this.batchIds;
|
||||
|
||||
this.clearBatch();
|
||||
|
||||
if (batchIds.length === 1) {
|
||||
let objectKey = batchIds[0];
|
||||
|
||||
//If there's only one request, just do a regular get
|
||||
return this.request(objectKey, "GET", undefined, abortSignal)
|
||||
.then(this.returnAsMap(objectKey));
|
||||
} else {
|
||||
return this.bulkGet(batchIds, abortSignal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
returnAsMap(objectKey) {
|
||||
return (result) => {
|
||||
let objectMap = {};
|
||||
objectMap[objectKey] = this.getModel(result);
|
||||
|
||||
return objectMap;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
clearBatch() {
|
||||
this.batchIds = [];
|
||||
delete this.bulkPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
waitOneEventCycle() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
bulkGet(ids, signal) {
|
||||
ids = this.removeDuplicates(ids);
|
||||
|
||||
const query = {
|
||||
'keys': ids
|
||||
};
|
||||
|
||||
return this.request(ALL_DOCS, 'POST', query, signal).then((response) => {
|
||||
if (response && response.rows !== undefined) {
|
||||
return response.rows.reduce((map, row) => {
|
||||
if (row.doc !== undefined) {
|
||||
map[row.key] = this.getModel(row.doc);
|
||||
}
|
||||
|
||||
return map;
|
||||
}, {});
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
removeDuplicates(array) {
|
||||
return Array.from(new Set(array));
|
||||
}
|
||||
|
||||
search(query, abortSignal) {
|
||||
const filter = {
|
||||
"selector": {
|
||||
"model": {
|
||||
"name": {
|
||||
"$regex": `(?i)${query}`
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return this.getObjectsByFilter(filter, abortSignal);
|
||||
}
|
||||
|
||||
async getObjectsByFilter(filter, abortSignal) {
|
||||
let objects = [];
|
||||
|
||||
let url = `${this.url}/_find`;
|
||||
@@ -149,6 +270,7 @@ export default class CouchObjectProvider {
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
signal: abortSignal,
|
||||
body
|
||||
});
|
||||
|
||||
@@ -203,6 +325,9 @@ export default class CouchObjectProvider {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
abortGetChanges() {
|
||||
if (this.controller) {
|
||||
this.controller.abort();
|
||||
@@ -212,6 +337,9 @@ export default class CouchObjectProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async observeObjectChanges(filter) {
|
||||
let intermediateResponse = this.getIntermediateResponse();
|
||||
|
||||
@@ -292,6 +420,9 @@ export default class CouchObjectProvider {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getIntermediateResponse() {
|
||||
let intermediateResponse = {};
|
||||
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
||||
@@ -302,6 +433,9 @@ export default class CouchObjectProvider {
|
||||
return intermediateResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
enqueueObject(key, model, intermediateResponse) {
|
||||
if (this.objectQueue[key]) {
|
||||
this.objectQueue[key].enqueue({
|
||||
@@ -330,6 +464,9 @@ export default class CouchObjectProvider {
|
||||
return intermediateResponse.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
updateQueued(key) {
|
||||
if (!this.objectQueue[key].pending) {
|
||||
this.objectQueue[key].pending = true;
|
||||
|
||||
Reference in New Issue
Block a user