UNPKG

rxdb

Version:

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

307 lines (300 loc) 12.2 kB
import _readOnlyError from "@babel/runtime/helpers/readOnlyError"; import { Subject } from 'rxjs'; import { getStartIndexStringFromLowerBound, getStartIndexStringFromUpperBound } from "../../custom-index.js"; import { getPrimaryFieldOfPrimaryKey } from "../../rx-schema-helper.js"; import { categorizeBulkWriteRows } from "../../rx-storage-helper.js"; import { deepEqual, ensureNotFalsy, now, PROMISE_RESOLVE_TRUE, PROMISE_RESOLVE_VOID, randomToken, requestIdlePromiseNoQueue } from "../../plugins/utils/index.js"; import { boundGE, boundGT, boundLE, boundLT } from "./binary-search-bounds.js"; import { attachmentMapKey, compareDocsWithIndex, ensureNotRemoved, getMemoryCollectionKey, putWriteRowToState, removeDocFromState } from "./memory-helper.js"; import { addIndexesToInternalsState, getMemoryIndexName } from "./memory-indexes.js"; import { getQueryMatcher, getSortComparator } from "../../rx-query-helper.js"; /** * Used in tests to ensure everything * is closed correctly */ export var OPEN_MEMORY_INSTANCES = new Set(); export var RxStorageInstanceMemory = /*#__PURE__*/function () { /** * Used by some plugins and storage wrappers * to find out details about the internals of a write operation. * For example if you want to know which documents really have been replaced * or newly inserted. */ function RxStorageInstanceMemory(storage, databaseName, collectionName, schema, internals, options, settings, devMode) { this.closed = false; this.categorizedByWriteInput = new WeakMap(); this.storage = storage; this.databaseName = databaseName; this.collectionName = collectionName; this.schema = schema; this.internals = internals; this.options = options; this.settings = settings; this.devMode = devMode; OPEN_MEMORY_INSTANCES.add(this); this.primaryPath = getPrimaryFieldOfPrimaryKey(this.schema.primaryKey); } var _proto = RxStorageInstanceMemory.prototype; _proto.bulkWrite = function bulkWrite(documentWrites, context) { this.ensurePersistence(); ensureNotRemoved(this); var internals = this.internals; var documentsById = this.internals.documents; var primaryPath = this.primaryPath; var categorized = categorizeBulkWriteRows(this, primaryPath, documentsById, documentWrites, context); var error = categorized.errors; /** * @performance * We have to return a Promise but we do not want to wait * one tick, so we directly create the promise * which makes it likely to be already resolved later. */ var awaitMe = Promise.resolve({ error }); this.categorizedByWriteInput.set(documentWrites, categorized); this.internals.ensurePersistenceTask = categorized; if (!this.internals.ensurePersistenceIdlePromise) { this.internals.ensurePersistenceIdlePromise = requestIdlePromiseNoQueue().then(() => { this.internals.ensurePersistenceIdlePromise = undefined; this.ensurePersistence(); }); } /** * Important: The events must be emitted AFTER the persistence * task has been added. */ if (categorized.eventBulk.events.length > 0) { var lastState = ensureNotFalsy(categorized.newestRow).document; categorized.eventBulk.checkpoint = { id: lastState[primaryPath], lwt: lastState._meta.lwt }; internals.changes$.next(categorized.eventBulk); } return awaitMe; } /** * Instead of directly inserting the documents into all indexes, * we do it lazy in the background. This gives the application time * to directly work with the write-result and to do stuff like rendering DOM * notes and processing RxDB queries. * Then in some later time, or just before the next read/write, * it is ensured that the indexes have been written. */; _proto.ensurePersistence = function ensurePersistence() { if (!this.internals.ensurePersistenceTask) { return; } var internals = this.internals; var documentsById = this.internals.documents; var primaryPath = this.primaryPath; var categorized = this.internals.ensurePersistenceTask; this.internals.ensurePersistenceTask = undefined; /** * Do inserts/updates */ var stateByIndex = Object.values(this.internals.byIndex); var bulkInsertDocs = categorized.bulkInsertDocs; for (var i = 0; i < bulkInsertDocs.length; ++i) { var writeRow = bulkInsertDocs[i]; var doc = writeRow.document; var docId = doc[primaryPath]; putWriteRowToState(docId, internals, stateByIndex, doc, undefined); } var bulkUpdateDocs = categorized.bulkUpdateDocs; for (var _i = 0; _i < bulkUpdateDocs.length; ++_i) { var _writeRow = bulkUpdateDocs[_i]; var _doc = _writeRow.document; var _docId = _doc[primaryPath]; putWriteRowToState(_docId, internals, stateByIndex, _doc, documentsById.get(_docId)); } /** * Handle attachments */ if (this.schema.attachments) { var attachmentsMap = internals.attachments; categorized.attachmentsAdd.forEach(attachment => { attachmentsMap.set(attachmentMapKey(attachment.documentId, attachment.attachmentId), { writeData: attachment.attachmentData, digest: attachment.digest }); }); if (this.schema.attachments) { categorized.attachmentsUpdate.forEach(attachment => { attachmentsMap.set(attachmentMapKey(attachment.documentId, attachment.attachmentId), { writeData: attachment.attachmentData, digest: attachment.digest }); }); categorized.attachmentsRemove.forEach(attachment => { attachmentsMap.delete(attachmentMapKey(attachment.documentId, attachment.attachmentId)); }); } } }; _proto.findDocumentsById = function findDocumentsById(docIds, withDeleted) { this.ensurePersistence(); var documentsById = this.internals.documents; var ret = []; if (documentsById.size === 0) { return Promise.resolve(ret); } for (var i = 0; i < docIds.length; ++i) { var docId = docIds[i]; var docInDb = documentsById.get(docId); if (docInDb && (!docInDb._deleted || withDeleted)) { ret.push(docInDb); } } return Promise.resolve(ret); }; _proto.query = function query(preparedQuery) { this.ensurePersistence(); var queryPlan = preparedQuery.queryPlan; var query = preparedQuery.query; var skip = query.skip ? query.skip : 0; var limit = query.limit ? query.limit : Infinity; var skipPlusLimit = skip + limit; var queryMatcher = false; if (!queryPlan.selectorSatisfiedByIndex) { queryMatcher = getQueryMatcher(this.schema, preparedQuery.query); } var queryPlanFields = queryPlan.index; var mustManuallyResort = !queryPlan.sortSatisfiedByIndex; var index = queryPlanFields; var lowerBound = queryPlan.startKeys; var lowerBoundString = getStartIndexStringFromLowerBound(this.schema, index, lowerBound); var upperBound = queryPlan.endKeys; upperBound = upperBound; var upperBoundString = getStartIndexStringFromUpperBound(this.schema, index, upperBound); var indexName = getMemoryIndexName(index); if (!this.internals.byIndex[indexName]) { throw new Error('index does not exist ' + indexName); } var docsWithIndex = this.internals.byIndex[indexName].docsWithIndex; var indexOfLower = (queryPlan.inclusiveStart ? boundGE : boundGT)(docsWithIndex, [lowerBoundString], compareDocsWithIndex); var indexOfUpper = (queryPlan.inclusiveEnd ? boundLE : boundLT)(docsWithIndex, [upperBoundString], compareDocsWithIndex); var rows = []; var done = false; while (!done) { var currentRow = docsWithIndex[indexOfLower]; if (!currentRow || indexOfLower > indexOfUpper) { break; } var currentDoc = currentRow[1]; if (!queryMatcher || queryMatcher(currentDoc)) { rows.push(currentDoc); } if (rows.length >= skipPlusLimit && !mustManuallyResort) { done = true; } indexOfLower++; } if (mustManuallyResort) { var sortComparator = getSortComparator(this.schema, preparedQuery.query); rows = rows.sort(sortComparator); } // apply skip and limit boundaries. rows = rows.slice(skip, skipPlusLimit); return Promise.resolve({ documents: rows }); }; _proto.count = async function count(preparedQuery) { this.ensurePersistence(); var result = await this.query(preparedQuery); return { count: result.documents.length, mode: 'fast' }; }; _proto.cleanup = function cleanup(minimumDeletedTime) { this.ensurePersistence(); var maxDeletionTime = now() - minimumDeletedTime; var index = ['_deleted', '_meta.lwt', this.primaryPath]; var indexName = getMemoryIndexName(index); var docsWithIndex = this.internals.byIndex[indexName].docsWithIndex; var lowerBoundString = getStartIndexStringFromLowerBound(this.schema, index, [true, 0, '']); var indexOfLower = boundGT(docsWithIndex, [lowerBoundString], compareDocsWithIndex); var done = false; while (!done) { var currentDoc = docsWithIndex[indexOfLower]; if (!currentDoc || currentDoc[1]._meta.lwt > maxDeletionTime) { done = true; } else { removeDocFromState(this.primaryPath, this.schema, this.internals, currentDoc[1]); indexOfLower++; } } return PROMISE_RESOLVE_TRUE; }; _proto.getAttachmentData = function getAttachmentData(documentId, attachmentId, digest) { this.ensurePersistence(); ensureNotRemoved(this); var key = attachmentMapKey(documentId, attachmentId); var data = this.internals.attachments.get(key); if (!digest || !data || data.digest !== digest) { throw new Error('attachment does not exist: ' + key); } return Promise.resolve(data.writeData.data); }; _proto.changeStream = function changeStream() { ensureNotRemoved(this); return this.internals.changes$.asObservable(); }; _proto.remove = async function remove() { if (this.closed) { throw new Error('closed'); } this.ensurePersistence(); ensureNotRemoved(this); this.internals.removed = true; this.storage.collectionStates.delete(getMemoryCollectionKey(this.databaseName, this.collectionName, this.schema.version)); await this.close(); }; _proto.close = function close() { OPEN_MEMORY_INSTANCES.delete(this); this.ensurePersistence(); if (this.closed) { return PROMISE_RESOLVE_VOID; } this.closed = true; this.internals.refCount = this.internals.refCount - 1; return PROMISE_RESOLVE_VOID; }; return RxStorageInstanceMemory; }(); export function createMemoryStorageInstance(storage, params, settings) { var collectionKey = getMemoryCollectionKey(params.databaseName, params.collectionName, params.schema.version); var internals = storage.collectionStates.get(collectionKey); if (!internals) { internals = { id: randomToken(5), schema: params.schema, removed: false, refCount: 1, documents: new Map(), attachments: params.schema.attachments ? new Map() : undefined, byIndex: {}, changes$: new Subject() }; addIndexesToInternalsState(internals, params.schema); storage.collectionStates.set(collectionKey, internals); } else { /** * Ensure that the storage was not already * created with a different schema. * This is very important because if this check * does not exist here, we have hard-to-debug problems * downstream. */ if (params.devMode && !deepEqual(internals.schema, params.schema)) { throw new Error('storage was already created with a different schema'); } internals.refCount = internals.refCount + 1; } var instance = new RxStorageInstanceMemory(storage, params.databaseName, params.collectionName, params.schema, internals, params.options, settings, params.devMode); return Promise.resolve(instance); } //# sourceMappingURL=rx-storage-instance-memory.js.map