import { mapDocumentsDataToCacheDocs } from './doc-cache.ts'; import { overwriteGetterForCaching } from './plugins/utils/index.ts'; import { newRxError } from './rx-error.ts'; import { RxQueryBase } from './rx-query.ts'; import type { RxDocument, RxDocumentData } from './types'; /** * Counter to create unique result IDs cheaply. * Used instead of now() to avoid expensive timestamp computation * when we only need uniqueness for change detection. */ let _resultIdCounter = 0; /** * RxDB needs the query results in multiple formats. * Sometimes as a Map or an array with only the documentData. * For better performance we work with this class * that initializes stuff lazily so that * we can directly work with the query results after RxQuery.exec() */ export class RxQuerySingleResult { /** * Unique identifier for this result state. * Used to determine if the result set has changed since X * so that we do not emit the same result multiple times on subscription. */ public readonly time = ++_resultIdCounter; public readonly documents: RxDocument[]; constructor( public readonly query: RxQueryBase, // only used internally, do not use outside, use this.docsData instead docsDataFromStorageInstance: RxDocumentData[], // can be overwritten for count-queries public readonly count: number, ) { /** * @performance Skip document mapping for count queries * since they only need the count number and pass an empty array. */ if (query.op === 'count') { this.documents = []; } else { this.documents = mapDocumentsDataToCacheDocs(this.query.collection._docCache, docsDataFromStorageInstance); } } /** * Instead of using the newResultData in the result cache, * we directly use the objects that are stored in the RxDocument * to ensure we do not store the same data twice and fill up the memory. * @overwrites itself with the actual value */ get docsData(): RxDocumentData[] { return overwriteGetterForCaching( this, 'docsData', this.documents.map(d => d._data) ); } // A key->document map, used in the event reduce optimization. get docsDataMap(): Map> { const map = new Map>(); this.documents.forEach(d => { map.set(d.primary, d._data); }); return overwriteGetterForCaching( this, 'docsDataMap', map ); } get docsMap(): Map> { const map = new Map>(); const documents = this.documents; for (let i = 0; i < documents.length; i++) { const doc = documents[i]; map.set(doc.primary, doc); } return overwriteGetterForCaching( this, 'docsMap', map ); } getValue(throwIfMissing?: boolean) { const op = this.query.op; if (op === 'count') { return this.count; } else if (op === 'findOne') { // findOne()-queries emit RxDocument or null const doc = this.documents.length === 0 ? null : this.documents[0]; if (!doc && throwIfMissing) { throw newRxError('QU10', { collection: this.query.collection.name, query: this.query.mangoQuery, op }); } else { return doc; } } else if (op === 'findByIds') { return this.docsMap; } else { // find()-queries emit RxDocument[] // Flat copy the array so it won't matter if the user modifies it. return this.documents.slice(0); } } }