UNPKG

rxdb

Version:

A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/

309 lines (300 loc) 10 kB
import _createClass from "@babel/runtime/helpers/createClass"; import { getFromMapOrThrow, overwriteGetterForCaching, requestIdlePromiseNoQueue } from "./plugins/utils/index.js"; import { overwritable } from "./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 */ export var 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.rev + docMeta.lwt); 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) { requestIdlePromiseNoQueue().then(() => { this.processTasks(); }); } }); } var _proto = DocumentCache.prototype; _proto.processTasks = function processTasks() { if (this.tasks.size === 0) { return; } this.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 = 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 _createClass(DocumentCache, [{ key: "getCachedRxDocuments", get: function () { var fn = getCachedRxDocumentMonad(this); return overwriteGetterForCaching(this, 'getCachedRxDocuments', fn); } }, { key: "getCachedRxDocument", get: function () { var fn = getCachedRxDocumentSingle(this); return overwriteGetterForCaching(this, 'getCachedRxDocument', fn); } }]); }(); /** * @hotPath Dedicated single-document function that avoids array allocations. * Used by getCachedRxDocument which is called from many call sites. */ function getCachedRxDocumentSingle(docCache) { var primaryPath = docCache.primaryPath; var cacheItemByDocId = docCache.cacheItemByDocId; var registry = docCache.registry; var deepFreezeWhenDevMode = overwritable.deepFreezeWhenDevMode; var documentCreator = docCache.documentCreator; return docData => { var docId = docData[primaryPath]; var rev = docData._rev; var lwt = docData._meta.lwt; var cacheKey = rev + lwt; var cacheItem = cacheItemByDocId.get(docId); if (!cacheItem) { docData = deepFreezeWhenDevMode(docData); var newDoc = documentCreator(docData); var newByRev = new Map(); newByRev.set(cacheKey, createWeakRefWithFallback(newDoc)); cacheItemByDocId.set(docId, [newByRev, docData]); if (registry) { docCache.tasks.add(() => { registry.register(newDoc, { docId: newDoc.primary, rev, lwt }); }); if (docCache.tasks.size <= 1) { requestIdlePromiseNoQueue().then(() => { docCache.processTasks(); }); } } return newDoc; } var byRev = cacheItem[0]; var cachedRxDocumentWeakRef = byRev.get(cacheKey); var cachedRxDocument = cachedRxDocumentWeakRef ? cachedRxDocumentWeakRef.deref() : undefined; if (!cachedRxDocument) { docData = deepFreezeWhenDevMode(docData); cachedRxDocument = documentCreator(docData); byRev.set(cacheKey, createWeakRefWithFallback(cachedRxDocument)); if (registry) { var registeredDoc = cachedRxDocument; docCache.tasks.add(() => { registry.register(registeredDoc, { docId: registeredDoc.primary, rev, lwt }); }); if (docCache.tasks.size <= 1) { requestIdlePromiseNoQueue().then(() => { docCache.processTasks(); }); } } } return cachedRxDocument; }; } /** * This function is called very very often. * @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.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 rev = docData._rev; var lwt = docData._meta.lwt; var cacheKey = rev + lwt; var cacheItem = cacheItemByDocId.get(docId); if (!cacheItem) { /** * New document - no need for WeakRef lookup. * Create cache item directly. */ docData = deepFreezeWhenDevMode(docData); var cachedRxDocument = documentCreator(docData); var byRev = new Map(); byRev.set(cacheKey, createWeakRefWithFallback(cachedRxDocument)); cacheItemByDocId.set(docId, [byRev, docData]); ret[index] = cachedRxDocument; if (registry) { if (!registryTasks) { registryTasks = []; } registryTasks.push({ doc: cachedRxDocument, rev, lwt }); } } else { var _byRev = cacheItem[0]; var cachedRxDocumentWeakRef = _byRev.get(cacheKey); var _cachedRxDocument = cachedRxDocumentWeakRef ? cachedRxDocumentWeakRef.deref() : undefined; if (!_cachedRxDocument) { docData = deepFreezeWhenDevMode(docData); _cachedRxDocument = documentCreator(docData); _byRev.set(cacheKey, createWeakRefWithFallback(_cachedRxDocument)); if (registry) { if (!registryTasks) { registryTasks = []; } registryTasks.push({ doc: _cachedRxDocument, rev, lwt }); } } ret[index] = _cachedRxDocument; } } if (registryTasks && registry) { /** * Calling registry.register() has shown to have * really bad performance. So we add the cached documents * lazily. */ var tasks = registryTasks; docCache.tasks.add(() => { for (var _index = 0; _index < tasks.length; _index++) { var task = tasks[_index]; registry.register(task.doc, { docId: task.doc.primary, rev: task.rev, lwt: task.lwt }); } }); if (docCache.tasks.size <= 1) { requestIdlePromiseNoQueue().then(() => { docCache.processTasks(); }); } } return ret; }; return fn; } export 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