rxdb
Version:
A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/
224 lines (219 loc) • 7.69 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DocumentCache = void 0;
exports.mapDocumentsDataToCacheDocs = mapDocumentsDataToCacheDocs;
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _index2 = require("./plugins/utils/index.js");
var _overwritable = require("./overwritable.js");
/**
* Because we have to create many cache items,
* we use an array instead of an object with properties
* for better performance and less memory usage.
* @link https://stackoverflow.com/questions/17295056/array-vs-object-efficiency-in-javascript
*/
/**
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry
*/
/**
* The DocumentCache stores RxDocument objects
* by their primary key and revision.
* This is useful on client side applications where
* it is not known how much memory can be used, so
* we de-duplicate RxDocument states to save memory.
* To not fill up the memory with old document states, the DocumentCache
* only contains weak references to the RxDocuments themself.
* @link https://caniuse.com/?search=weakref
*/
var DocumentCache = exports.DocumentCache = /*#__PURE__*/function () {
/**
* Process stuff lazy to not block the CPU
* on critical paths.
*/
/**
* Some JavaScript runtimes like QuickJS,
* so not have a FinalizationRegistry or WeakRef.
* Therefore we need a workaround which might waste a lot of memory,
* but at least works.
*/
function DocumentCache(primaryPath, changes$,
/**
* A method that can create a RxDocument by the given document data.
*/
documentCreator) {
this.cacheItemByDocId = new Map();
this.tasks = new Set();
this.registry = typeof FinalizationRegistry === 'function' ? new FinalizationRegistry(docMeta => {
var docId = docMeta.docId;
var cacheItem = this.cacheItemByDocId.get(docId);
if (cacheItem) {
cacheItem[0].delete(docMeta.revisionHeight);
if (cacheItem[0].size === 0) {
/**
* No state of the document is cached anymore,
* so we can clean up.
*/
this.cacheItemByDocId.delete(docId);
}
}
}) : undefined;
this.primaryPath = primaryPath;
this.changes$ = changes$;
this.documentCreator = documentCreator;
changes$.subscribe(events => {
this.tasks.add(() => {
var cacheItemByDocId = this.cacheItemByDocId;
for (var index = 0; index < events.length; index++) {
var event = events[index];
var cacheItem = cacheItemByDocId.get(event.documentId);
if (cacheItem) {
var documentData = event.documentData;
if (!documentData) {
documentData = event.previousDocumentData;
}
cacheItem[1] = documentData;
}
}
});
if (this.tasks.size <= 1) {
(0, _index2.requestIdlePromiseNoQueue)().then(() => {
this.processTasks();
});
}
});
}
var _proto = DocumentCache.prototype;
_proto.processTasks = function processTasks() {
if (this.tasks.size === 0) {
return;
}
var tasks = Array.from(this.tasks);
tasks.forEach(task => task());
this.tasks.clear();
}
/**
* Get the RxDocument from the cache
* and create a new one if not exits before.
* @overwrites itself with the actual function
* because this is @performance relevant.
* It is called on each document row for each write and read.
*/;
/**
* Throws if not exists
*/
_proto.getLatestDocumentData = function getLatestDocumentData(docId) {
this.processTasks();
var cacheItem = (0, _index2.getFromMapOrThrow)(this.cacheItemByDocId, docId);
return cacheItem[1];
};
_proto.getLatestDocumentDataIfExists = function getLatestDocumentDataIfExists(docId) {
this.processTasks();
var cacheItem = this.cacheItemByDocId.get(docId);
if (cacheItem) {
return cacheItem[1];
}
};
return (0, _createClass2.default)(DocumentCache, [{
key: "getCachedRxDocuments",
get: function () {
var fn = getCachedRxDocumentMonad(this);
return (0, _index2.overwriteGetterForCaching)(this, 'getCachedRxDocuments', fn);
}
}, {
key: "getCachedRxDocument",
get: function () {
var fn = getCachedRxDocumentMonad(this);
return (0, _index2.overwriteGetterForCaching)(this, 'getCachedRxDocument', doc => fn([doc])[0]);
}
}]);
}();
/**
* This function is called very very often.
* This is likely the most important function for RxDB overall performance
* @hotPath This is one of the most important methods for performance.
* It is used in many places to transform the raw document data into RxDocuments.
*/
function getCachedRxDocumentMonad(docCache) {
var primaryPath = docCache.primaryPath;
var cacheItemByDocId = docCache.cacheItemByDocId;
var registry = docCache.registry;
var deepFreezeWhenDevMode = _overwritable.overwritable.deepFreezeWhenDevMode;
var documentCreator = docCache.documentCreator;
var fn = docsData => {
var ret = new Array(docsData.length);
var registryTasks = [];
for (var index = 0; index < docsData.length; index++) {
var docData = docsData[index];
var docId = docData[primaryPath];
var revisionHeight = (0, _index2.getHeightOfRevision)(docData._rev);
var byRev = void 0;
var cachedRxDocumentWeakRef = void 0;
var cacheItem = cacheItemByDocId.get(docId);
if (!cacheItem) {
byRev = new Map();
cacheItem = [byRev, docData];
cacheItemByDocId.set(docId, cacheItem);
} else {
byRev = cacheItem[0];
cachedRxDocumentWeakRef = byRev.get(revisionHeight);
}
var cachedRxDocument = cachedRxDocumentWeakRef ? cachedRxDocumentWeakRef.deref() : undefined;
if (!cachedRxDocument) {
docData = deepFreezeWhenDevMode(docData);
cachedRxDocument = documentCreator(docData);
byRev.set(revisionHeight, createWeakRefWithFallback(cachedRxDocument));
if (registry) {
registryTasks.push(cachedRxDocument);
}
}
ret[index] = cachedRxDocument;
}
if (registryTasks.length > 0 && registry) {
/**
* Calling registry.register() has shown to have
* really bad performance. So we add the cached documents
* lazily.
*/
docCache.tasks.add(() => {
for (var _index = 0; _index < registryTasks.length; _index++) {
var doc = registryTasks[_index];
registry.register(doc, {
docId: doc.primary,
revisionHeight: (0, _index2.getHeightOfRevision)(doc.revision)
});
}
});
if (docCache.tasks.size <= 1) {
(0, _index2.requestIdlePromiseNoQueue)().then(() => {
docCache.processTasks();
});
}
}
return ret;
};
return fn;
}
function mapDocumentsDataToCacheDocs(docCache, docsData) {
var getCachedRxDocuments = docCache.getCachedRxDocuments;
return getCachedRxDocuments(docsData);
}
/**
* Fallback for JavaScript runtimes that do not support WeakRef.
* The fallback will keep the items in cache forever,
* but at least works.
*/
var HAS_WEAK_REF = typeof WeakRef === 'function';
var createWeakRefWithFallback = HAS_WEAK_REF ? createWeakRef : createWeakRefFallback;
function createWeakRef(obj) {
return new WeakRef(obj);
}
function createWeakRefFallback(obj) {
return {
deref() {
return obj;
}
};
}
//# sourceMappingURL=doc-cache.js.map
;