From 9c7f018182b4ddc7a03a154b95b791eb36a19eae Mon Sep 17 00:00:00 2001 From: Hannes Moser Date: Fri, 24 Nov 2023 23:47:44 +0100 Subject: [PATCH] Add query cache persistence --- .../storage-memory/memory-types.js.map | 2 +- dist/es/plugins/utils/utils-object.js | 2 +- dist/es/plugins/utils/utils-object.js.map | 2 +- dist/es/replication-protocol/upstream.js | 4 +- dist/es/replication-protocol/upstream.js.map | 2 +- dist/es/rx-query.js | 261 +++++++++++----- dist/es/rx-query.js.map | 2 +- .../storage-memory/memory-types.js.map | 2 +- dist/lib/plugins/utils/utils-object.js | 2 +- dist/lib/plugins/utils/utils-object.js.map | 2 +- dist/lib/replication-protocol/upstream.js | 4 +- dist/lib/replication-protocol/upstream.js.map | 2 +- dist/lib/rx-query.js | 261 +++++++++++----- dist/lib/rx-query.js.map | 2 +- dist/types/rx-query.d.ts | 2 + dist/types/types/rx-query.d.ts | 2 + src/plugins/storage-memory/memory-types.ts | 2 +- src/plugins/utils/utils-object.ts | 2 +- src/rx-query.ts | 282 +++++++++++++----- test/helper/cache.ts | 22 ++ test/unit/rx-query.test.ts | 51 +++- 21 files changed, 671 insertions(+), 242 deletions(-) create mode 100644 test/helper/cache.ts diff --git a/dist/es/plugins/storage-memory/memory-types.js.map b/dist/es/plugins/storage-memory/memory-types.js.map index 2494b97ef9f..d1e66ced723 100644 --- a/dist/es/plugins/storage-memory/memory-types.js.map +++ b/dist/es/plugins/storage-memory/memory-types.js.map @@ -1 +1 @@ -{"version":3,"file":"memory-types.js","names":[],"sources":["../../../../src/plugins/storage-memory/memory-types.ts"],"sourcesContent":["import { Subject } from 'rxjs';\nimport type {\n DefaultPreparedQuery,\n EventBulk,\n RxAttachmentWriteData,\n RxConflictResultionTask,\n RxDocumentData,\n RxStorage,\n RxStorageChangeEvent,\n RxStorageDefaultCheckpoint\n} from '../../types';\n\nexport type RxStorageMemorySettings = {};\nexport type RxStorageMemoryInstanceCreationOptions = {};\nexport type RxStorageMemory = RxStorage, RxStorageMemoryInstanceCreationOptions> & {\n /**\n * State by collectionKey\n */\n collectionStates: Map>;\n};\n\nexport type MemoryStorageInternalsByIndex = {\n index: string[];\n docsWithIndex: DocWithIndexString[];\n getIndexableString: (docData: RxDocumentData) => string;\n};\n\n/**\n * The internals are shared between multiple storage instances\n * that have been created with the same [databaseName+collectionName] combination.\n */\nexport type MemoryStorageInternals = {\n /**\n * We re-use the memory state when multiple instances\n * are created with the same params.\n * If refCount becomes 0, we can delete the state.\n */\n refCount: number;\n /**\n * If this becomes true,\n * it means that an instance has called remove()\n * so all other instances should also not work anymore.\n */\n removed: boolean;\n documents: Map>;\n /**\n * Attachments data, indexed by a combined string\n * consisting of [documentId + '||' + attachmentId]\n */\n attachments: Map;\n byIndex: {\n /**\n * Because RxDB requires a deterministic sorting\n * on all indexes, we can be sure that the composed index key\n * of each document is unique, because it contains the primaryKey\n * as last index part.\n * So we do not have to store the index-position when we want to do fast\n * writes. Instead we can do a binary search over the existing array\n * because RxDB also knows the previous state of the document when we do a bulkWrite().\n */\n [indexName: string]: MemoryStorageInternalsByIndex;\n };\n\n /**\n * To easier test the conflict resolution,\n * the memory storage exposes the conflict resolution task subject\n * so that we can inject own tasks during tests.\n */\n conflictResultionTasks$: Subject>;\n changes$: Subject>, RxStorageDefaultCheckpoint>>;\n};\n\nexport type DocWithIndexString = {\n id: string;\n doc: RxDocumentData;\n indexString: string;\n};\n\nexport type MemoryPreparedQuery = DefaultPreparedQuery;\n"],"mappings":""} \ No newline at end of file +{"version":3,"file":"memory-types.js","names":[],"sources":["../../../../src/plugins/storage-memory/memory-types.ts"],"sourcesContent":["import { Subject } from 'rxjs';\nimport type {\n DefaultPreparedQuery,\n EventBulk,\n RxAttachmentWriteData,\n RxConflictResultionTask,\n RxDocumentData,\n RxStorage,\n RxStorageChangeEvent,\n RxStorageDefaultCheckpoint\n} from '../../types';\n\nexport type RxStorageMemorySettings = {};\nexport type RxStorageMemoryInstanceCreationOptions = {};\nexport type RxStorageMemory = RxStorage, RxStorageMemoryInstanceCreationOptions> & {\n /**\n * State by collectionKey\n */\n collectionStates: Map>;\n};\n\nexport type MemoryStorageInternalsByIndex = {\n index: string[];\n docsWithIndex: DocWithIndexString[];\n getIndexableString: (docData: RxDocumentData) => string;\n};\n\n/**\n * The internals are shared between multiple storage instances\n * that have been created with the same [databaseName+collectionName] combination.\n */\nexport type MemoryStorageInternals = {\n /**\n * We reuse the memory state when multiple instances\n * are created with the same params.\n * If refCount becomes 0, we can delete the state.\n */\n refCount: number;\n /**\n * If this becomes true,\n * it means that an instance has called remove()\n * so all other instances should also not work anymore.\n */\n removed: boolean;\n documents: Map>;\n /**\n * Attachments data, indexed by a combined string\n * consisting of [documentId + '||' + attachmentId]\n */\n attachments: Map;\n byIndex: {\n /**\n * Because RxDB requires a deterministic sorting\n * on all indexes, we can be sure that the composed index key\n * of each document is unique, because it contains the primaryKey\n * as last index part.\n * So we do not have to store the index-position when we want to do fast\n * writes. Instead we can do a binary search over the existing array\n * because RxDB also knows the previous state of the document when we do a bulkWrite().\n */\n [indexName: string]: MemoryStorageInternalsByIndex;\n };\n\n /**\n * To easier test the conflict resolution,\n * the memory storage exposes the conflict resolution task subject\n * so that we can inject own tasks during tests.\n */\n conflictResultionTasks$: Subject>;\n changes$: Subject>, RxStorageDefaultCheckpoint>>;\n};\n\nexport type DocWithIndexString = {\n id: string;\n doc: RxDocumentData;\n indexString: string;\n};\n\nexport type MemoryPreparedQuery = DefaultPreparedQuery;\n"],"mappings":""} \ No newline at end of file diff --git a/dist/es/plugins/utils/utils-object.js b/dist/es/plugins/utils/utils-object.js index 11ef30170e7..2cd71200ef0 100644 --- a/dist/es/plugins/utils/utils-object.js +++ b/dist/es/plugins/utils/utils-object.js @@ -13,7 +13,7 @@ export function deepFreeze(o) { * RxDB normally uses the 'dot-prop' npm module. * But when performance is really relevant, this is not fast enough. * Instead we use a monad that can prepare some stuff up front - * and we can re-use the generated function. + * and we can reuse the generated function. */ export function objectPathMonad(objectPath) { diff --git a/dist/es/plugins/utils/utils-object.js.map b/dist/es/plugins/utils/utils-object.js.map index 8cfc1623697..adc15a1c129 100644 --- a/dist/es/plugins/utils/utils-object.js.map +++ b/dist/es/plugins/utils/utils-object.js.map @@ -1 +1 @@ -{"version":3,"file":"utils-object.js","names":["deepFreeze","o","Object","freeze","getOwnPropertyNames","forEach","prop","hasOwnProperty","isFrozen","objectPathMonad","objectPath","split","splitLength","length","obj","currentVal","i","subPath","getFromObjectOrThrow","key","val","Error","flattenObject","ob","toReturn","flatObject","x","flatClone","assign","firstPropertyNameOfObject","keys","firstPropertyValueOfObject","sortObject","noArraySort","Array","isArray","sort","a","b","localeCompare","map","RegExp","out","deepClone","src","ret","dest","clone","overwriteGetterForCaching","getterName","value","defineProperty","get","stringifyFilter","toString"],"sources":["../../../../src/plugins/utils/utils-object.ts"],"sourcesContent":["import type {\n DeepReadonlyObject\n} from '../../types';\n\nexport function deepFreeze(o: T): T {\n Object.freeze(o);\n Object.getOwnPropertyNames(o).forEach(function (prop) {\n if (\n (o as any).hasOwnProperty(prop)\n &&\n (o as any)[prop] !== null\n &&\n (\n typeof (o as any)[prop] === 'object'\n ||\n typeof (o as any)[prop] === 'function'\n )\n &&\n !Object.isFrozen((o as any)[prop])\n ) {\n deepFreeze((o as any)[prop]);\n }\n });\n return o;\n}\n\n\n\n/**\n * To get specific nested path values from objects,\n * RxDB normally uses the 'dot-prop' npm module.\n * But when performance is really relevant, this is not fast enough.\n * Instead we use a monad that can prepare some stuff up front\n * and we can re-use the generated function.\n */\nexport type ObjectPathMonadFunction = (obj: T) => R;\nexport function objectPathMonad(objectPath: string): ObjectPathMonadFunction {\n const split = objectPath.split('.');\n\n // reuse this variable for better performance.\n const splitLength = split.length;\n\n /**\n * Performance shortcut,\n * if no nested path is used,\n * directly return the field of the object.\n */\n if (splitLength === 1) {\n return (obj: T) => (obj as any)[objectPath];\n }\n\n\n return (obj: T) => {\n let currentVal: any = obj;\n for (let i = 0; i < splitLength; ++i) {\n const subPath = split[i];\n currentVal = currentVal[subPath];\n if (typeof currentVal === 'undefined') {\n return currentVal;\n }\n }\n return currentVal;\n };\n}\n\n\nexport function getFromObjectOrThrow(\n obj: { [k: string]: V; },\n key: string\n): V {\n const val = obj[key];\n if (!val) {\n throw new Error('missing value from object ' + key);\n }\n return val;\n}\n\n/**\n * returns a flattened object\n * @link https://gist.github.com/penguinboy/762197\n */\nexport function flattenObject(ob: any) {\n const toReturn: any = {};\n\n for (const i in ob) {\n if (!ob.hasOwnProperty(i)) continue;\n\n if ((typeof ob[i]) === 'object') {\n const flatObject = flattenObject(ob[i]);\n for (const x in flatObject) {\n if (!flatObject.hasOwnProperty(x)) continue;\n\n toReturn[i + '.' + x] = flatObject[x];\n }\n } else {\n toReturn[i] = ob[i];\n }\n }\n return toReturn;\n}\n\n\n/**\n * does a flat copy on the objects,\n * is about 3 times faster then using deepClone\n * @link https://jsperf.com/object-rest-spread-vs-clone/2\n */\nexport function flatClone(obj: T | DeepReadonlyObject | Readonly): T {\n return Object.assign({}, obj) as any;\n}\n\n/**\n * @link https://stackoverflow.com/a/11509718/3443137\n */\nexport function firstPropertyNameOfObject(obj: any): string {\n return Object.keys(obj)[0];\n}\nexport function firstPropertyValueOfObject(obj: { [k: string]: T; }): T {\n const key = Object.keys(obj)[0];\n return obj[key];\n}\n\n\n/**\n * deep-sort an object so its attributes are in lexical order.\n * Also sorts the arrays inside of the object if no-array-sort not set\n */\nexport function sortObject(obj: any, noArraySort = false): any {\n if (!obj) return obj; // do not sort null, false or undefined\n\n // array\n if (!noArraySort && Array.isArray(obj)) {\n return obj\n .sort((a, b) => {\n if (typeof a === 'string' && typeof b === 'string')\n return a.localeCompare(b);\n\n if (typeof a === 'object') return 1;\n else return -1;\n })\n .map(i => sortObject(i, noArraySort));\n }\n\n // object\n // array is also of type object\n if (typeof obj === 'object' && !Array.isArray(obj)) {\n if (obj instanceof RegExp) {\n return obj;\n }\n\n const out: any = {};\n Object.keys(obj)\n .sort((a, b) => a.localeCompare(b))\n .forEach(key => {\n out[key] = sortObject(obj[key], noArraySort);\n });\n return out;\n }\n\n // everything else\n return obj;\n}\n\n\n\n/**\n * Deep clone a plain json object.\n * Does not work with recursive stuff\n * or non-plain-json.\n * IMPORTANT: Performance of this is very important,\n * do not change it without running performance tests!\n *\n * @link https://github.com/zxdong262/deep-copy/blob/master/src/index.ts\n */\nfunction deepClone(src: T | DeepReadonlyObject): T {\n if (!src) {\n return src;\n }\n if (src === null || typeof (src) !== 'object') {\n return src;\n }\n if (Array.isArray(src)) {\n const ret = new Array(src.length);\n let i = ret.length;\n while (i--) {\n ret[i] = deepClone(src[i]);\n }\n return ret as any;\n }\n const dest: any = {};\n // eslint-disable-next-line guard-for-in\n for (const key in src) {\n dest[key] = deepClone(src[key]);\n }\n return dest;\n}\nexport const clone = deepClone;\n\n\n\n/**\n * overwrites the getter with the actual value\n * Mostly used for caching stuff on the first run\n */\nexport function overwriteGetterForCaching(\n obj: any,\n getterName: string,\n value: ValueType\n): ValueType {\n Object.defineProperty(obj, getterName, {\n get: function () {\n return value;\n }\n });\n return value;\n}\n\n\n\n/**\n * used to JSON.stringify() objects that contain a regex\n * @link https://stackoverflow.com/a/33416684 thank you Fabian Jakobs!\n */\nexport function stringifyFilter(key: string, value: any) {\n if (value instanceof RegExp) {\n return value.toString();\n }\n return value;\n}\n"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"utils-object.js","names":["deepFreeze","o","Object","freeze","getOwnPropertyNames","forEach","prop","hasOwnProperty","isFrozen","objectPathMonad","objectPath","split","splitLength","length","obj","currentVal","i","subPath","getFromObjectOrThrow","key","val","Error","flattenObject","ob","toReturn","flatObject","x","flatClone","assign","firstPropertyNameOfObject","keys","firstPropertyValueOfObject","sortObject","noArraySort","Array","isArray","sort","a","b","localeCompare","map","RegExp","out","deepClone","src","ret","dest","clone","overwriteGetterForCaching","getterName","value","defineProperty","get","stringifyFilter","toString"],"sources":["../../../../src/plugins/utils/utils-object.ts"],"sourcesContent":["import type {\n DeepReadonlyObject\n} from '../../types';\n\nexport function deepFreeze(o: T): T {\n Object.freeze(o);\n Object.getOwnPropertyNames(o).forEach(function (prop) {\n if (\n (o as any).hasOwnProperty(prop)\n &&\n (o as any)[prop] !== null\n &&\n (\n typeof (o as any)[prop] === 'object'\n ||\n typeof (o as any)[prop] === 'function'\n )\n &&\n !Object.isFrozen((o as any)[prop])\n ) {\n deepFreeze((o as any)[prop]);\n }\n });\n return o;\n}\n\n\n\n/**\n * To get specific nested path values from objects,\n * RxDB normally uses the 'dot-prop' npm module.\n * But when performance is really relevant, this is not fast enough.\n * Instead we use a monad that can prepare some stuff up front\n * and we can reuse the generated function.\n */\nexport type ObjectPathMonadFunction = (obj: T) => R;\nexport function objectPathMonad(objectPath: string): ObjectPathMonadFunction {\n const split = objectPath.split('.');\n\n // reuse this variable for better performance.\n const splitLength = split.length;\n\n /**\n * Performance shortcut,\n * if no nested path is used,\n * directly return the field of the object.\n */\n if (splitLength === 1) {\n return (obj: T) => (obj as any)[objectPath];\n }\n\n\n return (obj: T) => {\n let currentVal: any = obj;\n for (let i = 0; i < splitLength; ++i) {\n const subPath = split[i];\n currentVal = currentVal[subPath];\n if (typeof currentVal === 'undefined') {\n return currentVal;\n }\n }\n return currentVal;\n };\n}\n\n\nexport function getFromObjectOrThrow(\n obj: { [k: string]: V; },\n key: string\n): V {\n const val = obj[key];\n if (!val) {\n throw new Error('missing value from object ' + key);\n }\n return val;\n}\n\n/**\n * returns a flattened object\n * @link https://gist.github.com/penguinboy/762197\n */\nexport function flattenObject(ob: any) {\n const toReturn: any = {};\n\n for (const i in ob) {\n if (!ob.hasOwnProperty(i)) continue;\n\n if ((typeof ob[i]) === 'object') {\n const flatObject = flattenObject(ob[i]);\n for (const x in flatObject) {\n if (!flatObject.hasOwnProperty(x)) continue;\n\n toReturn[i + '.' + x] = flatObject[x];\n }\n } else {\n toReturn[i] = ob[i];\n }\n }\n return toReturn;\n}\n\n\n/**\n * does a flat copy on the objects,\n * is about 3 times faster then using deepClone\n * @link https://jsperf.com/object-rest-spread-vs-clone/2\n */\nexport function flatClone(obj: T | DeepReadonlyObject | Readonly): T {\n return Object.assign({}, obj) as any;\n}\n\n/**\n * @link https://stackoverflow.com/a/11509718/3443137\n */\nexport function firstPropertyNameOfObject(obj: any): string {\n return Object.keys(obj)[0];\n}\nexport function firstPropertyValueOfObject(obj: { [k: string]: T; }): T {\n const key = Object.keys(obj)[0];\n return obj[key];\n}\n\n\n/**\n * deep-sort an object so its attributes are in lexical order.\n * Also sorts the arrays inside of the object if no-array-sort not set\n */\nexport function sortObject(obj: any, noArraySort = false): any {\n if (!obj) return obj; // do not sort null, false or undefined\n\n // array\n if (!noArraySort && Array.isArray(obj)) {\n return obj\n .sort((a, b) => {\n if (typeof a === 'string' && typeof b === 'string')\n return a.localeCompare(b);\n\n if (typeof a === 'object') return 1;\n else return -1;\n })\n .map(i => sortObject(i, noArraySort));\n }\n\n // object\n // array is also of type object\n if (typeof obj === 'object' && !Array.isArray(obj)) {\n if (obj instanceof RegExp) {\n return obj;\n }\n\n const out: any = {};\n Object.keys(obj)\n .sort((a, b) => a.localeCompare(b))\n .forEach(key => {\n out[key] = sortObject(obj[key], noArraySort);\n });\n return out;\n }\n\n // everything else\n return obj;\n}\n\n\n\n/**\n * Deep clone a plain json object.\n * Does not work with recursive stuff\n * or non-plain-json.\n * IMPORTANT: Performance of this is very important,\n * do not change it without running performance tests!\n *\n * @link https://github.com/zxdong262/deep-copy/blob/master/src/index.ts\n */\nfunction deepClone(src: T | DeepReadonlyObject): T {\n if (!src) {\n return src;\n }\n if (src === null || typeof (src) !== 'object') {\n return src;\n }\n if (Array.isArray(src)) {\n const ret = new Array(src.length);\n let i = ret.length;\n while (i--) {\n ret[i] = deepClone(src[i]);\n }\n return ret as any;\n }\n const dest: any = {};\n // eslint-disable-next-line guard-for-in\n for (const key in src) {\n dest[key] = deepClone(src[key]);\n }\n return dest;\n}\nexport const clone = deepClone;\n\n\n\n/**\n * overwrites the getter with the actual value\n * Mostly used for caching stuff on the first run\n */\nexport function overwriteGetterForCaching(\n obj: any,\n getterName: string,\n value: ValueType\n): ValueType {\n Object.defineProperty(obj, getterName, {\n get: function () {\n return value;\n }\n });\n return value;\n}\n\n\n\n/**\n * used to JSON.stringify() objects that contain a regex\n * @link https://stackoverflow.com/a/33416684 thank you Fabian Jakobs!\n */\nexport function stringifyFilter(key: string, value: any) {\n if (value instanceof RegExp) {\n return value.toString();\n }\n return value;\n}\n"],"mappings":"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"} \ No newline at end of file diff --git a/dist/es/replication-protocol/upstream.js b/dist/es/replication-protocol/upstream.js index bb45e866bc0..6b8ee243205 100644 --- a/dist/es/replication-protocol/upstream.js +++ b/dist/es/replication-protocol/upstream.js @@ -170,14 +170,14 @@ export async function startReplicationUpstream(state) { assumedMasterDoc.metaDocument.isResolvedConflict !== fullDocData._rev && (await state.input.conflictHandler({ realMasterState: assumedMasterDoc.docData, newDocumentState: docData - }, 'upstream-check-if-equal')).isEqual || + }, 'upstream-check-if-equal')).isEqual || ( /** * If the master works with _rev fields, * we use that to check if our current doc state * is different from the assumedMasterDoc. */ - assumedMasterDoc && assumedMasterDoc.docData._rev && parseRevision(fullDocData._rev).height === fullDocData._meta[state.input.identifier]) { + assumedMasterDoc && assumedMasterDoc.docData._rev && parseRevision(fullDocData._rev).height === fullDocData._meta[state.input.identifier])) { return; } writeRowsToMasterIds.push(docId); diff --git a/dist/es/replication-protocol/upstream.js.map b/dist/es/replication-protocol/upstream.js.map index 644834723bf..897e74ead8e 100644 --- a/dist/es/replication-protocol/upstream.js.map +++ b/dist/es/replication-protocol/upstream.js.map @@ -1 +1 @@ -{"version":3,"file":"upstream.js","names":["firstValueFrom","filter","stackCheckpoints","appendToArray","batchArray","ensureNotFalsy","parseRevision","PROMISE_RESOLVE_FALSE","getLastCheckpointDoc","setCheckpoint","resolveConflictError","writeDocToDocState","getAssumedMasterState","getMetaWriteRow","startReplicationUpstream","state","input","initialCheckpoint","upstream","checkpointDoc","replicationHandler","streamQueue","up","then","upstreamInitialSync","processTasks","timer","initialSyncStartTime","openTasks","sub","forkInstance","changeStream","pipe","eventBulk","context","downstreamBulkWriteFlag","subscribe","stats","forkChangeStreamEmit","push","task","time","waitBeforePersist","events","canceled","unsubscribe","getValue","checkpointQueue","lastCheckpoint","promises","upResult","getChangedDocumentsSince","pushBatchSize","documents","length","checkpoint","persistToMaster","resolvedPromises","Promise","all","hadConflicts","find","r","firstSyncDone","next","active","docs","taskWithTime","shift","map","documentData","promise","persistenceQueue","nonPersistedFromMaster","forEach","docData","docId","primaryPath","upDocsById","useCheckpoint","docIds","Object","keys","assumedMasterState","writeRowsToMaster","writeRowsToMasterIds","writeRowsToMeta","forkStateById","fullDocData","assumedMasterDoc","metaDocument","isResolvedConflict","_rev","conflictHandler","realMasterState","newDocumentState","isEqual","height","_meta","identifier","undefined","writeRowsArray","values","conflictIds","Set","conflictsById","writeBatches","writeBatch","masterWriteResult","masterWrite","conflictDoc","id","add","useWriteRowsToMeta","has","processed","metaInstance","bulkWrite","hadConflictWrites","size","persistToMasterHadConflicts","conflictWriteFork","conflictWriteMeta","entries","writeToMasterRow","resolved","resolvedConflicts","output","previous","document","resolvedDoc","persistToMasterConflictWrites","forkWriteResult","useMetaWrites","success","catch","unhandledError","error"],"sources":["../../../src/replication-protocol/upstream.ts"],"sourcesContent":["import { firstValueFrom, filter } from 'rxjs';\nimport { stackCheckpoints } from '../rx-storage-helper';\nimport type {\n BulkWriteRow,\n BulkWriteRowById,\n ById,\n EventBulk,\n RxDocumentData,\n RxReplicationWriteToMasterRow,\n RxStorageChangeEvent,\n RxStorageInstanceReplicationState,\n RxStorageReplicationMeta,\n WithDeleted\n} from '../types';\nimport {\n appendToArray,\n batchArray,\n ensureNotFalsy,\n parseRevision,\n PROMISE_RESOLVE_FALSE\n} from '../plugins/utils';\nimport {\n getLastCheckpointDoc,\n setCheckpoint\n} from './checkpoint';\nimport { resolveConflictError } from './conflicts';\nimport { writeDocToDocState } from './helper';\nimport {\n getAssumedMasterState,\n getMetaWriteRow\n} from './meta-instance';\n\n/**\n * Writes all document changes from the fork to the master.\n * The upstream runs on two modes:\n * - For initial replication, a checkpoint-iteration is used\n * - For ongoing local writes, we just subscribe to the changeStream of the fork.\n * In contrast to the master, the fork can be assumed to never loose connection,\n * so we do not have to prepare for missed out events.\n */\nexport async function startReplicationUpstream(\n state: RxStorageInstanceReplicationState\n) {\n\n if (\n state.input.initialCheckpoint &&\n state.input.initialCheckpoint.upstream\n ) {\n const checkpointDoc = await getLastCheckpointDoc(state, 'up');\n if (!checkpointDoc) {\n await setCheckpoint(\n state,\n 'up',\n state.input.initialCheckpoint.upstream\n );\n }\n }\n\n const replicationHandler = state.input.replicationHandler;\n state.streamQueue.up = state.streamQueue.up.then(() => {\n return upstreamInitialSync().then(() => {\n processTasks();\n });\n });\n\n // used to detect which tasks etc can in it at which order.\n let timer = 0;\n let initialSyncStartTime = -1;\n\n type Task = EventBulk, any>;\n type TaskWithTime = {\n task: Task;\n time: number;\n };\n const openTasks: TaskWithTime[] = [];\n\n\n const sub = state.input.forkInstance.changeStream()\n .pipe(\n filter(eventBulk => eventBulk.context !== state.downstreamBulkWriteFlag)\n ).subscribe(eventBulk => {\n state.stats.up.forkChangeStreamEmit = state.stats.up.forkChangeStreamEmit + 1;\n openTasks.push({\n task: eventBulk,\n time: timer++\n });\n if (state.input.waitBeforePersist) {\n return state.input.waitBeforePersist()\n .then(() => processTasks());\n } else {\n return processTasks();\n }\n });\n firstValueFrom(\n state.events.canceled.pipe(\n filter(canceled => !!canceled)\n )\n ).then(() => sub.unsubscribe());\n\n\n async function upstreamInitialSync() {\n state.stats.up.upstreamInitialSync = state.stats.up.upstreamInitialSync + 1;\n if (state.events.canceled.getValue()) {\n return;\n }\n\n state.checkpointQueue = state.checkpointQueue.then(() => getLastCheckpointDoc(state, 'up'));\n let lastCheckpoint: CheckpointType = await state.checkpointQueue;\n\n const promises: Promise[] = [];\n while (!state.events.canceled.getValue()) {\n initialSyncStartTime = timer++;\n const upResult = await state.input.forkInstance.getChangedDocumentsSince(\n state.input.pushBatchSize,\n lastCheckpoint\n );\n if (upResult.documents.length === 0) {\n break;\n }\n\n lastCheckpoint = stackCheckpoints([lastCheckpoint, upResult.checkpoint]);\n\n promises.push(\n persistToMaster(\n upResult.documents,\n ensureNotFalsy(lastCheckpoint)\n )\n );\n }\n\n /**\n * If we had conflicts during the initial sync,\n * it means that we likely have new writes to the fork\n * and so we have to run the initial sync again to upstream these new writes.\n */\n const resolvedPromises = await Promise.all(promises);\n const hadConflicts = resolvedPromises.find(r => !!r);\n if (hadConflicts) {\n await upstreamInitialSync();\n } else if (\n !state.firstSyncDone.up.getValue() &&\n !state.events.canceled.getValue()\n ) {\n state.firstSyncDone.up.next(true);\n }\n }\n\n\n /**\n * Takes all open tasks an processes them at once.\n */\n function processTasks() {\n if (\n state.events.canceled.getValue() ||\n openTasks.length === 0\n ) {\n state.events.active.up.next(false);\n return;\n }\n state.stats.up.processTasks = state.stats.up.processTasks + 1;\n state.events.active.up.next(true);\n state.streamQueue.up = state.streamQueue.up.then(() => {\n /**\n * Merge/filter all open tasks\n */\n const docs: RxDocumentData[] = [];\n let checkpoint: CheckpointType = {} as any;\n while (openTasks.length > 0) {\n const taskWithTime = ensureNotFalsy(openTasks.shift());\n /**\n * If the task came in before the last time the initial sync fetching\n * has run, we can ignore the task because the initial sync already processed\n * these documents.\n */\n if (taskWithTime.time < initialSyncStartTime) {\n continue;\n }\n appendToArray(\n docs,\n taskWithTime.task.events.map(r => {\n return r.documentData as any;\n })\n );\n checkpoint = stackCheckpoints([checkpoint, taskWithTime.task.checkpoint]);\n }\n\n const promise = docs.length === 0 ? PROMISE_RESOLVE_FALSE : persistToMaster(\n docs,\n checkpoint\n );\n return promise.then(() => {\n if (openTasks.length === 0) {\n state.events.active.up.next(false);\n } else {\n processTasks();\n }\n });\n });\n }\n\n let persistenceQueue: Promise = PROMISE_RESOLVE_FALSE;\n const nonPersistedFromMaster: {\n checkpoint?: CheckpointType;\n docs: ById>;\n } = {\n docs: {}\n };\n\n /**\n * Returns true if had conflicts,\n * false if not.\n */\n function persistToMaster(\n docs: RxDocumentData[],\n checkpoint: CheckpointType\n ): Promise {\n state.stats.up.persistToMaster = state.stats.up.persistToMaster + 1;\n\n /**\n * Add the new docs to the non-persistent list\n */\n docs.forEach(docData => {\n const docId: string = (docData as any)[state.primaryPath];\n nonPersistedFromMaster.docs[docId] = docData;\n });\n nonPersistedFromMaster.checkpoint = checkpoint;\n\n\n persistenceQueue = persistenceQueue.then(async () => {\n if (state.events.canceled.getValue()) {\n return false;\n }\n\n const upDocsById: ById> = nonPersistedFromMaster.docs;\n nonPersistedFromMaster.docs = {};\n const useCheckpoint = nonPersistedFromMaster.checkpoint;\n const docIds = Object.keys(upDocsById);\n if (docIds.length === 0) {\n return false;\n }\n\n const assumedMasterState = await getAssumedMasterState(\n state,\n docIds\n );\n\n const writeRowsToMaster: ById> = {};\n const writeRowsToMasterIds: string[] = [];\n const writeRowsToMeta: BulkWriteRowById = {};\n const forkStateById: ById> = {};\n\n await Promise.all(\n docIds.map(async (docId) => {\n const fullDocData: RxDocumentData = upDocsById[docId];\n forkStateById[docId] = fullDocData;\n const docData: WithDeleted = writeDocToDocState(fullDocData);\n const assumedMasterDoc = assumedMasterState[docId];\n\n /**\n * If the master state is equal to the\n * fork state, we can assume that the document state is already\n * replicated.\n */\n if (\n (\n assumedMasterDoc &&\n // if the isResolvedConflict is correct, we do not have to compare the documents.\n assumedMasterDoc.metaDocument.isResolvedConflict !== fullDocData._rev\n &&\n (await state.input.conflictHandler({\n realMasterState: assumedMasterDoc.docData,\n newDocumentState: docData\n }, 'upstream-check-if-equal')).isEqual\n )\n ||\n /**\n * If the master works with _rev fields,\n * we use that to check if our current doc state\n * is different from the assumedMasterDoc.\n */\n (\n assumedMasterDoc &&\n (assumedMasterDoc.docData as any)._rev &&\n parseRevision(fullDocData._rev).height === fullDocData._meta[state.input.identifier]\n )\n ) {\n return;\n }\n\n writeRowsToMasterIds.push(docId);\n\n writeRowsToMaster[docId] = {\n assumedMasterState: assumedMasterDoc ? assumedMasterDoc.docData : undefined,\n newDocumentState: docData\n };\n writeRowsToMeta[docId] = getMetaWriteRow(\n state,\n docData,\n assumedMasterDoc ? assumedMasterDoc.metaDocument : undefined\n );\n })\n );\n\n if (writeRowsToMasterIds.length === 0) {\n return false;\n }\n\n\n const writeRowsArray = Object.values(writeRowsToMaster);\n const conflictIds: Set = new Set();\n const conflictsById: ById> = {};\n\n /**\n * To always respect the push.batchSize,\n * we have to split the write rows into batches\n * to ensure that replicationHandler.masterWrite() is never\n * called with more documents than what the batchSize limits.\n */\n const writeBatches = batchArray(writeRowsArray, state.input.pushBatchSize);\n await Promise.all(\n writeBatches.map(async (writeBatch) => {\n const masterWriteResult = await replicationHandler.masterWrite(writeBatch);\n masterWriteResult.forEach(conflictDoc => {\n const id = (conflictDoc as any)[state.primaryPath];\n conflictIds.add(id);\n conflictsById[id] = conflictDoc;\n });\n })\n );\n\n\n const useWriteRowsToMeta: BulkWriteRow[] = [];\n\n\n writeRowsToMasterIds.forEach(docId => {\n if (!conflictIds.has(docId)) {\n state.events.processed.up.next(writeRowsToMaster[docId]);\n useWriteRowsToMeta.push(writeRowsToMeta[docId]);\n }\n });\n\n if (useWriteRowsToMeta.length > 0) {\n await state.input.metaInstance.bulkWrite(\n useWriteRowsToMeta,\n 'replication-up-write-meta'\n );\n // TODO what happens when we have conflicts here?\n }\n\n /**\n * Resolve conflicts by writing a new document\n * state to the fork instance and the 'real' master state\n * to the meta instance.\n * Non-409 errors will be detected by resolveConflictError()\n */\n let hadConflictWrites = false;\n if (conflictIds.size > 0) {\n state.stats.up.persistToMasterHadConflicts = state.stats.up.persistToMasterHadConflicts + 1;\n const conflictWriteFork: BulkWriteRow[] = [];\n const conflictWriteMeta: BulkWriteRowById = {};\n await Promise.all(\n Object\n .entries(conflictsById)\n .map(([docId, realMasterState]) => {\n const writeToMasterRow = writeRowsToMaster[docId];\n const input = {\n newDocumentState: writeToMasterRow.newDocumentState,\n assumedMasterState: writeToMasterRow.assumedMasterState,\n realMasterState\n };\n return resolveConflictError(\n state,\n input,\n forkStateById[docId]\n ).then(resolved => {\n if (resolved) {\n state.events.resolvedConflicts.next({\n input,\n output: resolved.output\n });\n conflictWriteFork.push({\n previous: forkStateById[docId],\n document: resolved.resolvedDoc\n });\n const assumedMasterDoc = assumedMasterState[docId];\n conflictWriteMeta[docId] = getMetaWriteRow(\n state,\n ensureNotFalsy(realMasterState),\n assumedMasterDoc ? assumedMasterDoc.metaDocument : undefined,\n resolved.resolvedDoc._rev\n );\n }\n });\n })\n );\n\n if (conflictWriteFork.length > 0) {\n hadConflictWrites = true;\n\n state.stats.up.persistToMasterConflictWrites = state.stats.up.persistToMasterConflictWrites + 1;\n const forkWriteResult = await state.input.forkInstance.bulkWrite(\n conflictWriteFork,\n 'replication-up-write-conflict'\n );\n /**\n * Errors in the forkWriteResult must not be handled\n * because they have been caused by a write to the forkInstance\n * in between which will anyway trigger a new upstream cycle\n * that will then resolved the conflict again.\n */\n const useMetaWrites: BulkWriteRow[] = [];\n Object\n .keys(forkWriteResult.success)\n .forEach((docId) => {\n useMetaWrites.push(\n conflictWriteMeta[docId]\n );\n });\n if (useMetaWrites.length > 0) {\n await state.input.metaInstance.bulkWrite(\n useMetaWrites,\n 'replication-up-write-conflict-meta'\n );\n }\n // TODO what to do with conflicts while writing to the metaInstance?\n }\n }\n\n /**\n * For better performance we do not await checkpoint writes,\n * but to ensure order on parallel checkpoint writes,\n * we have to use a queue.\n */\n state.checkpointQueue = state.checkpointQueue.then(() => setCheckpoint(\n state,\n 'up',\n useCheckpoint\n ));\n\n return hadConflictWrites;\n }).catch(unhandledError => {\n state.events.error.next(unhandledError);\n return false;\n });\n\n return persistenceQueue;\n }\n}\n\n"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"upstream.js","names":["firstValueFrom","filter","stackCheckpoints","appendToArray","batchArray","ensureNotFalsy","parseRevision","PROMISE_RESOLVE_FALSE","getLastCheckpointDoc","setCheckpoint","resolveConflictError","writeDocToDocState","getAssumedMasterState","getMetaWriteRow","startReplicationUpstream","state","input","initialCheckpoint","upstream","checkpointDoc","replicationHandler","streamQueue","up","then","upstreamInitialSync","processTasks","timer","initialSyncStartTime","openTasks","sub","forkInstance","changeStream","pipe","eventBulk","context","downstreamBulkWriteFlag","subscribe","stats","forkChangeStreamEmit","push","task","time","waitBeforePersist","events","canceled","unsubscribe","getValue","checkpointQueue","lastCheckpoint","promises","upResult","getChangedDocumentsSince","pushBatchSize","documents","length","checkpoint","persistToMaster","resolvedPromises","Promise","all","hadConflicts","find","r","firstSyncDone","next","active","docs","taskWithTime","shift","map","documentData","promise","persistenceQueue","nonPersistedFromMaster","forEach","docData","docId","primaryPath","upDocsById","useCheckpoint","docIds","Object","keys","assumedMasterState","writeRowsToMaster","writeRowsToMasterIds","writeRowsToMeta","forkStateById","fullDocData","assumedMasterDoc","metaDocument","isResolvedConflict","_rev","conflictHandler","realMasterState","newDocumentState","isEqual","height","_meta","identifier","undefined","writeRowsArray","values","conflictIds","Set","conflictsById","writeBatches","writeBatch","masterWriteResult","masterWrite","conflictDoc","id","add","useWriteRowsToMeta","has","processed","metaInstance","bulkWrite","hadConflictWrites","size","persistToMasterHadConflicts","conflictWriteFork","conflictWriteMeta","entries","writeToMasterRow","resolved","resolvedConflicts","output","previous","document","resolvedDoc","persistToMasterConflictWrites","forkWriteResult","useMetaWrites","success","catch","unhandledError","error"],"sources":["../../../src/replication-protocol/upstream.ts"],"sourcesContent":["import { firstValueFrom, filter } from 'rxjs';\nimport { stackCheckpoints } from '../rx-storage-helper';\nimport type {\n BulkWriteRow,\n BulkWriteRowById,\n ById,\n EventBulk,\n RxDocumentData,\n RxReplicationWriteToMasterRow,\n RxStorageChangeEvent,\n RxStorageInstanceReplicationState,\n RxStorageReplicationMeta,\n WithDeleted\n} from '../types';\nimport {\n appendToArray,\n batchArray,\n ensureNotFalsy,\n parseRevision,\n PROMISE_RESOLVE_FALSE\n} from '../plugins/utils';\nimport {\n getLastCheckpointDoc,\n setCheckpoint\n} from './checkpoint';\nimport { resolveConflictError } from './conflicts';\nimport { writeDocToDocState } from './helper';\nimport {\n getAssumedMasterState,\n getMetaWriteRow\n} from './meta-instance';\n\n/**\n * Writes all document changes from the fork to the master.\n * The upstream runs on two modes:\n * - For initial replication, a checkpoint-iteration is used\n * - For ongoing local writes, we just subscribe to the changeStream of the fork.\n * In contrast to the master, the fork can be assumed to never loose connection,\n * so we do not have to prepare for missed out events.\n */\nexport async function startReplicationUpstream(\n state: RxStorageInstanceReplicationState\n) {\n\n if (\n state.input.initialCheckpoint &&\n state.input.initialCheckpoint.upstream\n ) {\n const checkpointDoc = await getLastCheckpointDoc(state, 'up');\n if (!checkpointDoc) {\n await setCheckpoint(\n state,\n 'up',\n state.input.initialCheckpoint.upstream\n );\n }\n }\n\n const replicationHandler = state.input.replicationHandler;\n state.streamQueue.up = state.streamQueue.up.then(() => {\n return upstreamInitialSync().then(() => {\n processTasks();\n });\n });\n\n // used to detect which tasks etc can in it at which order.\n let timer = 0;\n let initialSyncStartTime = -1;\n\n type Task = EventBulk, any>;\n type TaskWithTime = {\n task: Task;\n time: number;\n };\n const openTasks: TaskWithTime[] = [];\n\n\n const sub = state.input.forkInstance.changeStream()\n .pipe(\n filter(eventBulk => eventBulk.context !== state.downstreamBulkWriteFlag)\n ).subscribe(eventBulk => {\n state.stats.up.forkChangeStreamEmit = state.stats.up.forkChangeStreamEmit + 1;\n openTasks.push({\n task: eventBulk,\n time: timer++\n });\n if (state.input.waitBeforePersist) {\n return state.input.waitBeforePersist()\n .then(() => processTasks());\n } else {\n return processTasks();\n }\n });\n firstValueFrom(\n state.events.canceled.pipe(\n filter(canceled => !!canceled)\n )\n ).then(() => sub.unsubscribe());\n\n\n async function upstreamInitialSync() {\n state.stats.up.upstreamInitialSync = state.stats.up.upstreamInitialSync + 1;\n if (state.events.canceled.getValue()) {\n return;\n }\n\n state.checkpointQueue = state.checkpointQueue.then(() => getLastCheckpointDoc(state, 'up'));\n let lastCheckpoint: CheckpointType = await state.checkpointQueue;\n\n const promises: Promise[] = [];\n while (!state.events.canceled.getValue()) {\n initialSyncStartTime = timer++;\n const upResult = await state.input.forkInstance.getChangedDocumentsSince(\n state.input.pushBatchSize,\n lastCheckpoint\n );\n if (upResult.documents.length === 0) {\n break;\n }\n\n lastCheckpoint = stackCheckpoints([lastCheckpoint, upResult.checkpoint]);\n\n promises.push(\n persistToMaster(\n upResult.documents,\n ensureNotFalsy(lastCheckpoint)\n )\n );\n }\n\n /**\n * If we had conflicts during the initial sync,\n * it means that we likely have new writes to the fork\n * and so we have to run the initial sync again to upstream these new writes.\n */\n const resolvedPromises = await Promise.all(promises);\n const hadConflicts = resolvedPromises.find(r => !!r);\n if (hadConflicts) {\n await upstreamInitialSync();\n } else if (\n !state.firstSyncDone.up.getValue() &&\n !state.events.canceled.getValue()\n ) {\n state.firstSyncDone.up.next(true);\n }\n }\n\n\n /**\n * Takes all open tasks an processes them at once.\n */\n function processTasks() {\n if (\n state.events.canceled.getValue() ||\n openTasks.length === 0\n ) {\n state.events.active.up.next(false);\n return;\n }\n state.stats.up.processTasks = state.stats.up.processTasks + 1;\n state.events.active.up.next(true);\n state.streamQueue.up = state.streamQueue.up.then(() => {\n /**\n * Merge/filter all open tasks\n */\n const docs: RxDocumentData[] = [];\n let checkpoint: CheckpointType = {} as any;\n while (openTasks.length > 0) {\n const taskWithTime = ensureNotFalsy(openTasks.shift());\n /**\n * If the task came in before the last time the initial sync fetching\n * has run, we can ignore the task because the initial sync already processed\n * these documents.\n */\n if (taskWithTime.time < initialSyncStartTime) {\n continue;\n }\n appendToArray(\n docs,\n taskWithTime.task.events.map(r => {\n return r.documentData as any;\n })\n );\n checkpoint = stackCheckpoints([checkpoint, taskWithTime.task.checkpoint]);\n }\n\n const promise = docs.length === 0 ? PROMISE_RESOLVE_FALSE : persistToMaster(\n docs,\n checkpoint\n );\n return promise.then(() => {\n if (openTasks.length === 0) {\n state.events.active.up.next(false);\n } else {\n processTasks();\n }\n });\n });\n }\n\n let persistenceQueue: Promise = PROMISE_RESOLVE_FALSE;\n const nonPersistedFromMaster: {\n checkpoint?: CheckpointType;\n docs: ById>;\n } = {\n docs: {}\n };\n\n /**\n * Returns true if had conflicts,\n * false if not.\n */\n function persistToMaster(\n docs: RxDocumentData[],\n checkpoint: CheckpointType\n ): Promise {\n state.stats.up.persistToMaster = state.stats.up.persistToMaster + 1;\n\n /**\n * Add the new docs to the non-persistent list\n */\n docs.forEach(docData => {\n const docId: string = (docData as any)[state.primaryPath];\n nonPersistedFromMaster.docs[docId] = docData;\n });\n nonPersistedFromMaster.checkpoint = checkpoint;\n\n\n persistenceQueue = persistenceQueue.then(async () => {\n if (state.events.canceled.getValue()) {\n return false;\n }\n\n const upDocsById: ById> = nonPersistedFromMaster.docs;\n nonPersistedFromMaster.docs = {};\n const useCheckpoint = nonPersistedFromMaster.checkpoint;\n const docIds = Object.keys(upDocsById);\n if (docIds.length === 0) {\n return false;\n }\n\n const assumedMasterState = await getAssumedMasterState(\n state,\n docIds\n );\n\n const writeRowsToMaster: ById> = {};\n const writeRowsToMasterIds: string[] = [];\n const writeRowsToMeta: BulkWriteRowById = {};\n const forkStateById: ById> = {};\n\n await Promise.all(\n docIds.map(async (docId) => {\n const fullDocData: RxDocumentData = upDocsById[docId];\n forkStateById[docId] = fullDocData;\n const docData: WithDeleted = writeDocToDocState(fullDocData);\n const assumedMasterDoc = assumedMasterState[docId];\n\n /**\n * If the master state is equal to the\n * fork state, we can assume that the document state is already\n * replicated.\n */\n if (\n (\n assumedMasterDoc &&\n // if the isResolvedConflict is correct, we do not have to compare the documents.\n assumedMasterDoc.metaDocument.isResolvedConflict !== fullDocData._rev\n &&\n (await state.input.conflictHandler({\n realMasterState: assumedMasterDoc.docData,\n newDocumentState: docData\n }, 'upstream-check-if-equal')).isEqual\n )\n ||\n /**\n * If the master works with _rev fields,\n * we use that to check if our current doc state\n * is different from the assumedMasterDoc.\n */\n (\n assumedMasterDoc &&\n (assumedMasterDoc.docData as any)._rev &&\n parseRevision(fullDocData._rev).height === fullDocData._meta[state.input.identifier]\n )\n ) {\n return;\n }\n\n writeRowsToMasterIds.push(docId);\n\n writeRowsToMaster[docId] = {\n assumedMasterState: assumedMasterDoc ? assumedMasterDoc.docData : undefined,\n newDocumentState: docData\n };\n writeRowsToMeta[docId] = getMetaWriteRow(\n state,\n docData,\n assumedMasterDoc ? assumedMasterDoc.metaDocument : undefined\n );\n })\n );\n\n if (writeRowsToMasterIds.length === 0) {\n return false;\n }\n\n\n const writeRowsArray = Object.values(writeRowsToMaster);\n const conflictIds: Set = new Set();\n const conflictsById: ById> = {};\n\n /**\n * To always respect the push.batchSize,\n * we have to split the write rows into batches\n * to ensure that replicationHandler.masterWrite() is never\n * called with more documents than what the batchSize limits.\n */\n const writeBatches = batchArray(writeRowsArray, state.input.pushBatchSize);\n await Promise.all(\n writeBatches.map(async (writeBatch) => {\n const masterWriteResult = await replicationHandler.masterWrite(writeBatch);\n masterWriteResult.forEach(conflictDoc => {\n const id = (conflictDoc as any)[state.primaryPath];\n conflictIds.add(id);\n conflictsById[id] = conflictDoc;\n });\n })\n );\n\n\n const useWriteRowsToMeta: BulkWriteRow[] = [];\n\n\n writeRowsToMasterIds.forEach(docId => {\n if (!conflictIds.has(docId)) {\n state.events.processed.up.next(writeRowsToMaster[docId]);\n useWriteRowsToMeta.push(writeRowsToMeta[docId]);\n }\n });\n\n if (useWriteRowsToMeta.length > 0) {\n await state.input.metaInstance.bulkWrite(\n useWriteRowsToMeta,\n 'replication-up-write-meta'\n );\n // TODO what happens when we have conflicts here?\n }\n\n /**\n * Resolve conflicts by writing a new document\n * state to the fork instance and the 'real' master state\n * to the meta instance.\n * Non-409 errors will be detected by resolveConflictError()\n */\n let hadConflictWrites = false;\n if (conflictIds.size > 0) {\n state.stats.up.persistToMasterHadConflicts = state.stats.up.persistToMasterHadConflicts + 1;\n const conflictWriteFork: BulkWriteRow[] = [];\n const conflictWriteMeta: BulkWriteRowById = {};\n await Promise.all(\n Object\n .entries(conflictsById)\n .map(([docId, realMasterState]) => {\n const writeToMasterRow = writeRowsToMaster[docId];\n const input = {\n newDocumentState: writeToMasterRow.newDocumentState,\n assumedMasterState: writeToMasterRow.assumedMasterState,\n realMasterState\n };\n return resolveConflictError(\n state,\n input,\n forkStateById[docId]\n ).then(resolved => {\n if (resolved) {\n state.events.resolvedConflicts.next({\n input,\n output: resolved.output\n });\n conflictWriteFork.push({\n previous: forkStateById[docId],\n document: resolved.resolvedDoc\n });\n const assumedMasterDoc = assumedMasterState[docId];\n conflictWriteMeta[docId] = getMetaWriteRow(\n state,\n ensureNotFalsy(realMasterState),\n assumedMasterDoc ? assumedMasterDoc.metaDocument : undefined,\n resolved.resolvedDoc._rev\n );\n }\n });\n })\n );\n\n if (conflictWriteFork.length > 0) {\n hadConflictWrites = true;\n\n state.stats.up.persistToMasterConflictWrites = state.stats.up.persistToMasterConflictWrites + 1;\n const forkWriteResult = await state.input.forkInstance.bulkWrite(\n conflictWriteFork,\n 'replication-up-write-conflict'\n );\n /**\n * Errors in the forkWriteResult must not be handled\n * because they have been caused by a write to the forkInstance\n * in between which will anyway trigger a new upstream cycle\n * that will then resolved the conflict again.\n */\n const useMetaWrites: BulkWriteRow[] = [];\n Object\n .keys(forkWriteResult.success)\n .forEach((docId) => {\n useMetaWrites.push(\n conflictWriteMeta[docId]\n );\n });\n if (useMetaWrites.length > 0) {\n await state.input.metaInstance.bulkWrite(\n useMetaWrites,\n 'replication-up-write-conflict-meta'\n );\n }\n // TODO what to do with conflicts while writing to the metaInstance?\n }\n }\n\n /**\n * For better performance we do not await checkpoint writes,\n * but to ensure order on parallel checkpoint writes,\n * we have to use a queue.\n */\n state.checkpointQueue = state.checkpointQueue.then(() => setCheckpoint(\n state,\n 'up',\n useCheckpoint\n ));\n\n return hadConflictWrites;\n }).catch(unhandledError => {\n state.events.error.next(unhandledError);\n return false;\n });\n\n return persistenceQueue;\n }\n}\n\n"],"mappings":"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"} \ No newline at end of file diff --git a/dist/es/rx-query.js b/dist/es/rx-query.js index 62e7df138de..5ddda45429b 100644 --- a/dist/es/rx-query.js +++ b/dist/es/rx-query.js @@ -7,6 +7,7 @@ import { runPluginHooks } from './hooks'; import { calculateNewResults } from './event-reduce'; import { triggerCacheReplacement } from './query-cache'; import { getQueryMatcher, normalizeMangoQuery } from './rx-query-helper'; +import { murmurHash } from 'ohash'; var _queryCount = 0; var newQueryID = function () { return ++_queryCount; @@ -40,6 +41,8 @@ export var RxQueryBase = /*#__PURE__*/function () { this._lastExecEnd = 0; this._limitBufferSize = null; this._limitBufferResults = null; + this.PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS = 1_000; + this._persistedQueryCacheResult = undefined; this._ensureEqualQueue = PROMISE_RESOLVE_FALSE; this.op = op; this.mangoQuery = mangoQuery; @@ -66,6 +69,7 @@ export var RxQueryBase = /*#__PURE__*/function () { if (typeof newResultData === 'number') { this._result = { docsData: [], + docsKeys: [], docsMap: new Map(), docsDataMap: new Map(), count: newResultData, @@ -85,13 +89,16 @@ export var RxQueryBase = /*#__PURE__*/function () { * 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. */ + var docsKeys = []; var docsData = docs.map(doc => { docsDataMap.set(doc.primary, doc._data); docsMap.set(doc.primary, doc); + docsKeys.push(doc.primary); return doc._data; }); this._result = { docsData, + docsKeys, docsMap, docsDataMap, count: docsData.length, @@ -108,6 +115,11 @@ export var RxQueryBase = /*#__PURE__*/function () { this._execOverDatabaseCount = this._execOverDatabaseCount + 1; this._lastExecStart = now(); if (this.op === 'count') { + // if we have a persisted query cache result, use the result + if (this._persistedQueryCacheResult) { + // TODO: correct this number, but how? + return Number(this._persistedQueryCacheResult); + } var preparedQuery = this.getPreparedQuery(); var result = await this.collection.storageInstance.count(preparedQuery); if (result.mode === 'slow' && !this.collection.database.allowSlowCount) { @@ -146,7 +158,7 @@ export var RxQueryBase = /*#__PURE__*/function () { return ret; } var docsPromise = queryCollection(this); - return docsPromise.then(docs => { + return await docsPromise.then(docs => { this._lastExecEnd = now(); return docs; }); @@ -157,7 +169,7 @@ export var RxQueryBase = /*#__PURE__*/function () { * To have an easier implementations, * just subscribe and use the first result */; - _proto.exec = function exec(throwIfMissing) { + _proto.exec = async function exec(throwIfMissing) { if (throwIfMissing && this.op !== 'findOne') { throw newRxError('QU9', { collection: this.collection.name, @@ -171,17 +183,17 @@ export var RxQueryBase = /*#__PURE__*/function () { * this will make sure that errors in the query which throw inside of the RxStorage, * will be thrown at this execution context and not in the background. */ - return _ensureEqual(this).then(() => firstValueFrom(this.$)).then(result => { - if (!result && throwIfMissing) { - throw newRxError('QU10', { - collection: this.collection.name, - query: this.mangoQuery, - op: this.op - }); - } else { - return result; - } - }); + await _ensureEqual(this); + var result = await firstValueFrom(this.$); + if (!result && throwIfMissing) { + throw newRxError('QU10', { + collection: this.collection.name, + query: this.mangoQuery, + op: this.op + }); + } else { + return result; + } } /** @@ -205,7 +217,7 @@ export var RxQueryBase = /*#__PURE__*/function () { /** * returns the prepared query - * which can be send to the storage instance to query for documents. + * which can be sent to the storage instance to query for documents. * @overwrites itself with the actual value. */; _proto.getPreparedQuery = function getPreparedQuery() { @@ -262,7 +274,7 @@ export var RxQueryBase = /*#__PURE__*/function () { } // we only set some methods of query-builder here - // because the others depend on these ones + // because the others depend on these ; _proto.where = function where(_queryObj) { throw pluginMissing('query-builder'); @@ -292,6 +304,38 @@ export var RxQueryBase = /*#__PURE__*/function () { this._limitBufferSize = bufferSize; return this; }; + _proto.enablePersistentQueryCache = function enablePersistentQueryCache(backend, limit = this.PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS) { + this._queryCacheBackend = backend; + this._queryCacheLimit = limit; + this._persistedQueryCacheLoaded = this._loadPersistedResultsIfTheyExist(); + return this; + }; + _proto._loadPersistedResultsIfTheyExist = async function _loadPersistedResultsIfTheyExist() { + if (!this._queryCacheBackend) { + return; + } + if (this._persistedQueryCacheResult) { + return; + } + var key = _queryKey(this); + var value = await this._queryCacheBackend.getItem("qc:" + key); + this._persistedQueryCacheResult = value ?? undefined; + + // if this is a regular query, also load documents into cache + if (Array.isArray(value) && value.length > 0) { + this._persistedQueryCacheResult = value; + + /** + * TODO: try to move this to where the query execution happens, maybe lazy loading is even better, but if + * query definition and data loading is almost happening at the same time, this is the more simple + * implementation. + */ + var documents = await this.collection.storageInstance.findDocumentsById(value, false); + for (var document of Object.values(documents)) { + this.collection._docCache.getCachedRxDocument(document); + } + } + }; _createClass(RxQueryBase, [{ key: "$", get: function () { @@ -304,10 +348,10 @@ export var RxQueryBase = /*#__PURE__*/function () { filter(changeEvent => !changeEvent.isLocal), /** * Start once to ensure the querying also starts - * when there where no changes. + * when there were no changes. */ startWith(null), - // ensure query results are up to date. + // ensure query results are up-to-date. mergeMap(() => _ensureEqual(this)), // use the current result set, written by _ensureEqual(). map(() => this._result), @@ -315,11 +359,7 @@ export var RxQueryBase = /*#__PURE__*/function () { shareReplay(RXJS_SHARE_REPLAY_DEFAULTS), // do not proceed if result set has not changed. distinctUntilChanged((prev, curr) => { - if (prev && prev.time === ensureNotFalsy(curr).time) { - return true; - } else { - return false; - } + return Boolean(prev && prev.time === ensureNotFalsy(curr).time); }), filter(result => !!result), /** * Map the result set to a single RxDocument or an array, @@ -336,7 +376,7 @@ export var RxQueryBase = /*#__PURE__*/function () { return useResult.docsMap; } else { // find()-queries emit RxDocument[] - // Flat copy the array so it won't matter if the user modifies it. + // Flat copy the array, so it won't matter if the user modifies it. return useResult.docs.slice(0); } })); @@ -355,6 +395,7 @@ export var RxQueryBase = /*#__PURE__*/function () { // time stamps on when the last full exec over the database has run // used to properly handle events that happen while the find-query is running // Fields used for the Limit Buffer when enabled: + // Fields used for the persistent query cache when enabled: /** * ensures that the exec-runs * are not run in parallel @@ -397,28 +438,25 @@ export function createRxQuery(op, queryObj, collection, other) { // ensure when created with same params, only one is created ret = tunnelQueryCache(ret); + // TODO: clear persistent query cache as well triggerCacheReplacement(collection); return ret; } /** * Check if the current results-state is in sync with the database - * which means that no write event happened since the last run. + * which means that no writes event happened since the last run. * @return false if not which means it should re-execute */ function _isResultsInSync(rxQuery) { var currentLatestEventNumber = rxQuery.asRxQuery.collection._changeEventBuffer.counter; - if (rxQuery._latestChangeEvent >= currentLatestEventNumber) { - return true; - } else { - return false; - } + return rxQuery._latestChangeEvent >= currentLatestEventNumber; } /** * wraps __ensureEqual() * to ensure it does not run in parallel - * @return true if has changed, false if not + * @return true if it has changed, false if not */ function _ensureEqual(rxQuery) { // Optimisation shortcut @@ -433,7 +471,8 @@ function _ensureEqual(rxQuery) { * ensures that the results of this query is equal to the results which a query over the database would give * @return true if results have changed */ -function __ensureEqual(rxQuery) { +async function __ensureEqual(rxQuery) { + await rxQuery._persistedQueryCacheLoaded; rxQuery._lastEnsureEqual = now(); /** @@ -465,7 +504,7 @@ function __ensureEqual(rxQuery) { rxQuery._latestChangeEvent = rxQuery.asRxQuery.collection._changeEventBuffer.counter; var runChangeEvents = rxQuery.asRxQuery.collection._changeEventBuffer.reduceByLastOfDoc(missedChangeEvents); if (rxQuery._limitBufferResults !== null) { - var _loop = function (cE) { + var _loop = async function (cE) { if (rxQuery._limitBufferResults.find(doc => doc[rxQuery.collection.schema.primaryPath] === cE.documentId)) { // If so, the limit buffer is potential invalid -- let's just blow it up // TODO: could we instead update the documents in the limit buffer? @@ -475,7 +514,7 @@ function __ensureEqual(rxQuery) { }; // Check if any item in our limit buffer was modified by a change event for (var cE of runChangeEvents) { - if (_loop(cE)) break; + if (await _loop(cE)) break; } } if (rxQuery.op === 'count') { @@ -495,6 +534,7 @@ function __ensureEqual(rxQuery) { if (newCount !== previousCount) { ret = true; // true because results changed rxQuery._setResultData(newCount); + await _updatePersistentQueryCache(rxQuery); } } else { // 'find' or 'findOne' query @@ -506,12 +546,13 @@ function __ensureEqual(rxQuery) { // we got the new results, we do not have to re-execute, mustReExec stays false ret = true; // true because results changed rxQuery._setResultData(eventReduceResult.newResults); + await _updatePersistentQueryCache(rxQuery); } } } } - // oh no we have to re-execute the whole query over the database + // oh, no we have to re-execute the whole query over the database if (mustReExec) { // counter can change while _execOverDatabase() is running so we save it here var latestAfter = rxQuery.collection._changeEventBuffer.counter; @@ -531,9 +572,58 @@ function __ensureEqual(rxQuery) { rxQuery._setResultData(newResultData); } return ret; + }).then(async returnValue => { + await _updatePersistentQueryCache(rxQuery); + return returnValue; + }); + } + return ret; // true if results have changed +} + +function _queryKey(rxQuery) { + return String(murmurHash(rxQuery.toString(), 42)); +} +async function _updatePersistentQueryCache(rxQuery) { + if (!rxQuery._queryCacheBackend) { + return; + } + var backend = rxQuery._queryCacheBackend; + var isCount = rxQuery._result?.docs.length === 0 && rxQuery._result.count > 0; + var key = _queryKey(rxQuery); + var value = isCount ? rxQuery._result?.count?.toString() ?? '0' : rxQuery._result?.docsKeys ?? []; + + // update _persistedQueryCacheResult + rxQuery._persistedQueryCacheResult = value; + + // persist query cache + var lwt = rxQuery._result?.time ?? 0; + await backend.setItem("qc:" + String(key), value); + await backend.setItem("qc:" + String(key) + ":lwt", lwt.toString()); +} + +// Refactored out of `queryCollection`: modifies the docResults array to fill it with data +async function _queryCollectionByIds(rxQuery, docResults, docIds) { + var collection = rxQuery.collection; + docIds = docIds.filter(docId => { + // first try to fill from docCache + var docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId); + if (docData) { + if (!docData._deleted) { + docResults.push(docData); + } + return false; + } else { + return true; + } + }); + + // otherwise get from storage + if (docIds.length > 0) { + var docsMap = await collection.storageInstance.findDocumentsById(docIds, false); + Object.values(docsMap).forEach(docData => { + docResults.push(docData); }); } - return Promise.resolve(ret); // true if results have changed } /** @@ -543,6 +633,7 @@ function __ensureEqual(rxQuery) { * when specific queries are used. */ export async function queryCollection(rxQuery) { + await rxQuery._persistedQueryCacheLoaded; var docs = []; var collection = rxQuery.collection; @@ -554,26 +645,7 @@ export async function queryCollection(rxQuery) { */ if (rxQuery.isFindOneByIdQuery) { if (Array.isArray(rxQuery.isFindOneByIdQuery)) { - var docIds = rxQuery.isFindOneByIdQuery; - docIds = docIds.filter(docId => { - // first try to fill from docCache - var docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId); - if (docData) { - if (!docData._deleted) { - docs.push(docData); - } - return false; - } else { - return true; - } - }); - // otherwise get from storage - if (docIds.length > 0) { - var docsMap = await collection.storageInstance.findDocumentsById(docIds, false); - Object.values(docsMap).forEach(docData => { - docs.push(docData); - }); - } + await _queryCollectionByIds(rxQuery, docs, rxQuery.isFindOneByIdQuery); } else { var docId = rxQuery.isFindOneByIdQuery; @@ -581,25 +653,66 @@ export async function queryCollection(rxQuery) { var docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId); if (!docData) { // otherwise get from storage - var _docsMap = await collection.storageInstance.findDocumentsById([docId], false); - if (_docsMap.hasOwnProperty(docId)) { - docData = _docsMap[docId]; + var docsMap = await collection.storageInstance.findDocumentsById([docId], false); + if (docsMap.hasOwnProperty(docId)) { + docData = docsMap[docId]; } } if (docData && !docData._deleted) { docs.push(docData); } } - } else { - var preparedQuery = rxQuery.getPreparedQuery(); - var queryResult = await collection.storageInstance.query(preparedQuery); - if (rxQuery._limitBufferSize !== null && rxQuery.mangoQuery.limit && queryResult.documents.length > rxQuery.mangoQuery.limit) { - // If there are more than query.limit results, we pull out our buffer items from the - // last rxQuery._limitBufferSize items of the results. - rxQuery._limitBufferResults = queryResult.documents.splice(rxQuery.mangoQuery.limit); + return docs; + } + if ((rxQuery.mangoQuery.limit && !rxQuery.mangoQuery.skip || !rxQuery.mangoQuery.limit) && rxQuery._queryCacheBackend && Array.isArray(rxQuery._persistedQueryCacheResult)) { + var persistedQueryCacheIds = new Set(rxQuery._persistedQueryCacheResult); + var primaryPath = rxQuery.collection.schema.primaryPath; + var queryKeyValue = _queryKey(rxQuery); + var lwt = (await rxQuery._queryCacheBackend.getItem("qc:" + queryKeyValue + ":lwt")) ?? 0; + var previousDocCount = rxQuery._result?.docsKeys?.length ?? persistedQueryCacheIds.size; + + // query all docs updated > last persisted, limit to an arbitrary 1_000_000 (10x of what we consider our largest library) + var { + documents: changedDocs + } = await collection.storageInstance.getChangedDocumentsSince(1_000_000, { + id: '', + lwt + }); + for (var changedDoc of changedDocs) { + /* + * no need to fetch again, we already got the doc from the list of changed docs, and therefore we filter + * deleted docs as well + */ + persistedQueryCacheIds.delete(changedDoc[primaryPath]); + + // ignore deleted docs or docs that do not match the query + if (!rxQuery.doesDocumentDataMatch(changedDoc)) { + continue; + } + + // doc should be in result + docs.push(changedDoc); } - docs = queryResult.documents; + + // If doc count does not match the number of docs previously returned by the query + // 1. try to pull data from the limit buffer + // 2. re-query data + // TODO: How can I utilize the limitBuffer? + var newDocCount = docs.length + persistedQueryCacheIds.size; + if (newDocCount === previousDocCount) { + // fetch remaining doc ids and add to result + await _queryCollectionByIds(rxQuery, docs, Array.from(persistedQueryCacheIds)); + return docs; + } + } + var preparedQuery = rxQuery.getPreparedQuery(); + var queryResult = await collection.storageInstance.query(preparedQuery); + if (rxQuery._limitBufferSize !== null && rxQuery.mangoQuery.limit && queryResult.documents.length > rxQuery.mangoQuery.limit) { + // If there are more than query.limit results, we pull out our buffer items from the + // last rxQuery._limitBufferSize items of the results. + rxQuery._limitBufferResults = queryResult.documents.splice(rxQuery.mangoQuery.limit); } + docs = queryResult.documents; return docs; } @@ -614,18 +727,18 @@ export async function queryCollection(rxQuery) { export function isFindOneByIdQuery(primaryPath, query) { // must have exactly one operator which must be $eq || $in if (!query.skip && query.selector && Object.keys(query.selector).length === 1 && query.selector[primaryPath]) { - var value = query.selector[primaryPath]; - if (typeof value === 'string') { - return value; - } else if (Object.keys(value).length === 1 && typeof value.$eq === 'string') { - return value.$eq; + var _value = query.selector[primaryPath]; + if (typeof _value === 'string') { + return _value; + } else if (Object.keys(_value).length === 1 && typeof _value.$eq === 'string') { + return _value.$eq; } // same with $in string arrays - if (Object.keys(value).length === 1 && Array.isArray(value.$eq) && + if (Object.keys(_value).length === 1 && Array.isArray(_value.$eq) && // must only contain strings - !value.$eq.find(r => typeof r !== 'string')) { - return value.$eq; + !_value.$eq.find(r => typeof r !== 'string')) { + return _value.$eq; } } return false; diff --git a/dist/es/rx-query.js.map b/dist/es/rx-query.js.map index d3ad713c275..6fc38884946 100644 --- a/dist/es/rx-query.js.map +++ b/dist/es/rx-query.js.map @@ -1 +1 @@ -{"version":3,"file":"rx-query.js","names":["BehaviorSubject","firstValueFrom","merge","mergeMap","filter","map","startWith","distinctUntilChanged","shareReplay","sortObject","stringifyFilter","pluginMissing","overwriteGetterForCaching","now","PROMISE_RESOLVE_FALSE","RXJS_SHARE_REPLAY_DEFAULTS","ensureNotFalsy","areRxDocumentArraysEqual","newRxError","runPluginHooks","calculateNewResults","triggerCacheReplacement","getQueryMatcher","normalizeMangoQuery","_queryCount","newQueryID","RxQueryBase","op","mangoQuery","collection","other","id","_execOverDatabaseCount","_creationTime","_lastEnsureEqual","uncached","refCount$","_result","_latestChangeEvent","_lastExecStart","_lastExecEnd","_limitBufferSize","_limitBufferResults","_ensureEqualQueue","_getDefaultQuery","isFindOneByIdQuery","schema","primaryPath","_proto","prototype","_setResultData","newResultData","docsData","docsMap","Map","docsDataMap","count","docs","time","Array","from","values","docData","_docCache","getCachedRxDocument","doc","set","primary","_data","length","_execOverDatabase","preparedQuery","getPreparedQuery","result","storageInstance","mode","database","allowSlowCount","queryObj","ids","selector","$in","ret","mustBeQueried","forEach","getLatestDocumentDataIfExists","_deleted","push","findDocumentsById","Object","docsPromise","queryCollection","then","exec","throwIfMissing","name","query","_ensureEqual","$","toString","stringObj","value","JSON","stringify","hookInput","rxQuery","jsonSchema","limit","storage","statics","prepareQuery","doesDocumentDataMatch","queryMatcher","remove","isArray","Promise","all","update","_updateObj","where","_queryObj","sort","_params","skip","_amount","enableLimitBuffer","bufferSize","console","error","_createClass","key","get","_$","results$","pipe","changeEvent","isLocal","prev","curr","useResult","slice","normalizedQuery","tunnelQueryCache","_queryCache","getByQuery","createRxQuery","_isResultsInSync","currentLatestEventNumber","asRxQuery","_changeEventBuffer","counter","destroyed","__ensureEqual","mustReExec","missedChangeEvents","getFrom","runChangeEvents","reduceByLastOfDoc","_loop","cE","find","documentId","previousCount","newCount","didMatchBefore","previousDocumentData","doesMatchNow","documentData","eventReduceResult","runFullQueryAgain","changed","newResults","latestAfter","resolve","docIds","docId","hasOwnProperty","queryResult","documents","splice","keys","$eq","r","isRxQuery","obj"],"sources":["../../src/rx-query.ts"],"sourcesContent":["import {\n BehaviorSubject,\n firstValueFrom,\n Observable,\n merge\n} from 'rxjs';\nimport {\n mergeMap,\n filter,\n map,\n startWith,\n distinctUntilChanged,\n shareReplay\n} from 'rxjs/operators';\nimport {\n sortObject,\n stringifyFilter,\n pluginMissing,\n overwriteGetterForCaching,\n now,\n PROMISE_RESOLVE_FALSE,\n RXJS_SHARE_REPLAY_DEFAULTS,\n ensureNotFalsy,\n areRxDocumentArraysEqual\n} from './plugins/utils';\nimport {\n newRxError\n} from './rx-error';\nimport {\n runPluginHooks\n} from './hooks';\nimport type {\n RxCollection,\n RxDocument,\n RxQueryOP,\n RxQuery,\n MangoQuery,\n MangoQuerySortPart,\n MangoQuerySelector,\n PreparedQuery,\n RxChangeEvent,\n RxDocumentWriteData,\n RxDocumentData,\n QueryMatcher\n} from './types';\nimport { calculateNewResults } from './event-reduce';\nimport { triggerCacheReplacement } from './query-cache';\nimport { getQueryMatcher, normalizeMangoQuery } from './rx-query-helper';\n\nlet _queryCount = 0;\nconst newQueryID = function (): number {\n return ++_queryCount;\n};\n\nexport class RxQueryBase<\n RxDocType,\n // TODO also pass DocMethods here\n RxQueryResult = RxDocument[] | RxDocument\n> {\n\n public id: number = newQueryID();\n\n /**\n * Some stats then are used for debugging and cache replacement policies\n */\n public _execOverDatabaseCount: number = 0;\n public _creationTime = now();\n\n // used in the query-cache to determine if the RxQuery can be cleaned up.\n public _lastEnsureEqual = 0;\n\n public uncached = false;\n\n // used to count the subscribers to the query\n public refCount$ = new BehaviorSubject(null);\n\n public isFindOneByIdQuery: false | string | string[];\n\n\n /**\n * Contains the current result state\n * or null if query has not run yet.\n */\n public _result: {\n docsData: RxDocumentData[];\n // A key->document map, used in the event reduce optimization.\n docsDataMap: Map;\n docsMap: Map>;\n docs: RxDocument[];\n count: number;\n /**\n * Time at which the current _result state was created.\n * Used to determine if the result set has changed since X\n * so that we do not emit the same result multiple times on subscription.\n */\n time: number;\n } | null = null;\n\n\n constructor(\n public op: RxQueryOP,\n public mangoQuery: Readonly>,\n public collection: RxCollection,\n // used by some plugins\n public other: any = {}\n ) {\n if (!mangoQuery) {\n this.mangoQuery = _getDefaultQuery();\n }\n\n this.isFindOneByIdQuery = isFindOneByIdQuery(\n this.collection.schema.primaryPath as string,\n mangoQuery\n );\n }\n get $(): BehaviorSubject {\n if (!this._$) {\n\n const results$ = this.collection.$.pipe(\n /**\n * Performance shortcut.\n * Changes to local documents are not relevant for the query.\n */\n filter(changeEvent => !changeEvent.isLocal),\n /**\n * Start once to ensure the querying also starts\n * when there where no changes.\n */\n startWith(null),\n // ensure query results are up to date.\n mergeMap(() => _ensureEqual(this as any)),\n // use the current result set, written by _ensureEqual().\n map(() => this._result),\n // do not run stuff above for each new subscriber, only once.\n shareReplay(RXJS_SHARE_REPLAY_DEFAULTS),\n // do not proceed if result set has not changed.\n distinctUntilChanged((prev, curr) => {\n if (prev && prev.time === ensureNotFalsy(curr).time) {\n return true;\n } else {\n return false;\n }\n }),\n filter(result => !!result),\n /**\n * Map the result set to a single RxDocument or an array,\n * depending on query type\n */\n map((result) => {\n const useResult = ensureNotFalsy(result);\n if (this.op === 'count') {\n return useResult.count;\n } else if (this.op === 'findOne') {\n // findOne()-queries emit RxDocument or null\n return useResult.docs.length === 0 ? null : useResult.docs[0];\n } else if (this.op === 'findByIds') {\n return useResult.docsMap;\n } else {\n // find()-queries emit RxDocument[]\n // Flat copy the array so it won't matter if the user modifies it.\n return useResult.docs.slice(0);\n }\n })\n );\n\n this._$ = merge(\n results$,\n /**\n * Also add the refCount$ to the query observable\n * to allow us to count the amount of subscribers.\n */\n this.refCount$.pipe(\n filter(() => false)\n )\n );\n }\n return this._$ as any;\n }\n\n\n // stores the changeEvent-number of the last handled change-event\n public _latestChangeEvent: -1 | number = -1;\n\n // time stamps on when the last full exec over the database has run\n // used to properly handle events that happen while the find-query is running\n public _lastExecStart: number = 0;\n public _lastExecEnd: number = 0;\n\n // Fields used for the Limit Buffer when enabled:\n public _limitBufferSize: number | null = null;\n public _limitBufferResults: RxDocumentData[] | null = null;\n\n /**\n * ensures that the exec-runs\n * are not run in parallel\n */\n public _ensureEqualQueue: Promise = PROMISE_RESOLVE_FALSE;\n\n /**\n * Returns an observable that emits the results\n * This should behave like an rxjs-BehaviorSubject which means:\n * - Emit the current result-set on subscribe\n * - Emit the new result-set when an RxChangeEvent comes in\n * - Do not emit anything before the first result-set was created (no null)\n */\n public _$?: Observable;\n\n /**\n * set the new result-data as result-docs of the query\n * @param newResultData json-docs that were received from the storage\n */\n _setResultData(newResultData: RxDocumentData[] | number | Map>): void {\n if (typeof newResultData === 'number') {\n this._result = {\n docsData: [],\n docsMap: new Map(),\n docsDataMap: new Map(),\n count: newResultData,\n docs: [],\n time: now()\n };\n return;\n } else if (newResultData instanceof Map) {\n newResultData = Array.from((newResultData as Map>).values());\n }\n\n const docsDataMap = new Map();\n const docsMap = new Map();\n\n\n const docs = newResultData.map(docData => this.collection._docCache.getCachedRxDocument(docData));\n\n /**\n * Instead of using the newResultData in the result cache,\n * we directly use the objects that are stored in the RxDocument\n * to ensure we do not store the same data twice and fill up the memory.\n */\n const docsData = docs.map(doc => {\n docsDataMap.set(doc.primary, doc._data);\n docsMap.set(doc.primary, doc);\n return doc._data;\n });\n\n this._result = {\n docsData,\n docsMap,\n docsDataMap,\n count: docsData.length,\n docs,\n time: now()\n };\n }\n\n /**\n * executes the query on the database\n * @return results-array with document-data\n */\n async _execOverDatabase(): Promise[] | number> {\n this._execOverDatabaseCount = this._execOverDatabaseCount + 1;\n this._lastExecStart = now();\n\n\n if (this.op === 'count') {\n const preparedQuery = this.getPreparedQuery();\n const result = await this.collection.storageInstance.count(preparedQuery);\n if (result.mode === 'slow' && !this.collection.database.allowSlowCount) {\n throw newRxError('QU14', {\n collection: this.collection,\n queryObj: this.mangoQuery\n });\n } else {\n return result.count;\n }\n }\n\n if (this.op === 'findByIds') {\n const ids: string[] = ensureNotFalsy(this.mangoQuery.selector as any)[this.collection.schema.primaryPath].$in;\n const ret = new Map>();\n const mustBeQueried: string[] = [];\n // first try to fill from docCache\n ids.forEach(id => {\n const docData = this.collection._docCache.getLatestDocumentDataIfExists(id);\n if (docData) {\n if (!docData._deleted) {\n const doc = this.collection._docCache.getCachedRxDocument(docData);\n ret.set(id, doc);\n }\n } else {\n mustBeQueried.push(id);\n }\n });\n // everything which was not in docCache must be fetched from the storage\n if (mustBeQueried.length > 0) {\n const docs = await this.collection.storageInstance.findDocumentsById(mustBeQueried, false);\n Object.values(docs).forEach(docData => {\n const doc = this.collection._docCache.getCachedRxDocument(docData);\n ret.set(doc.primary, doc);\n });\n }\n return ret as any;\n }\n\n\n const docsPromise = queryCollection(this as any);\n return docsPromise.then(docs => {\n this._lastExecEnd = now();\n return docs;\n });\n }\n\n /**\n * Execute the query\n * To have an easier implementations,\n * just subscribe and use the first result\n */\n public exec(throwIfMissing: true): Promise>;\n public exec(): Promise;\n public exec(throwIfMissing?: boolean): Promise {\n if (throwIfMissing && this.op !== 'findOne') {\n throw newRxError('QU9', {\n collection: this.collection.name,\n query: this.mangoQuery,\n op: this.op\n });\n }\n\n\n /**\n * run _ensureEqual() here,\n * this will make sure that errors in the query which throw inside of the RxStorage,\n * will be thrown at this execution context and not in the background.\n */\n return _ensureEqual(this)\n .then(() => firstValueFrom(this.$))\n .then(result => {\n if (!result && throwIfMissing) {\n throw newRxError('QU10', {\n collection: this.collection.name,\n query: this.mangoQuery,\n op: this.op\n });\n } else {\n return result;\n }\n });\n }\n\n\n\n /**\n * cached call to get the queryMatcher\n * @overwrites itself with the actual value\n */\n get queryMatcher(): QueryMatcher> {\n const schema = this.collection.schema.jsonSchema;\n const normalizedQuery = normalizeMangoQuery(\n this.collection.schema.jsonSchema,\n this.mangoQuery\n );\n return overwriteGetterForCaching(\n this,\n 'queryMatcher',\n getQueryMatcher(\n schema,\n normalizedQuery\n ) as any\n );\n }\n\n /**\n * returns a string that is used for equal-comparisons\n * @overwrites itself with the actual value\n */\n toString(): string {\n const stringObj = sortObject({\n op: this.op,\n query: this.mangoQuery,\n other: this.other\n }, true);\n const value = JSON.stringify(stringObj, stringifyFilter);\n this.toString = () => value;\n return value;\n }\n\n /**\n * returns the prepared query\n * which can be send to the storage instance to query for documents.\n * @overwrites itself with the actual value.\n */\n getPreparedQuery(): PreparedQuery {\n const hookInput = {\n rxQuery: this,\n // can be mutated by the hooks so we have to deep clone first.\n mangoQuery: normalizeMangoQuery(\n this.collection.schema.jsonSchema,\n this.mangoQuery\n )\n };\n\n if (this._limitBufferSize !== null && hookInput.mangoQuery.limit) {\n hookInput.mangoQuery.limit = hookInput.mangoQuery.limit + this._limitBufferSize;\n }\n\n runPluginHooks('prePrepareQuery', hookInput);\n\n const value = this.collection.database.storage.statics.prepareQuery(\n this.collection.schema.jsonSchema,\n hookInput.mangoQuery\n );\n\n this.getPreparedQuery = () => value;\n return value;\n }\n\n /**\n * returns true if the document matches the query,\n * does not use the 'skip' and 'limit'\n */\n doesDocumentDataMatch(docData: RxDocType | any): boolean {\n // if doc is deleted, it cannot match\n if (docData._deleted) {\n return false;\n }\n\n return this.queryMatcher(docData);\n }\n\n /**\n * deletes all found documents\n * @return promise with deleted documents\n */\n remove(): Promise {\n return this\n .exec()\n .then(docs => {\n if (Array.isArray(docs)) {\n // TODO use a bulk operation instead of running .remove() on each document\n return Promise.all(docs.map(doc => doc.remove()));\n } else {\n return (docs as any).remove();\n }\n });\n }\n\n\n /**\n * helper function to transform RxQueryBase to RxQuery type\n */\n get asRxQuery(): RxQuery {\n return this as any;\n }\n\n /**\n * updates all found documents\n * @overwritten by plugin (optional)\n */\n update(_updateObj: any): Promise {\n throw pluginMissing('update');\n }\n\n\n // we only set some methods of query-builder here\n // because the others depend on these ones\n where(_queryObj: MangoQuerySelector | keyof RxDocType | string): RxQuery {\n throw pluginMissing('query-builder');\n }\n sort(_params: string | MangoQuerySortPart): RxQuery {\n throw pluginMissing('query-builder');\n }\n skip(_amount: number | null): RxQuery {\n throw pluginMissing('query-builder');\n }\n limit(_amount: number | null): RxQuery {\n throw pluginMissing('query-builder');\n }\n\n enableLimitBuffer(bufferSize: number) {\n if (this._limitBufferSize !== null) {\n // Limit buffer has already been enabled, do nothing:\n return this;\n }\n if (this._lastExecStart !== 0) {\n console.error('Can\\'t use limit buffer if query has already executed');\n return this;\n }\n if (this.mangoQuery.skip || !this.mangoQuery.limit) {\n console.error('Right now, limit buffer only works on non-skip, limit queries.');\n return this;\n }\n this._limitBufferSize = bufferSize;\n return this;\n }\n}\n\nexport function _getDefaultQuery(): MangoQuery {\n return {\n selector: {}\n };\n}\n\n/**\n * run this query through the QueryCache\n */\nexport function tunnelQueryCache(\n rxQuery: RxQueryBase\n): RxQuery {\n return rxQuery.collection._queryCache.getByQuery(rxQuery as any);\n}\n\nexport function createRxQuery(\n op: RxQueryOP,\n queryObj: MangoQuery,\n collection: RxCollection,\n other?: any\n) {\n runPluginHooks('preCreateRxQuery', {\n op,\n queryObj,\n collection,\n other\n });\n\n let ret = new RxQueryBase(op, queryObj, collection, other);\n\n // ensure when created with same params, only one is created\n ret = tunnelQueryCache(ret);\n triggerCacheReplacement(collection);\n\n return ret;\n}\n\n/**\n * Check if the current results-state is in sync with the database\n * which means that no write event happened since the last run.\n * @return false if not which means it should re-execute\n */\nfunction _isResultsInSync(rxQuery: RxQueryBase): boolean {\n const currentLatestEventNumber = rxQuery.asRxQuery.collection._changeEventBuffer.counter;\n if (rxQuery._latestChangeEvent >= currentLatestEventNumber) {\n return true;\n } else {\n return false;\n }\n}\n\n\n/**\n * wraps __ensureEqual()\n * to ensure it does not run in parallel\n * @return true if has changed, false if not\n */\nfunction _ensureEqual(rxQuery: RxQueryBase): Promise {\n // Optimisation shortcut\n if (\n rxQuery.collection.database.destroyed ||\n _isResultsInSync(rxQuery)\n ) {\n return PROMISE_RESOLVE_FALSE;\n }\n\n rxQuery._ensureEqualQueue = rxQuery._ensureEqualQueue\n .then(() => __ensureEqual(rxQuery));\n return rxQuery._ensureEqualQueue;\n}\n\n/**\n * ensures that the results of this query is equal to the results which a query over the database would give\n * @return true if results have changed\n */\nfunction __ensureEqual(rxQuery: RxQueryBase): Promise {\n rxQuery._lastEnsureEqual = now();\n\n /**\n * Optimisation shortcuts\n */\n if (\n // db is closed\n rxQuery.collection.database.destroyed ||\n // nothing happened since last run\n _isResultsInSync(rxQuery)\n ) {\n return PROMISE_RESOLVE_FALSE;\n }\n\n let ret = false;\n let mustReExec = false; // if this becomes true, a whole execution over the database is made\n if (rxQuery._latestChangeEvent === -1) {\n // have not executed yet -> must run\n mustReExec = true;\n }\n\n /**\n * try to use EventReduce to calculate the new results\n */\n if (!mustReExec) {\n const missedChangeEvents = rxQuery.asRxQuery.collection._changeEventBuffer.getFrom(rxQuery._latestChangeEvent + 1);\n if (missedChangeEvents === null) {\n // changeEventBuffer is of bounds -> we must re-execute over the database\n mustReExec = true;\n } else {\n rxQuery._latestChangeEvent = rxQuery.asRxQuery.collection._changeEventBuffer.counter;\n\n const runChangeEvents: RxChangeEvent[] = rxQuery.asRxQuery.collection\n ._changeEventBuffer\n .reduceByLastOfDoc(missedChangeEvents);\n\n if (rxQuery._limitBufferResults !== null) {\n // Check if any item in our limit buffer was modified by a change event\n for (const cE of runChangeEvents) {\n if (rxQuery._limitBufferResults.find((doc) => doc[rxQuery.collection.schema.primaryPath] === cE.documentId)) {\n // If so, the limit buffer is potential invalid -- let's just blow it up\n // TODO: could we instead update the documents in the limit buffer?\n rxQuery._limitBufferResults = null;\n break;\n }\n }\n }\n\n if (rxQuery.op === 'count') {\n // 'count' query\n const previousCount = ensureNotFalsy(rxQuery._result).count;\n let newCount = previousCount;\n runChangeEvents.forEach(cE => {\n const didMatchBefore = cE.previousDocumentData && rxQuery.doesDocumentDataMatch(cE.previousDocumentData);\n const doesMatchNow = rxQuery.doesDocumentDataMatch(cE.documentData);\n\n if (!didMatchBefore && doesMatchNow) {\n newCount++;\n }\n if (didMatchBefore && !doesMatchNow) {\n newCount--;\n }\n });\n if (newCount !== previousCount) {\n ret = true; // true because results changed\n rxQuery._setResultData(newCount as any);\n }\n } else {\n // 'find' or 'findOne' query\n const eventReduceResult = calculateNewResults(\n rxQuery as any,\n runChangeEvents\n );\n if (eventReduceResult.runFullQueryAgain) {\n // could not calculate the new results, execute must be done\n mustReExec = true;\n } else if (eventReduceResult.changed) {\n // we got the new results, we do not have to re-execute, mustReExec stays false\n ret = true; // true because results changed\n rxQuery._setResultData(eventReduceResult.newResults as any);\n }\n }\n }\n }\n\n\n\n // oh no we have to re-execute the whole query over the database\n if (mustReExec) {\n // counter can change while _execOverDatabase() is running so we save it here\n const latestAfter: number = (rxQuery as any).collection._changeEventBuffer.counter;\n return rxQuery._execOverDatabase()\n .then(newResultData => {\n rxQuery._latestChangeEvent = latestAfter;\n\n // A count query needs a different has-changed check.\n if (typeof newResultData === 'number') {\n if (\n !rxQuery._result ||\n newResultData !== rxQuery._result.count\n ) {\n ret = true;\n rxQuery._setResultData(newResultData as any);\n }\n return ret;\n }\n if (\n !rxQuery._result ||\n !areRxDocumentArraysEqual(\n rxQuery.collection.schema.primaryPath,\n newResultData,\n rxQuery._result.docsData\n )\n ) {\n ret = true; // true because results changed\n rxQuery._setResultData(newResultData as any);\n }\n return ret;\n });\n }\n return Promise.resolve(ret); // true if results have changed\n}\n\n/**\n * Runs the query over the storage instance\n * of the collection.\n * Does some optimizations to ensure findById is used\n * when specific queries are used.\n */\nexport async function queryCollection(\n rxQuery: RxQuery | RxQueryBase\n): Promise[]> {\n let docs: RxDocumentData[] = [];\n const collection = rxQuery.collection;\n\n /**\n * Optimizations shortcut.\n * If query is find-one-document-by-id,\n * then we do not have to use the slow query() method\n * but instead can use findDocumentsById()\n */\n if (rxQuery.isFindOneByIdQuery) {\n if (Array.isArray(rxQuery.isFindOneByIdQuery)) {\n let docIds = rxQuery.isFindOneByIdQuery;\n docIds = docIds.filter(docId => {\n // first try to fill from docCache\n const docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId);\n if (docData) {\n if (!docData._deleted) {\n docs.push(docData);\n }\n return false;\n } else {\n return true;\n }\n });\n // otherwise get from storage\n if (docIds.length > 0) {\n const docsMap = await collection.storageInstance.findDocumentsById(docIds, false);\n Object.values(docsMap).forEach(docData => {\n docs.push(docData);\n });\n }\n } else {\n const docId = rxQuery.isFindOneByIdQuery;\n\n // first try to fill from docCache\n let docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId);\n if (!docData) {\n // otherwise get from storage\n const docsMap = await collection.storageInstance.findDocumentsById([docId], false);\n if (docsMap.hasOwnProperty(docId)) {\n docData = docsMap[docId];\n }\n }\n if (docData && !docData._deleted) {\n docs.push(docData);\n }\n }\n } else {\n const preparedQuery = rxQuery.getPreparedQuery();\n const queryResult = await collection.storageInstance.query(preparedQuery);\n if (rxQuery._limitBufferSize !== null && rxQuery.mangoQuery.limit && queryResult.documents.length > rxQuery.mangoQuery.limit) {\n // If there are more than query.limit results, we pull out our buffer items from the\n // last rxQuery._limitBufferSize items of the results.\n rxQuery._limitBufferResults = queryResult.documents.splice(rxQuery.mangoQuery.limit);\n }\n docs = queryResult.documents;\n }\n return docs;\n\n}\n\n/**\n * Returns true if the given query\n * selects exactly one document by its id.\n * Used to optimize performance because these kind of\n * queries do not have to run over an index and can use get-by-id instead.\n * Returns false if no query of that kind.\n * Returns the document id otherwise.\n */\nexport function isFindOneByIdQuery(\n primaryPath: string,\n query: MangoQuery\n): false | string | string[] {\n // must have exactly one operator which must be $eq || $in\n if (\n !query.skip &&\n query.selector &&\n Object.keys(query.selector).length === 1 &&\n query.selector[primaryPath]\n ) {\n const value: any = query.selector[primaryPath];\n if (typeof value === 'string') {\n return value;\n } else if (\n Object.keys(value).length === 1 &&\n typeof value.$eq === 'string'\n ) {\n return value.$eq;\n }\n\n // same with $in string arrays\n if (\n Object.keys(value).length === 1 &&\n Array.isArray(value.$eq) &&\n // must only contain strings\n !(value.$eq as any[]).find(r => typeof r !== 'string')\n ) {\n return value.$eq;\n }\n }\n return false;\n}\n\n\n\nexport function isRxQuery(obj: any): boolean {\n return obj instanceof RxQueryBase;\n}\n"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"rx-query.js","names":["BehaviorSubject","firstValueFrom","merge","mergeMap","filter","map","startWith","distinctUntilChanged","shareReplay","sortObject","stringifyFilter","pluginMissing","overwriteGetterForCaching","now","PROMISE_RESOLVE_FALSE","RXJS_SHARE_REPLAY_DEFAULTS","ensureNotFalsy","areRxDocumentArraysEqual","newRxError","runPluginHooks","calculateNewResults","triggerCacheReplacement","getQueryMatcher","normalizeMangoQuery","murmurHash","_queryCount","newQueryID","RxQueryBase","op","mangoQuery","collection","other","id","_execOverDatabaseCount","_creationTime","_lastEnsureEqual","uncached","refCount$","_result","_latestChangeEvent","_lastExecStart","_lastExecEnd","_limitBufferSize","_limitBufferResults","PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS","_persistedQueryCacheResult","undefined","_ensureEqualQueue","_getDefaultQuery","isFindOneByIdQuery","schema","primaryPath","_proto","prototype","_setResultData","newResultData","docsData","docsKeys","docsMap","Map","docsDataMap","count","docs","time","Array","from","values","docData","_docCache","getCachedRxDocument","doc","set","primary","_data","push","length","_execOverDatabase","Number","preparedQuery","getPreparedQuery","result","storageInstance","mode","database","allowSlowCount","queryObj","ids","selector","$in","ret","mustBeQueried","forEach","getLatestDocumentDataIfExists","_deleted","findDocumentsById","Object","docsPromise","queryCollection","then","exec","throwIfMissing","name","query","_ensureEqual","$","toString","stringObj","value","JSON","stringify","hookInput","rxQuery","jsonSchema","limit","storage","statics","prepareQuery","doesDocumentDataMatch","queryMatcher","remove","isArray","Promise","all","update","_updateObj","where","_queryObj","sort","_params","skip","_amount","enableLimitBuffer","bufferSize","console","error","enablePersistentQueryCache","backend","_queryCacheBackend","_queryCacheLimit","_persistedQueryCacheLoaded","_loadPersistedResultsIfTheyExist","key","_queryKey","getItem","documents","document","_createClass","get","_$","results$","pipe","changeEvent","isLocal","prev","curr","Boolean","useResult","slice","normalizedQuery","tunnelQueryCache","_queryCache","getByQuery","createRxQuery","_isResultsInSync","currentLatestEventNumber","asRxQuery","_changeEventBuffer","counter","destroyed","__ensureEqual","mustReExec","missedChangeEvents","getFrom","runChangeEvents","reduceByLastOfDoc","_loop","cE","find","documentId","previousCount","newCount","didMatchBefore","previousDocumentData","doesMatchNow","documentData","_updatePersistentQueryCache","eventReduceResult","runFullQueryAgain","changed","newResults","latestAfter","returnValue","String","isCount","lwt","setItem","_queryCollectionByIds","docResults","docIds","docId","hasOwnProperty","persistedQueryCacheIds","Set","queryKeyValue","previousDocCount","size","changedDocs","getChangedDocumentsSince","changedDoc","delete","newDocCount","queryResult","splice","keys","$eq","r","isRxQuery","obj"],"sources":["../../src/rx-query.ts"],"sourcesContent":["import {\n BehaviorSubject,\n firstValueFrom,\n Observable,\n merge\n} from 'rxjs';\nimport {\n mergeMap,\n filter,\n map,\n startWith,\n distinctUntilChanged,\n shareReplay\n} from 'rxjs/operators';\nimport {\n sortObject,\n stringifyFilter,\n pluginMissing,\n overwriteGetterForCaching,\n now,\n PROMISE_RESOLVE_FALSE,\n RXJS_SHARE_REPLAY_DEFAULTS,\n ensureNotFalsy,\n areRxDocumentArraysEqual\n} from './plugins/utils';\nimport {\n newRxError\n} from './rx-error';\nimport {\n runPluginHooks\n} from './hooks';\nimport type {\n RxCollection,\n RxDocument,\n RxQueryOP,\n RxQuery,\n MangoQuery,\n MangoQuerySortPart,\n MangoQuerySelector,\n PreparedQuery,\n RxChangeEvent,\n RxDocumentWriteData,\n RxDocumentData,\n QueryMatcher\n} from './types';\nimport { calculateNewResults } from './event-reduce';\nimport { triggerCacheReplacement } from './query-cache';\nimport { getQueryMatcher, normalizeMangoQuery } from './rx-query-helper';\nimport {murmurHash} from 'ohash';\n\nexport interface QueryCacheBackend {\n getItem(key: string): Promise;\n setItem(key: string, value: T): Promise;\n}\n\nlet _queryCount = 0;\nconst newQueryID = function (): number {\n return ++_queryCount;\n};\n\nexport class RxQueryBase<\n RxDocType,\n // TODO also pass DocMethods here\n RxQueryResult = RxDocument[] | RxDocument\n> {\n public id: number = newQueryID();\n\n /**\n * Some stats then are used for debugging and cache replacement policies\n */\n public _execOverDatabaseCount: number = 0;\n public _creationTime = now();\n\n // used in the query-cache to determine if the RxQuery can be cleaned up.\n public _lastEnsureEqual = 0;\n\n public uncached = false;\n\n // used to count the subscribers to the query\n public refCount$ = new BehaviorSubject(null);\n\n public isFindOneByIdQuery: false | string | string[];\n\n /**\n * Contains the current result state\n * or null if query has not run yet.\n */\n public _result: {\n docsData: RxDocumentData[];\n // A key->document map, used in the event reduce optimization.\n docsDataMap: Map;\n docsKeys: string[];\n docsMap: Map>;\n docs: RxDocument[];\n count: number;\n /**\n * Time at which the current _result state was created.\n * Used to determine if the result set has changed since X\n * so that we do not emit the same result multiple times on subscription.\n */\n time: number;\n } | null = null;\n\n\n constructor(\n public op: RxQueryOP,\n public mangoQuery: Readonly>,\n public collection: RxCollection,\n // used by some plugins\n public other: any = {}\n ) {\n if (!mangoQuery) {\n this.mangoQuery = _getDefaultQuery();\n }\n\n this.isFindOneByIdQuery = isFindOneByIdQuery(\n this.collection.schema.primaryPath as string,\n mangoQuery\n );\n }\n get $(): BehaviorSubject {\n if (!this._$) {\n\n const results$ = this.collection.$.pipe(\n /**\n * Performance shortcut.\n * Changes to local documents are not relevant for the query.\n */\n filter(changeEvent => !changeEvent.isLocal),\n /**\n * Start once to ensure the querying also starts\n * when there were no changes.\n */\n startWith(null),\n // ensure query results are up-to-date.\n mergeMap(() => _ensureEqual(this as any)),\n // use the current result set, written by _ensureEqual().\n map(() => this._result),\n // do not run stuff above for each new subscriber, only once.\n shareReplay(RXJS_SHARE_REPLAY_DEFAULTS),\n // do not proceed if result set has not changed.\n distinctUntilChanged((prev, curr) => {\n return Boolean(prev && prev.time === ensureNotFalsy(curr).time);\n }),\n filter(result => !!result),\n /**\n * Map the result set to a single RxDocument or an array,\n * depending on query type\n */\n map((result) => {\n const useResult = ensureNotFalsy(result);\n if (this.op === 'count') {\n return useResult.count;\n } else if (this.op === 'findOne') {\n // findOne()-queries emit RxDocument or null\n return useResult.docs.length === 0 ? null : useResult.docs[0];\n } else if (this.op === 'findByIds') {\n return useResult.docsMap;\n } else {\n // find()-queries emit RxDocument[]\n // Flat copy the array, so it won't matter if the user modifies it.\n return useResult.docs.slice(0);\n }\n })\n );\n\n this._$ = merge(\n results$,\n /**\n * Also add the refCount$ to the query observable\n * to allow us to count the amount of subscribers.\n */\n this.refCount$.pipe(\n filter(() => false)\n )\n );\n }\n return this._$ as any;\n }\n\n\n // stores the changeEvent-number of the last handled change-event\n public _latestChangeEvent: -1 | number = -1;\n\n // time stamps on when the last full exec over the database has run\n // used to properly handle events that happen while the find-query is running\n public _lastExecStart: number = 0;\n public _lastExecEnd: number = 0;\n\n // Fields used for the Limit Buffer when enabled:\n public _limitBufferSize: number | null = null;\n public _limitBufferResults: RxDocumentData[] | null = null;\n\n // Fields used for the persistent query cache when enabled:\n private PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS = 1_000;\n public _persistedQueryCacheResult?: string[] | string = undefined;\n public _persistedQueryCacheLoaded?: Promise;\n public _queryCacheLimit?: number;\n public _queryCacheBackend?: QueryCacheBackend;\n\n /**\n * ensures that the exec-runs\n * are not run in parallel\n */\n public _ensureEqualQueue: Promise = PROMISE_RESOLVE_FALSE;\n\n /**\n * Returns an observable that emits the results\n * This should behave like an rxjs-BehaviorSubject which means:\n * - Emit the current result-set on subscribe\n * - Emit the new result-set when an RxChangeEvent comes in\n * - Do not emit anything before the first result-set was created (no null)\n */\n public _$?: Observable;\n\n /**\n * set the new result-data as result-docs of the query\n * @param newResultData json-docs that were received from the storage\n */\n _setResultData(newResultData: RxDocumentData[] | number | Map>): void {\n if (typeof newResultData === 'number') {\n this._result = {\n docsData: [],\n docsKeys: [],\n docsMap: new Map(),\n docsDataMap: new Map(),\n count: newResultData,\n docs: [],\n time: now()\n };\n return;\n } else if (newResultData instanceof Map) {\n newResultData = Array.from((newResultData as Map>).values());\n }\n\n const docsDataMap = new Map();\n const docsMap = new Map();\n\n\n const docs = newResultData.map(docData => this.collection._docCache.getCachedRxDocument(docData));\n\n /**\n * Instead of using the newResultData in the result cache,\n * we directly use the objects that are stored in the RxDocument\n * to ensure we do not store the same data twice and fill up the memory.\n */\n const docsKeys: string[] = [];\n const docsData = docs.map(doc => {\n docsDataMap.set(doc.primary, doc._data);\n docsMap.set(doc.primary, doc);\n docsKeys.push(doc.primary);\n return doc._data;\n });\n\n this._result = {\n docsData,\n docsKeys,\n docsMap,\n docsDataMap,\n count: docsData.length,\n docs,\n time: now()\n };\n }\n\n /**\n * executes the query on the database\n * @return results-array with document-data\n */\n async _execOverDatabase(): Promise[] | number> {\n this._execOverDatabaseCount = this._execOverDatabaseCount + 1;\n this._lastExecStart = now();\n\n if (this.op === 'count') {\n // if we have a persisted query cache result, use the result\n if (this._persistedQueryCacheResult) {\n // TODO: correct this number, but how?\n return Number(this._persistedQueryCacheResult);\n }\n\n const preparedQuery = this.getPreparedQuery();\n const result = await this.collection.storageInstance.count(preparedQuery);\n if (result.mode === 'slow' && !this.collection.database.allowSlowCount) {\n throw newRxError('QU14', {\n collection: this.collection,\n queryObj: this.mangoQuery\n });\n } else {\n return result.count;\n }\n }\n\n if (this.op === 'findByIds') {\n const ids: string[] = ensureNotFalsy(this.mangoQuery.selector as any)[this.collection.schema.primaryPath].$in;\n const ret = new Map>();\n const mustBeQueried: string[] = [];\n // first try to fill from docCache\n ids.forEach(id => {\n const docData = this.collection._docCache.getLatestDocumentDataIfExists(id);\n if (docData) {\n if (!docData._deleted) {\n const doc = this.collection._docCache.getCachedRxDocument(docData);\n ret.set(id, doc);\n }\n } else {\n mustBeQueried.push(id);\n }\n });\n // everything which was not in docCache must be fetched from the storage\n if (mustBeQueried.length > 0) {\n const docs = await this.collection.storageInstance.findDocumentsById(mustBeQueried, false);\n Object.values(docs).forEach(docData => {\n const doc = this.collection._docCache.getCachedRxDocument(docData);\n ret.set(doc.primary, doc);\n });\n }\n return ret as any;\n }\n\n const docsPromise = queryCollection(this as any);\n return await docsPromise.then(docs => {\n this._lastExecEnd = now();\n return docs;\n });\n }\n\n /**\n * Execute the query\n * To have an easier implementations,\n * just subscribe and use the first result\n */\n public exec(throwIfMissing: true): Promise>;\n public exec(): Promise;\n public async exec(throwIfMissing?: boolean): Promise {\n if (throwIfMissing && this.op !== 'findOne') {\n throw newRxError('QU9', {\n collection: this.collection.name,\n query: this.mangoQuery,\n op: this.op\n });\n }\n\n /**\n * run _ensureEqual() here,\n * this will make sure that errors in the query which throw inside of the RxStorage,\n * will be thrown at this execution context and not in the background.\n */\n await _ensureEqual(this);\n const result = await firstValueFrom(this.$);\n if (!result && throwIfMissing) {\n throw newRxError('QU10', {\n collection: this.collection.name,\n query: this.mangoQuery,\n op: this.op\n });\n } else {\n return result;\n }\n }\n\n /**\n * cached call to get the queryMatcher\n * @overwrites itself with the actual value\n */\n get queryMatcher(): QueryMatcher> {\n const schema = this.collection.schema.jsonSchema;\n const normalizedQuery = normalizeMangoQuery(\n this.collection.schema.jsonSchema,\n this.mangoQuery\n );\n return overwriteGetterForCaching(\n this,\n 'queryMatcher',\n getQueryMatcher(\n schema,\n normalizedQuery\n ) as any\n );\n }\n\n /**\n * returns a string that is used for equal-comparisons\n * @overwrites itself with the actual value\n */\n toString(): string {\n const stringObj = sortObject({\n op: this.op,\n query: this.mangoQuery,\n other: this.other\n }, true);\n const value = JSON.stringify(stringObj, stringifyFilter);\n this.toString = () => value;\n return value;\n }\n\n /**\n * returns the prepared query\n * which can be sent to the storage instance to query for documents.\n * @overwrites itself with the actual value.\n */\n getPreparedQuery(): PreparedQuery {\n const hookInput = {\n rxQuery: this,\n // can be mutated by the hooks so we have to deep clone first.\n mangoQuery: normalizeMangoQuery(\n this.collection.schema.jsonSchema,\n this.mangoQuery\n )\n };\n\n if (this._limitBufferSize !== null && hookInput.mangoQuery.limit) {\n hookInput.mangoQuery.limit = hookInput.mangoQuery.limit + this._limitBufferSize;\n }\n\n runPluginHooks('prePrepareQuery', hookInput);\n\n const value = this.collection.database.storage.statics.prepareQuery(\n this.collection.schema.jsonSchema,\n hookInput.mangoQuery\n );\n\n this.getPreparedQuery = () => value;\n return value;\n }\n\n /**\n * returns true if the document matches the query,\n * does not use the 'skip' and 'limit'\n */\n doesDocumentDataMatch(docData: RxDocType | any): boolean {\n // if doc is deleted, it cannot match\n if (docData._deleted) {\n return false;\n }\n\n return this.queryMatcher(docData);\n }\n\n /**\n * deletes all found documents\n * @return promise with deleted documents\n */\n remove(): Promise {\n return this\n .exec()\n .then(docs => {\n if (Array.isArray(docs)) {\n // TODO use a bulk operation instead of running .remove() on each document\n return Promise.all(docs.map(doc => doc.remove()));\n } else {\n return (docs as any).remove();\n }\n });\n }\n\n /**\n * helper function to transform RxQueryBase to RxQuery type\n */\n get asRxQuery(): RxQuery {\n return this as any;\n }\n\n /**\n * updates all found documents\n * @overwritten by plugin (optional)\n */\n update(_updateObj: any): Promise {\n throw pluginMissing('update');\n }\n\n // we only set some methods of query-builder here\n // because the others depend on these\n where(_queryObj: MangoQuerySelector | keyof RxDocType | string): RxQuery {\n throw pluginMissing('query-builder');\n }\n sort(_params: string | MangoQuerySortPart): RxQuery {\n throw pluginMissing('query-builder');\n }\n skip(_amount: number | null): RxQuery {\n throw pluginMissing('query-builder');\n }\n limit(_amount: number | null): RxQuery {\n throw pluginMissing('query-builder');\n }\n\n enableLimitBuffer(bufferSize: number) {\n if (this._limitBufferSize !== null) {\n // Limit buffer has already been enabled, do nothing:\n return this;\n }\n if (this._lastExecStart !== 0) {\n console.error('Can\\'t use limit buffer if query has already executed');\n return this;\n }\n if (this.mangoQuery.skip || !this.mangoQuery.limit) {\n console.error('Right now, limit buffer only works on non-skip, limit queries.');\n return this;\n }\n this._limitBufferSize = bufferSize;\n return this;\n }\n\n enablePersistentQueryCache(backend: QueryCacheBackend, limit: number = this.PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS) {\n this._queryCacheBackend = backend;\n this._queryCacheLimit = limit;\n\n this._persistedQueryCacheLoaded = this._loadPersistedResultsIfTheyExist();\n return this;\n }\n\n private async _loadPersistedResultsIfTheyExist() {\n if (!this._queryCacheBackend) {\n return;\n }\n\n if (this._persistedQueryCacheResult) {\n return;\n }\n\n const key = _queryKey(this);\n const value = await this._queryCacheBackend.getItem(`qc:${key}`);\n\n this._persistedQueryCacheResult = value ?? undefined;\n\n // if this is a regular query, also load documents into cache\n if (Array.isArray(value) && value.length > 0) {\n this._persistedQueryCacheResult = value;\n\n /**\n * TODO: try to move this to where the query execution happens, maybe lazy loading is even better, but if\n * query definition and data loading is almost happening at the same time, this is the more simple\n * implementation.\n */\n const documents = await this.collection.storageInstance\n .findDocumentsById(value, false);\n for (const document of Object.values(documents)) {\n this.collection._docCache.getCachedRxDocument(document);\n }\n }\n }\n}\n\nexport function _getDefaultQuery(): MangoQuery {\n return {\n selector: {}\n };\n}\n\n/**\n * run this query through the QueryCache\n */\nexport function tunnelQueryCache(\n rxQuery: RxQueryBase\n): RxQuery {\n return rxQuery.collection._queryCache.getByQuery(rxQuery as any);\n}\n\nexport function createRxQuery(\n op: RxQueryOP,\n queryObj: MangoQuery,\n collection: RxCollection,\n other?: any\n) {\n runPluginHooks('preCreateRxQuery', {\n op,\n queryObj,\n collection,\n other\n });\n\n let ret = new RxQueryBase(op, queryObj, collection, other);\n\n // ensure when created with same params, only one is created\n ret = tunnelQueryCache(ret);\n // TODO: clear persistent query cache as well\n triggerCacheReplacement(collection);\n\n return ret;\n}\n\n/**\n * Check if the current results-state is in sync with the database\n * which means that no writes event happened since the last run.\n * @return false if not which means it should re-execute\n */\nfunction _isResultsInSync(rxQuery: RxQueryBase): boolean {\n const currentLatestEventNumber = rxQuery.asRxQuery.collection._changeEventBuffer.counter;\n return rxQuery._latestChangeEvent >= currentLatestEventNumber;\n}\n\n/**\n * wraps __ensureEqual()\n * to ensure it does not run in parallel\n * @return true if it has changed, false if not\n */\nfunction _ensureEqual(rxQuery: RxQueryBase): Promise {\n // Optimisation shortcut\n if (\n rxQuery.collection.database.destroyed ||\n _isResultsInSync(rxQuery)\n ) {\n return PROMISE_RESOLVE_FALSE;\n }\n\n rxQuery._ensureEqualQueue = rxQuery._ensureEqualQueue\n .then(() => __ensureEqual(rxQuery));\n return rxQuery._ensureEqualQueue;\n}\n\n/**\n * ensures that the results of this query is equal to the results which a query over the database would give\n * @return true if results have changed\n */\nasync function __ensureEqual(rxQuery: RxQueryBase): Promise {\n await rxQuery._persistedQueryCacheLoaded;\n\n rxQuery._lastEnsureEqual = now();\n\n /**\n * Optimisation shortcuts\n */\n if (\n // db is closed\n rxQuery.collection.database.destroyed ||\n // nothing happened since last run\n _isResultsInSync(rxQuery)\n ) {\n return PROMISE_RESOLVE_FALSE;\n }\n\n let ret = false;\n let mustReExec = false; // if this becomes true, a whole execution over the database is made\n if (rxQuery._latestChangeEvent === -1) {\n // have not executed yet -> must run\n mustReExec = true;\n }\n\n /**\n * try to use EventReduce to calculate the new results\n */\n if (!mustReExec) {\n const missedChangeEvents = rxQuery.asRxQuery.collection._changeEventBuffer.getFrom(rxQuery._latestChangeEvent + 1);\n if (missedChangeEvents === null) {\n // changeEventBuffer is of bounds -> we must re-execute over the database\n mustReExec = true;\n } else {\n rxQuery._latestChangeEvent = rxQuery.asRxQuery.collection._changeEventBuffer.counter;\n\n const runChangeEvents: RxChangeEvent[] = rxQuery.asRxQuery.collection\n ._changeEventBuffer\n .reduceByLastOfDoc(missedChangeEvents);\n\n if (rxQuery._limitBufferResults !== null) {\n // Check if any item in our limit buffer was modified by a change event\n for (const cE of runChangeEvents) {\n if (rxQuery._limitBufferResults.find((doc) => doc[rxQuery.collection.schema.primaryPath] === cE.documentId)) {\n // If so, the limit buffer is potential invalid -- let's just blow it up\n // TODO: could we instead update the documents in the limit buffer?\n rxQuery._limitBufferResults = null;\n break;\n }\n }\n }\n\n if (rxQuery.op === 'count') {\n // 'count' query\n const previousCount = ensureNotFalsy(rxQuery._result).count;\n let newCount = previousCount;\n runChangeEvents.forEach(cE => {\n const didMatchBefore = cE.previousDocumentData && rxQuery.doesDocumentDataMatch(cE.previousDocumentData);\n const doesMatchNow = rxQuery.doesDocumentDataMatch(cE.documentData);\n\n if (!didMatchBefore && doesMatchNow) {\n newCount++;\n }\n if (didMatchBefore && !doesMatchNow) {\n newCount--;\n }\n });\n if (newCount !== previousCount) {\n ret = true; // true because results changed\n rxQuery._setResultData(newCount as any);\n await _updatePersistentQueryCache(rxQuery);\n }\n } else {\n // 'find' or 'findOne' query\n const eventReduceResult = calculateNewResults(\n rxQuery as any,\n runChangeEvents\n );\n if (eventReduceResult.runFullQueryAgain) {\n // could not calculate the new results, execute must be done\n mustReExec = true;\n } else if (eventReduceResult.changed) {\n // we got the new results, we do not have to re-execute, mustReExec stays false\n ret = true; // true because results changed\n rxQuery._setResultData(eventReduceResult.newResults as any);\n await _updatePersistentQueryCache(rxQuery);\n }\n }\n }\n }\n\n // oh, no we have to re-execute the whole query over the database\n if (mustReExec) {\n // counter can change while _execOverDatabase() is running so we save it here\n const latestAfter: number = (rxQuery as any).collection._changeEventBuffer.counter;\n return rxQuery._execOverDatabase()\n .then((newResultData) => {\n rxQuery._latestChangeEvent = latestAfter;\n\n // A count query needs a different has-changed check.\n if (typeof newResultData === 'number') {\n if (\n !rxQuery._result ||\n newResultData !== rxQuery._result.count\n ) {\n ret = true;\n rxQuery._setResultData(newResultData as any);\n }\n return ret;\n }\n if (\n !rxQuery._result ||\n !areRxDocumentArraysEqual(\n rxQuery.collection.schema.primaryPath,\n newResultData,\n rxQuery._result.docsData\n )\n ) {\n ret = true; // true because results changed\n rxQuery._setResultData(newResultData as any);\n }\n return ret;\n })\n .then(async (returnValue) => {\n await _updatePersistentQueryCache(rxQuery);\n return returnValue;\n });\n }\n\n return ret; // true if results have changed\n}\n\nfunction _queryKey(rxQuery: RxQueryBase) {\n return String(murmurHash(rxQuery.toString(), 42));\n}\n\nasync function _updatePersistentQueryCache(rxQuery: RxQueryBase) {\n if (!rxQuery._queryCacheBackend) {\n return;\n }\n\n const backend = rxQuery._queryCacheBackend;\n\n const isCount = rxQuery._result?.docs.length === 0 && rxQuery._result.count > 0;\n\n const key = _queryKey(rxQuery);\n const value = isCount\n ? rxQuery._result?.count?.toString() ?? '0'\n : rxQuery._result?.docsKeys ?? [];\n\n // update _persistedQueryCacheResult\n rxQuery._persistedQueryCacheResult = value;\n\n // persist query cache\n const lwt = rxQuery._result?.time ?? 0;\n await backend.setItem(`qc:${String(key)}`, value);\n await backend.setItem(`qc:${String(key)}:lwt`, lwt.toString());\n}\n\n// Refactored out of `queryCollection`: modifies the docResults array to fill it with data\nasync function _queryCollectionByIds(rxQuery: RxQuery | RxQueryBase, docResults: RxDocumentData[], docIds: string[]) {\n const collection = rxQuery.collection;\n docIds = docIds.filter(docId => {\n // first try to fill from docCache\n const docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId);\n if (docData) {\n if (!docData._deleted) {\n docResults.push(docData);\n }\n return false;\n } else {\n return true;\n }\n });\n\n // otherwise get from storage\n if (docIds.length > 0) {\n const docsMap = await collection.storageInstance.findDocumentsById(docIds, false);\n Object.values(docsMap).forEach(docData => {\n docResults.push(docData);\n });\n }\n}\n\n/**\n * Runs the query over the storage instance\n * of the collection.\n * Does some optimizations to ensure findById is used\n * when specific queries are used.\n */\nexport async function queryCollection(\n rxQuery: RxQuery | RxQueryBase\n): Promise[]> {\n await rxQuery._persistedQueryCacheLoaded;\n\n let docs: RxDocumentData[] = [];\n const collection = rxQuery.collection;\n\n /**\n * Optimizations shortcut.\n * If query is find-one-document-by-id,\n * then we do not have to use the slow query() method\n * but instead can use findDocumentsById()\n */\n if (rxQuery.isFindOneByIdQuery) {\n if (Array.isArray(rxQuery.isFindOneByIdQuery)) {\n await _queryCollectionByIds(rxQuery, docs, rxQuery.isFindOneByIdQuery);\n } else {\n const docId = rxQuery.isFindOneByIdQuery;\n\n // first try to fill from docCache\n let docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId);\n if (!docData) {\n // otherwise get from storage\n const docsMap = await collection.storageInstance.findDocumentsById([docId], false);\n if (docsMap.hasOwnProperty(docId)) {\n docData = docsMap[docId];\n }\n }\n if (docData && !docData._deleted) {\n docs.push(docData);\n }\n }\n return docs;\n }\n\n if (((rxQuery.mangoQuery.limit && !rxQuery.mangoQuery.skip) || !rxQuery.mangoQuery.limit)\n && rxQuery._queryCacheBackend\n && Array.isArray(rxQuery._persistedQueryCacheResult)) {\n const persistedQueryCacheIds = new Set(rxQuery._persistedQueryCacheResult);\n const primaryPath = rxQuery.collection.schema.primaryPath;\n const queryKeyValue = _queryKey(rxQuery as RxQueryBase);\n const lwt = (await rxQuery._queryCacheBackend.getItem(`qc:${queryKeyValue}:lwt`)) as string ?? 0;\n const previousDocCount = rxQuery._result?.docsKeys?.length ?? persistedQueryCacheIds.size;\n\n // query all docs updated > last persisted, limit to an arbitrary 1_000_000 (10x of what we consider our largest library)\n const {documents: changedDocs} = await collection.storageInstance.getChangedDocumentsSince(\n 1_000_000,\n {id: '', lwt }\n );\n for (const changedDoc of changedDocs) {\n /*\n * no need to fetch again, we already got the doc from the list of changed docs, and therefore we filter\n * deleted docs as well\n */\n persistedQueryCacheIds.delete(changedDoc[primaryPath] as string);\n\n // ignore deleted docs or docs that do not match the query\n if (!rxQuery.doesDocumentDataMatch(changedDoc)) {\n continue;\n }\n\n // doc should be in result\n docs.push(changedDoc);\n }\n\n // If doc count does not match the number of docs previously returned by the query\n // 1. try to pull data from the limit buffer\n // 2. re-query data\n // TODO: How can I utilize the limitBuffer?\n const newDocCount = docs.length + persistedQueryCacheIds.size;\n if (newDocCount === previousDocCount) {\n // fetch remaining doc ids and add to result\n await _queryCollectionByIds(rxQuery, docs, Array.from(persistedQueryCacheIds));\n return docs;\n }\n }\n\n const preparedQuery = rxQuery.getPreparedQuery();\n const queryResult = await collection.storageInstance.query(preparedQuery);\n if (rxQuery._limitBufferSize !== null && rxQuery.mangoQuery.limit && queryResult.documents.length > rxQuery.mangoQuery.limit) {\n // If there are more than query.limit results, we pull out our buffer items from the\n // last rxQuery._limitBufferSize items of the results.\n rxQuery._limitBufferResults = queryResult.documents.splice(rxQuery.mangoQuery.limit);\n }\n docs = queryResult.documents;\n\n return docs;\n\n}\n\n/**\n * Returns true if the given query\n * selects exactly one document by its id.\n * Used to optimize performance because these kind of\n * queries do not have to run over an index and can use get-by-id instead.\n * Returns false if no query of that kind.\n * Returns the document id otherwise.\n */\nexport function isFindOneByIdQuery(\n primaryPath: string,\n query: MangoQuery\n): false | string | string[] {\n // must have exactly one operator which must be $eq || $in\n if (\n !query.skip &&\n query.selector &&\n Object.keys(query.selector).length === 1 &&\n query.selector[primaryPath]\n ) {\n const value: any = query.selector[primaryPath];\n if (typeof value === 'string') {\n return value;\n } else if (\n Object.keys(value).length === 1 &&\n typeof value.$eq === 'string'\n ) {\n return value.$eq;\n }\n\n // same with $in string arrays\n if (\n Object.keys(value).length === 1 &&\n Array.isArray(value.$eq) &&\n // must only contain strings\n !(value.$eq as any[]).find(r => typeof r !== 'string')\n ) {\n return value.$eq;\n }\n }\n return false;\n}\n\nexport function isRxQuery(obj: any): boolean {\n return obj instanceof RxQueryBase;\n}\n"],"mappings":"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"} \ No newline at end of file diff --git a/dist/lib/plugins/storage-memory/memory-types.js.map b/dist/lib/plugins/storage-memory/memory-types.js.map index 2494b97ef9f..d1e66ced723 100644 --- a/dist/lib/plugins/storage-memory/memory-types.js.map +++ b/dist/lib/plugins/storage-memory/memory-types.js.map @@ -1 +1 @@ -{"version":3,"file":"memory-types.js","names":[],"sources":["../../../../src/plugins/storage-memory/memory-types.ts"],"sourcesContent":["import { Subject } from 'rxjs';\nimport type {\n DefaultPreparedQuery,\n EventBulk,\n RxAttachmentWriteData,\n RxConflictResultionTask,\n RxDocumentData,\n RxStorage,\n RxStorageChangeEvent,\n RxStorageDefaultCheckpoint\n} from '../../types';\n\nexport type RxStorageMemorySettings = {};\nexport type RxStorageMemoryInstanceCreationOptions = {};\nexport type RxStorageMemory = RxStorage, RxStorageMemoryInstanceCreationOptions> & {\n /**\n * State by collectionKey\n */\n collectionStates: Map>;\n};\n\nexport type MemoryStorageInternalsByIndex = {\n index: string[];\n docsWithIndex: DocWithIndexString[];\n getIndexableString: (docData: RxDocumentData) => string;\n};\n\n/**\n * The internals are shared between multiple storage instances\n * that have been created with the same [databaseName+collectionName] combination.\n */\nexport type MemoryStorageInternals = {\n /**\n * We re-use the memory state when multiple instances\n * are created with the same params.\n * If refCount becomes 0, we can delete the state.\n */\n refCount: number;\n /**\n * If this becomes true,\n * it means that an instance has called remove()\n * so all other instances should also not work anymore.\n */\n removed: boolean;\n documents: Map>;\n /**\n * Attachments data, indexed by a combined string\n * consisting of [documentId + '||' + attachmentId]\n */\n attachments: Map;\n byIndex: {\n /**\n * Because RxDB requires a deterministic sorting\n * on all indexes, we can be sure that the composed index key\n * of each document is unique, because it contains the primaryKey\n * as last index part.\n * So we do not have to store the index-position when we want to do fast\n * writes. Instead we can do a binary search over the existing array\n * because RxDB also knows the previous state of the document when we do a bulkWrite().\n */\n [indexName: string]: MemoryStorageInternalsByIndex;\n };\n\n /**\n * To easier test the conflict resolution,\n * the memory storage exposes the conflict resolution task subject\n * so that we can inject own tasks during tests.\n */\n conflictResultionTasks$: Subject>;\n changes$: Subject>, RxStorageDefaultCheckpoint>>;\n};\n\nexport type DocWithIndexString = {\n id: string;\n doc: RxDocumentData;\n indexString: string;\n};\n\nexport type MemoryPreparedQuery = DefaultPreparedQuery;\n"],"mappings":""} \ No newline at end of file +{"version":3,"file":"memory-types.js","names":[],"sources":["../../../../src/plugins/storage-memory/memory-types.ts"],"sourcesContent":["import { Subject } from 'rxjs';\nimport type {\n DefaultPreparedQuery,\n EventBulk,\n RxAttachmentWriteData,\n RxConflictResultionTask,\n RxDocumentData,\n RxStorage,\n RxStorageChangeEvent,\n RxStorageDefaultCheckpoint\n} from '../../types';\n\nexport type RxStorageMemorySettings = {};\nexport type RxStorageMemoryInstanceCreationOptions = {};\nexport type RxStorageMemory = RxStorage, RxStorageMemoryInstanceCreationOptions> & {\n /**\n * State by collectionKey\n */\n collectionStates: Map>;\n};\n\nexport type MemoryStorageInternalsByIndex = {\n index: string[];\n docsWithIndex: DocWithIndexString[];\n getIndexableString: (docData: RxDocumentData) => string;\n};\n\n/**\n * The internals are shared between multiple storage instances\n * that have been created with the same [databaseName+collectionName] combination.\n */\nexport type MemoryStorageInternals = {\n /**\n * We reuse the memory state when multiple instances\n * are created with the same params.\n * If refCount becomes 0, we can delete the state.\n */\n refCount: number;\n /**\n * If this becomes true,\n * it means that an instance has called remove()\n * so all other instances should also not work anymore.\n */\n removed: boolean;\n documents: Map>;\n /**\n * Attachments data, indexed by a combined string\n * consisting of [documentId + '||' + attachmentId]\n */\n attachments: Map;\n byIndex: {\n /**\n * Because RxDB requires a deterministic sorting\n * on all indexes, we can be sure that the composed index key\n * of each document is unique, because it contains the primaryKey\n * as last index part.\n * So we do not have to store the index-position when we want to do fast\n * writes. Instead we can do a binary search over the existing array\n * because RxDB also knows the previous state of the document when we do a bulkWrite().\n */\n [indexName: string]: MemoryStorageInternalsByIndex;\n };\n\n /**\n * To easier test the conflict resolution,\n * the memory storage exposes the conflict resolution task subject\n * so that we can inject own tasks during tests.\n */\n conflictResultionTasks$: Subject>;\n changes$: Subject>, RxStorageDefaultCheckpoint>>;\n};\n\nexport type DocWithIndexString = {\n id: string;\n doc: RxDocumentData;\n indexString: string;\n};\n\nexport type MemoryPreparedQuery = DefaultPreparedQuery;\n"],"mappings":""} \ No newline at end of file diff --git a/dist/lib/plugins/utils/utils-object.js b/dist/lib/plugins/utils/utils-object.js index 585dddf0720..32b318318cd 100644 --- a/dist/lib/plugins/utils/utils-object.js +++ b/dist/lib/plugins/utils/utils-object.js @@ -29,7 +29,7 @@ function deepFreeze(o) { * RxDB normally uses the 'dot-prop' npm module. * But when performance is really relevant, this is not fast enough. * Instead we use a monad that can prepare some stuff up front - * and we can re-use the generated function. + * and we can reuse the generated function. */ function objectPathMonad(objectPath) { diff --git a/dist/lib/plugins/utils/utils-object.js.map b/dist/lib/plugins/utils/utils-object.js.map index 8c5eaf21075..066f2bf5a92 100644 --- a/dist/lib/plugins/utils/utils-object.js.map +++ b/dist/lib/plugins/utils/utils-object.js.map @@ -1 +1 @@ -{"version":3,"file":"utils-object.js","names":["deepFreeze","o","Object","freeze","getOwnPropertyNames","forEach","prop","hasOwnProperty","isFrozen","objectPathMonad","objectPath","split","splitLength","length","obj","currentVal","i","subPath","getFromObjectOrThrow","key","val","Error","flattenObject","ob","toReturn","flatObject","x","flatClone","assign","firstPropertyNameOfObject","keys","firstPropertyValueOfObject","sortObject","noArraySort","Array","isArray","sort","a","b","localeCompare","map","RegExp","out","deepClone","src","ret","dest","clone","exports","overwriteGetterForCaching","getterName","value","defineProperty","get","stringifyFilter","toString"],"sources":["../../../../src/plugins/utils/utils-object.ts"],"sourcesContent":["import type {\n DeepReadonlyObject\n} from '../../types';\n\nexport function deepFreeze(o: T): T {\n Object.freeze(o);\n Object.getOwnPropertyNames(o).forEach(function (prop) {\n if (\n (o as any).hasOwnProperty(prop)\n &&\n (o as any)[prop] !== null\n &&\n (\n typeof (o as any)[prop] === 'object'\n ||\n typeof (o as any)[prop] === 'function'\n )\n &&\n !Object.isFrozen((o as any)[prop])\n ) {\n deepFreeze((o as any)[prop]);\n }\n });\n return o;\n}\n\n\n\n/**\n * To get specific nested path values from objects,\n * RxDB normally uses the 'dot-prop' npm module.\n * But when performance is really relevant, this is not fast enough.\n * Instead we use a monad that can prepare some stuff up front\n * and we can re-use the generated function.\n */\nexport type ObjectPathMonadFunction = (obj: T) => R;\nexport function objectPathMonad(objectPath: string): ObjectPathMonadFunction {\n const split = objectPath.split('.');\n\n // reuse this variable for better performance.\n const splitLength = split.length;\n\n /**\n * Performance shortcut,\n * if no nested path is used,\n * directly return the field of the object.\n */\n if (splitLength === 1) {\n return (obj: T) => (obj as any)[objectPath];\n }\n\n\n return (obj: T) => {\n let currentVal: any = obj;\n for (let i = 0; i < splitLength; ++i) {\n const subPath = split[i];\n currentVal = currentVal[subPath];\n if (typeof currentVal === 'undefined') {\n return currentVal;\n }\n }\n return currentVal;\n };\n}\n\n\nexport function getFromObjectOrThrow(\n obj: { [k: string]: V; },\n key: string\n): V {\n const val = obj[key];\n if (!val) {\n throw new Error('missing value from object ' + key);\n }\n return val;\n}\n\n/**\n * returns a flattened object\n * @link https://gist.github.com/penguinboy/762197\n */\nexport function flattenObject(ob: any) {\n const toReturn: any = {};\n\n for (const i in ob) {\n if (!ob.hasOwnProperty(i)) continue;\n\n if ((typeof ob[i]) === 'object') {\n const flatObject = flattenObject(ob[i]);\n for (const x in flatObject) {\n if (!flatObject.hasOwnProperty(x)) continue;\n\n toReturn[i + '.' + x] = flatObject[x];\n }\n } else {\n toReturn[i] = ob[i];\n }\n }\n return toReturn;\n}\n\n\n/**\n * does a flat copy on the objects,\n * is about 3 times faster then using deepClone\n * @link https://jsperf.com/object-rest-spread-vs-clone/2\n */\nexport function flatClone(obj: T | DeepReadonlyObject | Readonly): T {\n return Object.assign({}, obj) as any;\n}\n\n/**\n * @link https://stackoverflow.com/a/11509718/3443137\n */\nexport function firstPropertyNameOfObject(obj: any): string {\n return Object.keys(obj)[0];\n}\nexport function firstPropertyValueOfObject(obj: { [k: string]: T; }): T {\n const key = Object.keys(obj)[0];\n return obj[key];\n}\n\n\n/**\n * deep-sort an object so its attributes are in lexical order.\n * Also sorts the arrays inside of the object if no-array-sort not set\n */\nexport function sortObject(obj: any, noArraySort = false): any {\n if (!obj) return obj; // do not sort null, false or undefined\n\n // array\n if (!noArraySort && Array.isArray(obj)) {\n return obj\n .sort((a, b) => {\n if (typeof a === 'string' && typeof b === 'string')\n return a.localeCompare(b);\n\n if (typeof a === 'object') return 1;\n else return -1;\n })\n .map(i => sortObject(i, noArraySort));\n }\n\n // object\n // array is also of type object\n if (typeof obj === 'object' && !Array.isArray(obj)) {\n if (obj instanceof RegExp) {\n return obj;\n }\n\n const out: any = {};\n Object.keys(obj)\n .sort((a, b) => a.localeCompare(b))\n .forEach(key => {\n out[key] = sortObject(obj[key], noArraySort);\n });\n return out;\n }\n\n // everything else\n return obj;\n}\n\n\n\n/**\n * Deep clone a plain json object.\n * Does not work with recursive stuff\n * or non-plain-json.\n * IMPORTANT: Performance of this is very important,\n * do not change it without running performance tests!\n *\n * @link https://github.com/zxdong262/deep-copy/blob/master/src/index.ts\n */\nfunction deepClone(src: T | DeepReadonlyObject): T {\n if (!src) {\n return src;\n }\n if (src === null || typeof (src) !== 'object') {\n return src;\n }\n if (Array.isArray(src)) {\n const ret = new Array(src.length);\n let i = ret.length;\n while (i--) {\n ret[i] = deepClone(src[i]);\n }\n return ret as any;\n }\n const dest: any = {};\n // eslint-disable-next-line guard-for-in\n for (const key in src) {\n dest[key] = deepClone(src[key]);\n }\n return dest;\n}\nexport const clone = deepClone;\n\n\n\n/**\n * overwrites the getter with the actual value\n * Mostly used for caching stuff on the first run\n */\nexport function overwriteGetterForCaching(\n obj: any,\n getterName: string,\n value: ValueType\n): ValueType {\n Object.defineProperty(obj, getterName, {\n get: function () {\n return value;\n }\n });\n return value;\n}\n\n\n\n/**\n * used to JSON.stringify() objects that contain a regex\n * @link https://stackoverflow.com/a/33416684 thank you Fabian Jakobs!\n */\nexport function stringifyFilter(key: string, value: any) {\n if (value instanceof RegExp) {\n return value.toString();\n }\n return value;\n}\n"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"utils-object.js","names":["deepFreeze","o","Object","freeze","getOwnPropertyNames","forEach","prop","hasOwnProperty","isFrozen","objectPathMonad","objectPath","split","splitLength","length","obj","currentVal","i","subPath","getFromObjectOrThrow","key","val","Error","flattenObject","ob","toReturn","flatObject","x","flatClone","assign","firstPropertyNameOfObject","keys","firstPropertyValueOfObject","sortObject","noArraySort","Array","isArray","sort","a","b","localeCompare","map","RegExp","out","deepClone","src","ret","dest","clone","exports","overwriteGetterForCaching","getterName","value","defineProperty","get","stringifyFilter","toString"],"sources":["../../../../src/plugins/utils/utils-object.ts"],"sourcesContent":["import type {\n DeepReadonlyObject\n} from '../../types';\n\nexport function deepFreeze(o: T): T {\n Object.freeze(o);\n Object.getOwnPropertyNames(o).forEach(function (prop) {\n if (\n (o as any).hasOwnProperty(prop)\n &&\n (o as any)[prop] !== null\n &&\n (\n typeof (o as any)[prop] === 'object'\n ||\n typeof (o as any)[prop] === 'function'\n )\n &&\n !Object.isFrozen((o as any)[prop])\n ) {\n deepFreeze((o as any)[prop]);\n }\n });\n return o;\n}\n\n\n\n/**\n * To get specific nested path values from objects,\n * RxDB normally uses the 'dot-prop' npm module.\n * But when performance is really relevant, this is not fast enough.\n * Instead we use a monad that can prepare some stuff up front\n * and we can reuse the generated function.\n */\nexport type ObjectPathMonadFunction = (obj: T) => R;\nexport function objectPathMonad(objectPath: string): ObjectPathMonadFunction {\n const split = objectPath.split('.');\n\n // reuse this variable for better performance.\n const splitLength = split.length;\n\n /**\n * Performance shortcut,\n * if no nested path is used,\n * directly return the field of the object.\n */\n if (splitLength === 1) {\n return (obj: T) => (obj as any)[objectPath];\n }\n\n\n return (obj: T) => {\n let currentVal: any = obj;\n for (let i = 0; i < splitLength; ++i) {\n const subPath = split[i];\n currentVal = currentVal[subPath];\n if (typeof currentVal === 'undefined') {\n return currentVal;\n }\n }\n return currentVal;\n };\n}\n\n\nexport function getFromObjectOrThrow(\n obj: { [k: string]: V; },\n key: string\n): V {\n const val = obj[key];\n if (!val) {\n throw new Error('missing value from object ' + key);\n }\n return val;\n}\n\n/**\n * returns a flattened object\n * @link https://gist.github.com/penguinboy/762197\n */\nexport function flattenObject(ob: any) {\n const toReturn: any = {};\n\n for (const i in ob) {\n if (!ob.hasOwnProperty(i)) continue;\n\n if ((typeof ob[i]) === 'object') {\n const flatObject = flattenObject(ob[i]);\n for (const x in flatObject) {\n if (!flatObject.hasOwnProperty(x)) continue;\n\n toReturn[i + '.' + x] = flatObject[x];\n }\n } else {\n toReturn[i] = ob[i];\n }\n }\n return toReturn;\n}\n\n\n/**\n * does a flat copy on the objects,\n * is about 3 times faster then using deepClone\n * @link https://jsperf.com/object-rest-spread-vs-clone/2\n */\nexport function flatClone(obj: T | DeepReadonlyObject | Readonly): T {\n return Object.assign({}, obj) as any;\n}\n\n/**\n * @link https://stackoverflow.com/a/11509718/3443137\n */\nexport function firstPropertyNameOfObject(obj: any): string {\n return Object.keys(obj)[0];\n}\nexport function firstPropertyValueOfObject(obj: { [k: string]: T; }): T {\n const key = Object.keys(obj)[0];\n return obj[key];\n}\n\n\n/**\n * deep-sort an object so its attributes are in lexical order.\n * Also sorts the arrays inside of the object if no-array-sort not set\n */\nexport function sortObject(obj: any, noArraySort = false): any {\n if (!obj) return obj; // do not sort null, false or undefined\n\n // array\n if (!noArraySort && Array.isArray(obj)) {\n return obj\n .sort((a, b) => {\n if (typeof a === 'string' && typeof b === 'string')\n return a.localeCompare(b);\n\n if (typeof a === 'object') return 1;\n else return -1;\n })\n .map(i => sortObject(i, noArraySort));\n }\n\n // object\n // array is also of type object\n if (typeof obj === 'object' && !Array.isArray(obj)) {\n if (obj instanceof RegExp) {\n return obj;\n }\n\n const out: any = {};\n Object.keys(obj)\n .sort((a, b) => a.localeCompare(b))\n .forEach(key => {\n out[key] = sortObject(obj[key], noArraySort);\n });\n return out;\n }\n\n // everything else\n return obj;\n}\n\n\n\n/**\n * Deep clone a plain json object.\n * Does not work with recursive stuff\n * or non-plain-json.\n * IMPORTANT: Performance of this is very important,\n * do not change it without running performance tests!\n *\n * @link https://github.com/zxdong262/deep-copy/blob/master/src/index.ts\n */\nfunction deepClone(src: T | DeepReadonlyObject): T {\n if (!src) {\n return src;\n }\n if (src === null || typeof (src) !== 'object') {\n return src;\n }\n if (Array.isArray(src)) {\n const ret = new Array(src.length);\n let i = ret.length;\n while (i--) {\n ret[i] = deepClone(src[i]);\n }\n return ret as any;\n }\n const dest: any = {};\n // eslint-disable-next-line guard-for-in\n for (const key in src) {\n dest[key] = deepClone(src[key]);\n }\n return dest;\n}\nexport const clone = deepClone;\n\n\n\n/**\n * overwrites the getter with the actual value\n * Mostly used for caching stuff on the first run\n */\nexport function overwriteGetterForCaching(\n obj: any,\n getterName: string,\n value: ValueType\n): ValueType {\n Object.defineProperty(obj, getterName, {\n get: function () {\n return value;\n }\n });\n return value;\n}\n\n\n\n/**\n * used to JSON.stringify() objects that contain a regex\n * @link https://stackoverflow.com/a/33416684 thank you Fabian Jakobs!\n */\nexport function stringifyFilter(key: string, value: any) {\n if (value instanceof RegExp) {\n return value.toString();\n }\n return value;\n}\n"],"mappings":"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"} \ No newline at end of file diff --git a/dist/lib/replication-protocol/upstream.js b/dist/lib/replication-protocol/upstream.js index 52ea2a26bda..fe60b05d8bd 100644 --- a/dist/lib/replication-protocol/upstream.js +++ b/dist/lib/replication-protocol/upstream.js @@ -175,14 +175,14 @@ async function startReplicationUpstream(state) { assumedMasterDoc.metaDocument.isResolvedConflict !== fullDocData._rev && (await state.input.conflictHandler({ realMasterState: assumedMasterDoc.docData, newDocumentState: docData - }, 'upstream-check-if-equal')).isEqual || + }, 'upstream-check-if-equal')).isEqual || ( /** * If the master works with _rev fields, * we use that to check if our current doc state * is different from the assumedMasterDoc. */ - assumedMasterDoc && assumedMasterDoc.docData._rev && (0, _utils.parseRevision)(fullDocData._rev).height === fullDocData._meta[state.input.identifier]) { + assumedMasterDoc && assumedMasterDoc.docData._rev && (0, _utils.parseRevision)(fullDocData._rev).height === fullDocData._meta[state.input.identifier])) { return; } writeRowsToMasterIds.push(docId); diff --git a/dist/lib/replication-protocol/upstream.js.map b/dist/lib/replication-protocol/upstream.js.map index 611b4b3de92..26f0f3e8270 100644 --- a/dist/lib/replication-protocol/upstream.js.map +++ b/dist/lib/replication-protocol/upstream.js.map @@ -1 +1 @@ -{"version":3,"file":"upstream.js","names":["_rxjs","require","_rxStorageHelper","_utils","_checkpoint","_conflicts","_helper","_metaInstance","startReplicationUpstream","state","input","initialCheckpoint","upstream","checkpointDoc","getLastCheckpointDoc","setCheckpoint","replicationHandler","streamQueue","up","then","upstreamInitialSync","processTasks","timer","initialSyncStartTime","openTasks","sub","forkInstance","changeStream","pipe","filter","eventBulk","context","downstreamBulkWriteFlag","subscribe","stats","forkChangeStreamEmit","push","task","time","waitBeforePersist","firstValueFrom","events","canceled","unsubscribe","getValue","checkpointQueue","lastCheckpoint","promises","upResult","getChangedDocumentsSince","pushBatchSize","documents","length","stackCheckpoints","checkpoint","persistToMaster","ensureNotFalsy","resolvedPromises","Promise","all","hadConflicts","find","r","firstSyncDone","next","active","docs","taskWithTime","shift","appendToArray","map","documentData","promise","PROMISE_RESOLVE_FALSE","persistenceQueue","nonPersistedFromMaster","forEach","docData","docId","primaryPath","upDocsById","useCheckpoint","docIds","Object","keys","assumedMasterState","getAssumedMasterState","writeRowsToMaster","writeRowsToMasterIds","writeRowsToMeta","forkStateById","fullDocData","writeDocToDocState","assumedMasterDoc","metaDocument","isResolvedConflict","_rev","conflictHandler","realMasterState","newDocumentState","isEqual","parseRevision","height","_meta","identifier","undefined","getMetaWriteRow","writeRowsArray","values","conflictIds","Set","conflictsById","writeBatches","batchArray","writeBatch","masterWriteResult","masterWrite","conflictDoc","id","add","useWriteRowsToMeta","has","processed","metaInstance","bulkWrite","hadConflictWrites","size","persistToMasterHadConflicts","conflictWriteFork","conflictWriteMeta","entries","writeToMasterRow","resolveConflictError","resolved","resolvedConflicts","output","previous","document","resolvedDoc","persistToMasterConflictWrites","forkWriteResult","useMetaWrites","success","catch","unhandledError","error"],"sources":["../../../src/replication-protocol/upstream.ts"],"sourcesContent":["import { firstValueFrom, filter } from 'rxjs';\nimport { stackCheckpoints } from '../rx-storage-helper';\nimport type {\n BulkWriteRow,\n BulkWriteRowById,\n ById,\n EventBulk,\n RxDocumentData,\n RxReplicationWriteToMasterRow,\n RxStorageChangeEvent,\n RxStorageInstanceReplicationState,\n RxStorageReplicationMeta,\n WithDeleted\n} from '../types';\nimport {\n appendToArray,\n batchArray,\n ensureNotFalsy,\n parseRevision,\n PROMISE_RESOLVE_FALSE\n} from '../plugins/utils';\nimport {\n getLastCheckpointDoc,\n setCheckpoint\n} from './checkpoint';\nimport { resolveConflictError } from './conflicts';\nimport { writeDocToDocState } from './helper';\nimport {\n getAssumedMasterState,\n getMetaWriteRow\n} from './meta-instance';\n\n/**\n * Writes all document changes from the fork to the master.\n * The upstream runs on two modes:\n * - For initial replication, a checkpoint-iteration is used\n * - For ongoing local writes, we just subscribe to the changeStream of the fork.\n * In contrast to the master, the fork can be assumed to never loose connection,\n * so we do not have to prepare for missed out events.\n */\nexport async function startReplicationUpstream(\n state: RxStorageInstanceReplicationState\n) {\n\n if (\n state.input.initialCheckpoint &&\n state.input.initialCheckpoint.upstream\n ) {\n const checkpointDoc = await getLastCheckpointDoc(state, 'up');\n if (!checkpointDoc) {\n await setCheckpoint(\n state,\n 'up',\n state.input.initialCheckpoint.upstream\n );\n }\n }\n\n const replicationHandler = state.input.replicationHandler;\n state.streamQueue.up = state.streamQueue.up.then(() => {\n return upstreamInitialSync().then(() => {\n processTasks();\n });\n });\n\n // used to detect which tasks etc can in it at which order.\n let timer = 0;\n let initialSyncStartTime = -1;\n\n type Task = EventBulk, any>;\n type TaskWithTime = {\n task: Task;\n time: number;\n };\n const openTasks: TaskWithTime[] = [];\n\n\n const sub = state.input.forkInstance.changeStream()\n .pipe(\n filter(eventBulk => eventBulk.context !== state.downstreamBulkWriteFlag)\n ).subscribe(eventBulk => {\n state.stats.up.forkChangeStreamEmit = state.stats.up.forkChangeStreamEmit + 1;\n openTasks.push({\n task: eventBulk,\n time: timer++\n });\n if (state.input.waitBeforePersist) {\n return state.input.waitBeforePersist()\n .then(() => processTasks());\n } else {\n return processTasks();\n }\n });\n firstValueFrom(\n state.events.canceled.pipe(\n filter(canceled => !!canceled)\n )\n ).then(() => sub.unsubscribe());\n\n\n async function upstreamInitialSync() {\n state.stats.up.upstreamInitialSync = state.stats.up.upstreamInitialSync + 1;\n if (state.events.canceled.getValue()) {\n return;\n }\n\n state.checkpointQueue = state.checkpointQueue.then(() => getLastCheckpointDoc(state, 'up'));\n let lastCheckpoint: CheckpointType = await state.checkpointQueue;\n\n const promises: Promise[] = [];\n while (!state.events.canceled.getValue()) {\n initialSyncStartTime = timer++;\n const upResult = await state.input.forkInstance.getChangedDocumentsSince(\n state.input.pushBatchSize,\n lastCheckpoint\n );\n if (upResult.documents.length === 0) {\n break;\n }\n\n lastCheckpoint = stackCheckpoints([lastCheckpoint, upResult.checkpoint]);\n\n promises.push(\n persistToMaster(\n upResult.documents,\n ensureNotFalsy(lastCheckpoint)\n )\n );\n }\n\n /**\n * If we had conflicts during the initial sync,\n * it means that we likely have new writes to the fork\n * and so we have to run the initial sync again to upstream these new writes.\n */\n const resolvedPromises = await Promise.all(promises);\n const hadConflicts = resolvedPromises.find(r => !!r);\n if (hadConflicts) {\n await upstreamInitialSync();\n } else if (\n !state.firstSyncDone.up.getValue() &&\n !state.events.canceled.getValue()\n ) {\n state.firstSyncDone.up.next(true);\n }\n }\n\n\n /**\n * Takes all open tasks an processes them at once.\n */\n function processTasks() {\n if (\n state.events.canceled.getValue() ||\n openTasks.length === 0\n ) {\n state.events.active.up.next(false);\n return;\n }\n state.stats.up.processTasks = state.stats.up.processTasks + 1;\n state.events.active.up.next(true);\n state.streamQueue.up = state.streamQueue.up.then(() => {\n /**\n * Merge/filter all open tasks\n */\n const docs: RxDocumentData[] = [];\n let checkpoint: CheckpointType = {} as any;\n while (openTasks.length > 0) {\n const taskWithTime = ensureNotFalsy(openTasks.shift());\n /**\n * If the task came in before the last time the initial sync fetching\n * has run, we can ignore the task because the initial sync already processed\n * these documents.\n */\n if (taskWithTime.time < initialSyncStartTime) {\n continue;\n }\n appendToArray(\n docs,\n taskWithTime.task.events.map(r => {\n return r.documentData as any;\n })\n );\n checkpoint = stackCheckpoints([checkpoint, taskWithTime.task.checkpoint]);\n }\n\n const promise = docs.length === 0 ? PROMISE_RESOLVE_FALSE : persistToMaster(\n docs,\n checkpoint\n );\n return promise.then(() => {\n if (openTasks.length === 0) {\n state.events.active.up.next(false);\n } else {\n processTasks();\n }\n });\n });\n }\n\n let persistenceQueue: Promise = PROMISE_RESOLVE_FALSE;\n const nonPersistedFromMaster: {\n checkpoint?: CheckpointType;\n docs: ById>;\n } = {\n docs: {}\n };\n\n /**\n * Returns true if had conflicts,\n * false if not.\n */\n function persistToMaster(\n docs: RxDocumentData[],\n checkpoint: CheckpointType\n ): Promise {\n state.stats.up.persistToMaster = state.stats.up.persistToMaster + 1;\n\n /**\n * Add the new docs to the non-persistent list\n */\n docs.forEach(docData => {\n const docId: string = (docData as any)[state.primaryPath];\n nonPersistedFromMaster.docs[docId] = docData;\n });\n nonPersistedFromMaster.checkpoint = checkpoint;\n\n\n persistenceQueue = persistenceQueue.then(async () => {\n if (state.events.canceled.getValue()) {\n return false;\n }\n\n const upDocsById: ById> = nonPersistedFromMaster.docs;\n nonPersistedFromMaster.docs = {};\n const useCheckpoint = nonPersistedFromMaster.checkpoint;\n const docIds = Object.keys(upDocsById);\n if (docIds.length === 0) {\n return false;\n }\n\n const assumedMasterState = await getAssumedMasterState(\n state,\n docIds\n );\n\n const writeRowsToMaster: ById> = {};\n const writeRowsToMasterIds: string[] = [];\n const writeRowsToMeta: BulkWriteRowById = {};\n const forkStateById: ById> = {};\n\n await Promise.all(\n docIds.map(async (docId) => {\n const fullDocData: RxDocumentData = upDocsById[docId];\n forkStateById[docId] = fullDocData;\n const docData: WithDeleted = writeDocToDocState(fullDocData);\n const assumedMasterDoc = assumedMasterState[docId];\n\n /**\n * If the master state is equal to the\n * fork state, we can assume that the document state is already\n * replicated.\n */\n if (\n (\n assumedMasterDoc &&\n // if the isResolvedConflict is correct, we do not have to compare the documents.\n assumedMasterDoc.metaDocument.isResolvedConflict !== fullDocData._rev\n &&\n (await state.input.conflictHandler({\n realMasterState: assumedMasterDoc.docData,\n newDocumentState: docData\n }, 'upstream-check-if-equal')).isEqual\n )\n ||\n /**\n * If the master works with _rev fields,\n * we use that to check if our current doc state\n * is different from the assumedMasterDoc.\n */\n (\n assumedMasterDoc &&\n (assumedMasterDoc.docData as any)._rev &&\n parseRevision(fullDocData._rev).height === fullDocData._meta[state.input.identifier]\n )\n ) {\n return;\n }\n\n writeRowsToMasterIds.push(docId);\n\n writeRowsToMaster[docId] = {\n assumedMasterState: assumedMasterDoc ? assumedMasterDoc.docData : undefined,\n newDocumentState: docData\n };\n writeRowsToMeta[docId] = getMetaWriteRow(\n state,\n docData,\n assumedMasterDoc ? assumedMasterDoc.metaDocument : undefined\n );\n })\n );\n\n if (writeRowsToMasterIds.length === 0) {\n return false;\n }\n\n\n const writeRowsArray = Object.values(writeRowsToMaster);\n const conflictIds: Set = new Set();\n const conflictsById: ById> = {};\n\n /**\n * To always respect the push.batchSize,\n * we have to split the write rows into batches\n * to ensure that replicationHandler.masterWrite() is never\n * called with more documents than what the batchSize limits.\n */\n const writeBatches = batchArray(writeRowsArray, state.input.pushBatchSize);\n await Promise.all(\n writeBatches.map(async (writeBatch) => {\n const masterWriteResult = await replicationHandler.masterWrite(writeBatch);\n masterWriteResult.forEach(conflictDoc => {\n const id = (conflictDoc as any)[state.primaryPath];\n conflictIds.add(id);\n conflictsById[id] = conflictDoc;\n });\n })\n );\n\n\n const useWriteRowsToMeta: BulkWriteRow[] = [];\n\n\n writeRowsToMasterIds.forEach(docId => {\n if (!conflictIds.has(docId)) {\n state.events.processed.up.next(writeRowsToMaster[docId]);\n useWriteRowsToMeta.push(writeRowsToMeta[docId]);\n }\n });\n\n if (useWriteRowsToMeta.length > 0) {\n await state.input.metaInstance.bulkWrite(\n useWriteRowsToMeta,\n 'replication-up-write-meta'\n );\n // TODO what happens when we have conflicts here?\n }\n\n /**\n * Resolve conflicts by writing a new document\n * state to the fork instance and the 'real' master state\n * to the meta instance.\n * Non-409 errors will be detected by resolveConflictError()\n */\n let hadConflictWrites = false;\n if (conflictIds.size > 0) {\n state.stats.up.persistToMasterHadConflicts = state.stats.up.persistToMasterHadConflicts + 1;\n const conflictWriteFork: BulkWriteRow[] = [];\n const conflictWriteMeta: BulkWriteRowById = {};\n await Promise.all(\n Object\n .entries(conflictsById)\n .map(([docId, realMasterState]) => {\n const writeToMasterRow = writeRowsToMaster[docId];\n const input = {\n newDocumentState: writeToMasterRow.newDocumentState,\n assumedMasterState: writeToMasterRow.assumedMasterState,\n realMasterState\n };\n return resolveConflictError(\n state,\n input,\n forkStateById[docId]\n ).then(resolved => {\n if (resolved) {\n state.events.resolvedConflicts.next({\n input,\n output: resolved.output\n });\n conflictWriteFork.push({\n previous: forkStateById[docId],\n document: resolved.resolvedDoc\n });\n const assumedMasterDoc = assumedMasterState[docId];\n conflictWriteMeta[docId] = getMetaWriteRow(\n state,\n ensureNotFalsy(realMasterState),\n assumedMasterDoc ? assumedMasterDoc.metaDocument : undefined,\n resolved.resolvedDoc._rev\n );\n }\n });\n })\n );\n\n if (conflictWriteFork.length > 0) {\n hadConflictWrites = true;\n\n state.stats.up.persistToMasterConflictWrites = state.stats.up.persistToMasterConflictWrites + 1;\n const forkWriteResult = await state.input.forkInstance.bulkWrite(\n conflictWriteFork,\n 'replication-up-write-conflict'\n );\n /**\n * Errors in the forkWriteResult must not be handled\n * because they have been caused by a write to the forkInstance\n * in between which will anyway trigger a new upstream cycle\n * that will then resolved the conflict again.\n */\n const useMetaWrites: BulkWriteRow[] = [];\n Object\n .keys(forkWriteResult.success)\n .forEach((docId) => {\n useMetaWrites.push(\n conflictWriteMeta[docId]\n );\n });\n if (useMetaWrites.length > 0) {\n await state.input.metaInstance.bulkWrite(\n useMetaWrites,\n 'replication-up-write-conflict-meta'\n );\n }\n // TODO what to do with conflicts while writing to the metaInstance?\n }\n }\n\n /**\n * For better performance we do not await checkpoint writes,\n * but to ensure order on parallel checkpoint writes,\n * we have to use a queue.\n */\n state.checkpointQueue = state.checkpointQueue.then(() => setCheckpoint(\n state,\n 'up',\n useCheckpoint\n ));\n\n return hadConflictWrites;\n }).catch(unhandledError => {\n state.events.error.next(unhandledError);\n return false;\n });\n\n return persistenceQueue;\n }\n}\n\n"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"upstream.js","names":["_rxjs","require","_rxStorageHelper","_utils","_checkpoint","_conflicts","_helper","_metaInstance","startReplicationUpstream","state","input","initialCheckpoint","upstream","checkpointDoc","getLastCheckpointDoc","setCheckpoint","replicationHandler","streamQueue","up","then","upstreamInitialSync","processTasks","timer","initialSyncStartTime","openTasks","sub","forkInstance","changeStream","pipe","filter","eventBulk","context","downstreamBulkWriteFlag","subscribe","stats","forkChangeStreamEmit","push","task","time","waitBeforePersist","firstValueFrom","events","canceled","unsubscribe","getValue","checkpointQueue","lastCheckpoint","promises","upResult","getChangedDocumentsSince","pushBatchSize","documents","length","stackCheckpoints","checkpoint","persistToMaster","ensureNotFalsy","resolvedPromises","Promise","all","hadConflicts","find","r","firstSyncDone","next","active","docs","taskWithTime","shift","appendToArray","map","documentData","promise","PROMISE_RESOLVE_FALSE","persistenceQueue","nonPersistedFromMaster","forEach","docData","docId","primaryPath","upDocsById","useCheckpoint","docIds","Object","keys","assumedMasterState","getAssumedMasterState","writeRowsToMaster","writeRowsToMasterIds","writeRowsToMeta","forkStateById","fullDocData","writeDocToDocState","assumedMasterDoc","metaDocument","isResolvedConflict","_rev","conflictHandler","realMasterState","newDocumentState","isEqual","parseRevision","height","_meta","identifier","undefined","getMetaWriteRow","writeRowsArray","values","conflictIds","Set","conflictsById","writeBatches","batchArray","writeBatch","masterWriteResult","masterWrite","conflictDoc","id","add","useWriteRowsToMeta","has","processed","metaInstance","bulkWrite","hadConflictWrites","size","persistToMasterHadConflicts","conflictWriteFork","conflictWriteMeta","entries","writeToMasterRow","resolveConflictError","resolved","resolvedConflicts","output","previous","document","resolvedDoc","persistToMasterConflictWrites","forkWriteResult","useMetaWrites","success","catch","unhandledError","error"],"sources":["../../../src/replication-protocol/upstream.ts"],"sourcesContent":["import { firstValueFrom, filter } from 'rxjs';\nimport { stackCheckpoints } from '../rx-storage-helper';\nimport type {\n BulkWriteRow,\n BulkWriteRowById,\n ById,\n EventBulk,\n RxDocumentData,\n RxReplicationWriteToMasterRow,\n RxStorageChangeEvent,\n RxStorageInstanceReplicationState,\n RxStorageReplicationMeta,\n WithDeleted\n} from '../types';\nimport {\n appendToArray,\n batchArray,\n ensureNotFalsy,\n parseRevision,\n PROMISE_RESOLVE_FALSE\n} from '../plugins/utils';\nimport {\n getLastCheckpointDoc,\n setCheckpoint\n} from './checkpoint';\nimport { resolveConflictError } from './conflicts';\nimport { writeDocToDocState } from './helper';\nimport {\n getAssumedMasterState,\n getMetaWriteRow\n} from './meta-instance';\n\n/**\n * Writes all document changes from the fork to the master.\n * The upstream runs on two modes:\n * - For initial replication, a checkpoint-iteration is used\n * - For ongoing local writes, we just subscribe to the changeStream of the fork.\n * In contrast to the master, the fork can be assumed to never loose connection,\n * so we do not have to prepare for missed out events.\n */\nexport async function startReplicationUpstream(\n state: RxStorageInstanceReplicationState\n) {\n\n if (\n state.input.initialCheckpoint &&\n state.input.initialCheckpoint.upstream\n ) {\n const checkpointDoc = await getLastCheckpointDoc(state, 'up');\n if (!checkpointDoc) {\n await setCheckpoint(\n state,\n 'up',\n state.input.initialCheckpoint.upstream\n );\n }\n }\n\n const replicationHandler = state.input.replicationHandler;\n state.streamQueue.up = state.streamQueue.up.then(() => {\n return upstreamInitialSync().then(() => {\n processTasks();\n });\n });\n\n // used to detect which tasks etc can in it at which order.\n let timer = 0;\n let initialSyncStartTime = -1;\n\n type Task = EventBulk, any>;\n type TaskWithTime = {\n task: Task;\n time: number;\n };\n const openTasks: TaskWithTime[] = [];\n\n\n const sub = state.input.forkInstance.changeStream()\n .pipe(\n filter(eventBulk => eventBulk.context !== state.downstreamBulkWriteFlag)\n ).subscribe(eventBulk => {\n state.stats.up.forkChangeStreamEmit = state.stats.up.forkChangeStreamEmit + 1;\n openTasks.push({\n task: eventBulk,\n time: timer++\n });\n if (state.input.waitBeforePersist) {\n return state.input.waitBeforePersist()\n .then(() => processTasks());\n } else {\n return processTasks();\n }\n });\n firstValueFrom(\n state.events.canceled.pipe(\n filter(canceled => !!canceled)\n )\n ).then(() => sub.unsubscribe());\n\n\n async function upstreamInitialSync() {\n state.stats.up.upstreamInitialSync = state.stats.up.upstreamInitialSync + 1;\n if (state.events.canceled.getValue()) {\n return;\n }\n\n state.checkpointQueue = state.checkpointQueue.then(() => getLastCheckpointDoc(state, 'up'));\n let lastCheckpoint: CheckpointType = await state.checkpointQueue;\n\n const promises: Promise[] = [];\n while (!state.events.canceled.getValue()) {\n initialSyncStartTime = timer++;\n const upResult = await state.input.forkInstance.getChangedDocumentsSince(\n state.input.pushBatchSize,\n lastCheckpoint\n );\n if (upResult.documents.length === 0) {\n break;\n }\n\n lastCheckpoint = stackCheckpoints([lastCheckpoint, upResult.checkpoint]);\n\n promises.push(\n persistToMaster(\n upResult.documents,\n ensureNotFalsy(lastCheckpoint)\n )\n );\n }\n\n /**\n * If we had conflicts during the initial sync,\n * it means that we likely have new writes to the fork\n * and so we have to run the initial sync again to upstream these new writes.\n */\n const resolvedPromises = await Promise.all(promises);\n const hadConflicts = resolvedPromises.find(r => !!r);\n if (hadConflicts) {\n await upstreamInitialSync();\n } else if (\n !state.firstSyncDone.up.getValue() &&\n !state.events.canceled.getValue()\n ) {\n state.firstSyncDone.up.next(true);\n }\n }\n\n\n /**\n * Takes all open tasks an processes them at once.\n */\n function processTasks() {\n if (\n state.events.canceled.getValue() ||\n openTasks.length === 0\n ) {\n state.events.active.up.next(false);\n return;\n }\n state.stats.up.processTasks = state.stats.up.processTasks + 1;\n state.events.active.up.next(true);\n state.streamQueue.up = state.streamQueue.up.then(() => {\n /**\n * Merge/filter all open tasks\n */\n const docs: RxDocumentData[] = [];\n let checkpoint: CheckpointType = {} as any;\n while (openTasks.length > 0) {\n const taskWithTime = ensureNotFalsy(openTasks.shift());\n /**\n * If the task came in before the last time the initial sync fetching\n * has run, we can ignore the task because the initial sync already processed\n * these documents.\n */\n if (taskWithTime.time < initialSyncStartTime) {\n continue;\n }\n appendToArray(\n docs,\n taskWithTime.task.events.map(r => {\n return r.documentData as any;\n })\n );\n checkpoint = stackCheckpoints([checkpoint, taskWithTime.task.checkpoint]);\n }\n\n const promise = docs.length === 0 ? PROMISE_RESOLVE_FALSE : persistToMaster(\n docs,\n checkpoint\n );\n return promise.then(() => {\n if (openTasks.length === 0) {\n state.events.active.up.next(false);\n } else {\n processTasks();\n }\n });\n });\n }\n\n let persistenceQueue: Promise = PROMISE_RESOLVE_FALSE;\n const nonPersistedFromMaster: {\n checkpoint?: CheckpointType;\n docs: ById>;\n } = {\n docs: {}\n };\n\n /**\n * Returns true if had conflicts,\n * false if not.\n */\n function persistToMaster(\n docs: RxDocumentData[],\n checkpoint: CheckpointType\n ): Promise {\n state.stats.up.persistToMaster = state.stats.up.persistToMaster + 1;\n\n /**\n * Add the new docs to the non-persistent list\n */\n docs.forEach(docData => {\n const docId: string = (docData as any)[state.primaryPath];\n nonPersistedFromMaster.docs[docId] = docData;\n });\n nonPersistedFromMaster.checkpoint = checkpoint;\n\n\n persistenceQueue = persistenceQueue.then(async () => {\n if (state.events.canceled.getValue()) {\n return false;\n }\n\n const upDocsById: ById> = nonPersistedFromMaster.docs;\n nonPersistedFromMaster.docs = {};\n const useCheckpoint = nonPersistedFromMaster.checkpoint;\n const docIds = Object.keys(upDocsById);\n if (docIds.length === 0) {\n return false;\n }\n\n const assumedMasterState = await getAssumedMasterState(\n state,\n docIds\n );\n\n const writeRowsToMaster: ById> = {};\n const writeRowsToMasterIds: string[] = [];\n const writeRowsToMeta: BulkWriteRowById = {};\n const forkStateById: ById> = {};\n\n await Promise.all(\n docIds.map(async (docId) => {\n const fullDocData: RxDocumentData = upDocsById[docId];\n forkStateById[docId] = fullDocData;\n const docData: WithDeleted = writeDocToDocState(fullDocData);\n const assumedMasterDoc = assumedMasterState[docId];\n\n /**\n * If the master state is equal to the\n * fork state, we can assume that the document state is already\n * replicated.\n */\n if (\n (\n assumedMasterDoc &&\n // if the isResolvedConflict is correct, we do not have to compare the documents.\n assumedMasterDoc.metaDocument.isResolvedConflict !== fullDocData._rev\n &&\n (await state.input.conflictHandler({\n realMasterState: assumedMasterDoc.docData,\n newDocumentState: docData\n }, 'upstream-check-if-equal')).isEqual\n )\n ||\n /**\n * If the master works with _rev fields,\n * we use that to check if our current doc state\n * is different from the assumedMasterDoc.\n */\n (\n assumedMasterDoc &&\n (assumedMasterDoc.docData as any)._rev &&\n parseRevision(fullDocData._rev).height === fullDocData._meta[state.input.identifier]\n )\n ) {\n return;\n }\n\n writeRowsToMasterIds.push(docId);\n\n writeRowsToMaster[docId] = {\n assumedMasterState: assumedMasterDoc ? assumedMasterDoc.docData : undefined,\n newDocumentState: docData\n };\n writeRowsToMeta[docId] = getMetaWriteRow(\n state,\n docData,\n assumedMasterDoc ? assumedMasterDoc.metaDocument : undefined\n );\n })\n );\n\n if (writeRowsToMasterIds.length === 0) {\n return false;\n }\n\n\n const writeRowsArray = Object.values(writeRowsToMaster);\n const conflictIds: Set = new Set();\n const conflictsById: ById> = {};\n\n /**\n * To always respect the push.batchSize,\n * we have to split the write rows into batches\n * to ensure that replicationHandler.masterWrite() is never\n * called with more documents than what the batchSize limits.\n */\n const writeBatches = batchArray(writeRowsArray, state.input.pushBatchSize);\n await Promise.all(\n writeBatches.map(async (writeBatch) => {\n const masterWriteResult = await replicationHandler.masterWrite(writeBatch);\n masterWriteResult.forEach(conflictDoc => {\n const id = (conflictDoc as any)[state.primaryPath];\n conflictIds.add(id);\n conflictsById[id] = conflictDoc;\n });\n })\n );\n\n\n const useWriteRowsToMeta: BulkWriteRow[] = [];\n\n\n writeRowsToMasterIds.forEach(docId => {\n if (!conflictIds.has(docId)) {\n state.events.processed.up.next(writeRowsToMaster[docId]);\n useWriteRowsToMeta.push(writeRowsToMeta[docId]);\n }\n });\n\n if (useWriteRowsToMeta.length > 0) {\n await state.input.metaInstance.bulkWrite(\n useWriteRowsToMeta,\n 'replication-up-write-meta'\n );\n // TODO what happens when we have conflicts here?\n }\n\n /**\n * Resolve conflicts by writing a new document\n * state to the fork instance and the 'real' master state\n * to the meta instance.\n * Non-409 errors will be detected by resolveConflictError()\n */\n let hadConflictWrites = false;\n if (conflictIds.size > 0) {\n state.stats.up.persistToMasterHadConflicts = state.stats.up.persistToMasterHadConflicts + 1;\n const conflictWriteFork: BulkWriteRow[] = [];\n const conflictWriteMeta: BulkWriteRowById = {};\n await Promise.all(\n Object\n .entries(conflictsById)\n .map(([docId, realMasterState]) => {\n const writeToMasterRow = writeRowsToMaster[docId];\n const input = {\n newDocumentState: writeToMasterRow.newDocumentState,\n assumedMasterState: writeToMasterRow.assumedMasterState,\n realMasterState\n };\n return resolveConflictError(\n state,\n input,\n forkStateById[docId]\n ).then(resolved => {\n if (resolved) {\n state.events.resolvedConflicts.next({\n input,\n output: resolved.output\n });\n conflictWriteFork.push({\n previous: forkStateById[docId],\n document: resolved.resolvedDoc\n });\n const assumedMasterDoc = assumedMasterState[docId];\n conflictWriteMeta[docId] = getMetaWriteRow(\n state,\n ensureNotFalsy(realMasterState),\n assumedMasterDoc ? assumedMasterDoc.metaDocument : undefined,\n resolved.resolvedDoc._rev\n );\n }\n });\n })\n );\n\n if (conflictWriteFork.length > 0) {\n hadConflictWrites = true;\n\n state.stats.up.persistToMasterConflictWrites = state.stats.up.persistToMasterConflictWrites + 1;\n const forkWriteResult = await state.input.forkInstance.bulkWrite(\n conflictWriteFork,\n 'replication-up-write-conflict'\n );\n /**\n * Errors in the forkWriteResult must not be handled\n * because they have been caused by a write to the forkInstance\n * in between which will anyway trigger a new upstream cycle\n * that will then resolved the conflict again.\n */\n const useMetaWrites: BulkWriteRow[] = [];\n Object\n .keys(forkWriteResult.success)\n .forEach((docId) => {\n useMetaWrites.push(\n conflictWriteMeta[docId]\n );\n });\n if (useMetaWrites.length > 0) {\n await state.input.metaInstance.bulkWrite(\n useMetaWrites,\n 'replication-up-write-conflict-meta'\n );\n }\n // TODO what to do with conflicts while writing to the metaInstance?\n }\n }\n\n /**\n * For better performance we do not await checkpoint writes,\n * but to ensure order on parallel checkpoint writes,\n * we have to use a queue.\n */\n state.checkpointQueue = state.checkpointQueue.then(() => setCheckpoint(\n state,\n 'up',\n useCheckpoint\n ));\n\n return hadConflictWrites;\n }).catch(unhandledError => {\n state.events.error.next(unhandledError);\n return false;\n });\n\n return persistenceQueue;\n }\n}\n\n"],"mappings":";;;;;;AAAA,IAAAA,KAAA,GAAAC,OAAA;AACA,IAAAC,gBAAA,GAAAD,OAAA;AAaA,IAAAE,MAAA,GAAAF,OAAA;AAOA,IAAAG,WAAA,GAAAH,OAAA;AAIA,IAAAI,UAAA,GAAAJ,OAAA;AACA,IAAAK,OAAA,GAAAL,OAAA;AACA,IAAAM,aAAA,GAAAN,OAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,eAAeO,wBAAwBA,CAC1CC,KAAmD,EACrD;EAEE,IACIA,KAAK,CAACC,KAAK,CAACC,iBAAiB,IAC7BF,KAAK,CAACC,KAAK,CAACC,iBAAiB,CAACC,QAAQ,EACxC;IACE,IAAMC,aAAa,GAAG,MAAM,IAAAC,gCAAoB,EAACL,KAAK,EAAE,IAAI,CAAC;IAC7D,IAAI,CAACI,aAAa,EAAE;MAChB,MAAM,IAAAE,yBAAa,EACfN,KAAK,EACL,IAAI,EACJA,KAAK,CAACC,KAAK,CAACC,iBAAiB,CAACC,QAClC,CAAC;IACL;EACJ;EAEA,IAAMI,kBAAkB,GAAGP,KAAK,CAACC,KAAK,CAACM,kBAAkB;EACzDP,KAAK,CAACQ,WAAW,CAACC,EAAE,GAAGT,KAAK,CAACQ,WAAW,CAACC,EAAE,CAACC,IAAI,CAAC,MAAM;IACnD,OAAOC,mBAAmB,CAAC,CAAC,CAACD,IAAI,CAAC,MAAM;MACpCE,YAAY,CAAC,CAAC;IAClB,CAAC,CAAC;EACN,CAAC,CAAC;;EAEF;EACA,IAAIC,KAAK,GAAG,CAAC;EACb,IAAIC,oBAAoB,GAAG,CAAC,CAAC;EAO7B,IAAMC,SAAyB,GAAG,EAAE;EAGpC,IAAMC,GAAG,GAAGhB,KAAK,CAACC,KAAK,CAACgB,YAAY,CAACC,YAAY,CAAC,CAAC,CAC9CC,IAAI,CACD,IAAAC,YAAM,EAACC,SAAS,IAAIA,SAAS,CAACC,OAAO,KAAKtB,KAAK,CAACuB,uBAAuB,CAC3E,CAAC,CAACC,SAAS,CAACH,SAAS,IAAI;IACrBrB,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAACiB,oBAAoB,GAAG1B,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAACiB,oBAAoB,GAAG,CAAC;IAC7EX,SAAS,CAACY,IAAI,CAAC;MACXC,IAAI,EAAEP,SAAS;MACfQ,IAAI,EAAEhB,KAAK;IACf,CAAC,CAAC;IACF,IAAIb,KAAK,CAACC,KAAK,CAAC6B,iBAAiB,EAAE;MAC/B,OAAO9B,KAAK,CAACC,KAAK,CAAC6B,iBAAiB,CAAC,CAAC,CACjCpB,IAAI,CAAC,MAAME,YAAY,CAAC,CAAC,CAAC;IACnC,CAAC,MAAM;MACH,OAAOA,YAAY,CAAC,CAAC;IACzB;EACJ,CAAC,CAAC;EACN,IAAAmB,oBAAc,EACV/B,KAAK,CAACgC,MAAM,CAACC,QAAQ,CAACd,IAAI,CACtB,IAAAC,YAAM,EAACa,QAAQ,IAAI,CAAC,CAACA,QAAQ,CACjC,CACJ,CAAC,CAACvB,IAAI,CAAC,MAAMM,GAAG,CAACkB,WAAW,CAAC,CAAC,CAAC;EAG/B,eAAevB,mBAAmBA,CAAA,EAAG;IACjCX,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAACE,mBAAmB,GAAGX,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAACE,mBAAmB,GAAG,CAAC;IAC3E,IAAIX,KAAK,CAACgC,MAAM,CAACC,QAAQ,CAACE,QAAQ,CAAC,CAAC,EAAE;MAClC;IACJ;IAEAnC,KAAK,CAACoC,eAAe,GAAGpC,KAAK,CAACoC,eAAe,CAAC1B,IAAI,CAAC,MAAM,IAAAL,gCAAoB,EAACL,KAAK,EAAE,IAAI,CAAC,CAAC;IAC3F,IAAIqC,cAA8B,GAAG,MAAMrC,KAAK,CAACoC,eAAe;IAEhE,IAAME,QAAwB,GAAG,EAAE;IACnC,OAAO,CAACtC,KAAK,CAACgC,MAAM,CAACC,QAAQ,CAACE,QAAQ,CAAC,CAAC,EAAE;MACtCrB,oBAAoB,GAAGD,KAAK,EAAE;MAC9B,IAAM0B,QAAQ,GAAG,MAAMvC,KAAK,CAACC,KAAK,CAACgB,YAAY,CAACuB,wBAAwB,CACpExC,KAAK,CAACC,KAAK,CAACwC,aAAa,EACzBJ,cACJ,CAAC;MACD,IAAIE,QAAQ,CAACG,SAAS,CAACC,MAAM,KAAK,CAAC,EAAE;QACjC;MACJ;MAEAN,cAAc,GAAG,IAAAO,iCAAgB,EAAC,CAACP,cAAc,EAAEE,QAAQ,CAACM,UAAU,CAAC,CAAC;MAExEP,QAAQ,CAACX,IAAI,CACTmB,eAAe,CACXP,QAAQ,CAACG,SAAS,EAClB,IAAAK,qBAAc,EAACV,cAAc,CACjC,CACJ,CAAC;IACL;;IAEA;AACR;AACA;AACA;AACA;IACQ,IAAMW,gBAAgB,GAAG,MAAMC,OAAO,CAACC,GAAG,CAACZ,QAAQ,CAAC;IACpD,IAAMa,YAAY,GAAGH,gBAAgB,CAACI,IAAI,CAACC,CAAC,IAAI,CAAC,CAACA,CAAC,CAAC;IACpD,IAAIF,YAAY,EAAE;MACd,MAAMxC,mBAAmB,CAAC,CAAC;IAC/B,CAAC,MAAM,IACH,CAACX,KAAK,CAACsD,aAAa,CAAC7C,EAAE,CAAC0B,QAAQ,CAAC,CAAC,IAClC,CAACnC,KAAK,CAACgC,MAAM,CAACC,QAAQ,CAACE,QAAQ,CAAC,CAAC,EACnC;MACEnC,KAAK,CAACsD,aAAa,CAAC7C,EAAE,CAAC8C,IAAI,CAAC,IAAI,CAAC;IACrC;EACJ;;EAGA;AACJ;AACA;EACI,SAAS3C,YAAYA,CAAA,EAAG;IACpB,IACIZ,KAAK,CAACgC,MAAM,CAACC,QAAQ,CAACE,QAAQ,CAAC,CAAC,IAChCpB,SAAS,CAAC4B,MAAM,KAAK,CAAC,EACxB;MACE3C,KAAK,CAACgC,MAAM,CAACwB,MAAM,CAAC/C,EAAE,CAAC8C,IAAI,CAAC,KAAK,CAAC;MAClC;IACJ;IACAvD,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAACG,YAAY,GAAGZ,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAACG,YAAY,GAAG,CAAC;IAC7DZ,KAAK,CAACgC,MAAM,CAACwB,MAAM,CAAC/C,EAAE,CAAC8C,IAAI,CAAC,IAAI,CAAC;IACjCvD,KAAK,CAACQ,WAAW,CAACC,EAAE,GAAGT,KAAK,CAACQ,WAAW,CAACC,EAAE,CAACC,IAAI,CAAC,MAAM;MACnD;AACZ;AACA;MACY,IAAM+C,IAAiC,GAAG,EAAE;MAC5C,IAAIZ,UAA0B,GAAG,CAAC,CAAQ;MAC1C,OAAO9B,SAAS,CAAC4B,MAAM,GAAG,CAAC,EAAE;QACzB,IAAMe,YAAY,GAAG,IAAAX,qBAAc,EAAChC,SAAS,CAAC4C,KAAK,CAAC,CAAC,CAAC;QACtD;AAChB;AACA;AACA;AACA;QACgB,IAAID,YAAY,CAAC7B,IAAI,GAAGf,oBAAoB,EAAE;UAC1C;QACJ;QACA,IAAA8C,oBAAa,EACTH,IAAI,EACJC,YAAY,CAAC9B,IAAI,CAACI,MAAM,CAAC6B,GAAG,CAACR,CAAC,IAAI;UAC9B,OAAOA,CAAC,CAACS,YAAY;QACzB,CAAC,CACL,CAAC;QACDjB,UAAU,GAAG,IAAAD,iCAAgB,EAAC,CAACC,UAAU,EAAEa,YAAY,CAAC9B,IAAI,CAACiB,UAAU,CAAC,CAAC;MAC7E;MAEA,IAAMkB,OAAO,GAAGN,IAAI,CAACd,MAAM,KAAK,CAAC,GAAGqB,4BAAqB,GAAGlB,eAAe,CACvEW,IAAI,EACJZ,UACJ,CAAC;MACD,OAAOkB,OAAO,CAACrD,IAAI,CAAC,MAAM;QACtB,IAAIK,SAAS,CAAC4B,MAAM,KAAK,CAAC,EAAE;UACxB3C,KAAK,CAACgC,MAAM,CAACwB,MAAM,CAAC/C,EAAE,CAAC8C,IAAI,CAAC,KAAK,CAAC;QACtC,CAAC,MAAM;UACH3C,YAAY,CAAC,CAAC;QAClB;MACJ,CAAC,CAAC;IACN,CAAC,CAAC;EACN;EAEA,IAAIqD,gBAAkC,GAAGD,4BAAqB;EAC9D,IAAME,sBAGL,GAAG;IACAT,IAAI,EAAE,CAAC;EACX,CAAC;;EAED;AACJ;AACA;AACA;EACI,SAASX,eAAeA,CACpBW,IAAiC,EACjCZ,UAA0B,EACV;IAChB7C,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAACqC,eAAe,GAAG9C,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAACqC,eAAe,GAAG,CAAC;;IAEnE;AACR;AACA;IACQW,IAAI,CAACU,OAAO,CAACC,OAAO,IAAI;MACpB,IAAMC,KAAa,GAAID,OAAO,CAASpE,KAAK,CAACsE,WAAW,CAAC;MACzDJ,sBAAsB,CAACT,IAAI,CAACY,KAAK,CAAC,GAAGD,OAAO;IAChD,CAAC,CAAC;IACFF,sBAAsB,CAACrB,UAAU,GAAGA,UAAU;IAG9CoB,gBAAgB,GAAGA,gBAAgB,CAACvD,IAAI,CAAC,YAAY;MACjD,IAAIV,KAAK,CAACgC,MAAM,CAACC,QAAQ,CAACE,QAAQ,CAAC,CAAC,EAAE;QAClC,OAAO,KAAK;MAChB;MAEA,IAAMoC,UAA2C,GAAGL,sBAAsB,CAACT,IAAI;MAC/ES,sBAAsB,CAACT,IAAI,GAAG,CAAC,CAAC;MAChC,IAAMe,aAAa,GAAGN,sBAAsB,CAACrB,UAAU;MACvD,IAAM4B,MAAM,GAAGC,MAAM,CAACC,IAAI,CAACJ,UAAU,CAAC;MACtC,IAAIE,MAAM,CAAC9B,MAAM,KAAK,CAAC,EAAE;QACrB,OAAO,KAAK;MAChB;MAEA,IAAMiC,kBAAkB,GAAG,MAAM,IAAAC,mCAAqB,EAClD7E,KAAK,EACLyE,MACJ,CAAC;MAED,IAAMK,iBAAiE,GAAG,CAAC,CAAC;MAC5E,IAAMC,oBAA8B,GAAG,EAAE;MACzC,IAAMC,eAA2D,GAAG,CAAC,CAAC;MACtE,IAAMC,aAA8C,GAAG,CAAC,CAAC;MAEzD,MAAMhC,OAAO,CAACC,GAAG,CACbuB,MAAM,CAACZ,GAAG,CAAC,MAAOQ,KAAK,IAAK;QACxB,IAAMa,WAAsC,GAAGX,UAAU,CAACF,KAAK,CAAC;QAChEY,aAAa,CAACZ,KAAK,CAAC,GAAGa,WAAW;QAClC,IAAMd,OAA+B,GAAG,IAAAe,0BAAkB,EAACD,WAAW,CAAC;QACvE,IAAME,gBAAgB,GAAGR,kBAAkB,CAACP,KAAK,CAAC;;QAElD;AACpB;AACA;AACA;AACA;QACoB,IAEQe,gBAAgB;QAChB;QACAA,gBAAgB,CAACC,YAAY,CAACC,kBAAkB,KAAKJ,WAAW,CAACK,IAAI,IAErE,CAAC,MAAMvF,KAAK,CAACC,KAAK,CAACuF,eAAe,CAAC;UAC/BC,eAAe,EAAEL,gBAAgB,CAAChB,OAAO;UACzCsB,gBAAgB,EAAEtB;QACtB,CAAC,EAAE,yBAAyB,CAAC,EAAEuB,OAAO;QAG1C;AACxB;AACA;AACA;AACA;;QAE4BP,gBAAgB,IACfA,gBAAgB,CAAChB,OAAO,CAASmB,IAAI,IACtC,IAAAK,oBAAa,EAACV,WAAW,CAACK,IAAI,CAAC,CAACM,MAAM,KAAKX,WAAW,CAACY,KAAK,CAAC9F,KAAK,CAACC,KAAK,CAAC8F,UAAU,CAAC,CACvF,EACH;UACE;QACJ;QAEAhB,oBAAoB,CAACpD,IAAI,CAAC0C,KAAK,CAAC;QAEhCS,iBAAiB,CAACT,KAAK,CAAC,GAAG;UACvBO,kBAAkB,EAAEQ,gBAAgB,GAAGA,gBAAgB,CAAChB,OAAO,GAAG4B,SAAS;UAC3EN,gBAAgB,EAAEtB;QACtB,CAAC;QACDY,eAAe,CAACX,KAAK,CAAC,GAAG,IAAA4B,6BAAe,EACpCjG,KAAK,EACLoE,OAAO,EACPgB,gBAAgB,GAAGA,gBAAgB,CAACC,YAAY,GAAGW,SACvD,CAAC;MACL,CAAC,CACL,CAAC;MAED,IAAIjB,oBAAoB,CAACpC,MAAM,KAAK,CAAC,EAAE;QACnC,OAAO,KAAK;MAChB;MAGA,IAAMuD,cAAc,GAAGxB,MAAM,CAACyB,MAAM,CAACrB,iBAAiB,CAAC;MACvD,IAAMsB,WAAwB,GAAG,IAAIC,GAAG,CAAC,CAAC;MAC1C,IAAMC,aAA2C,GAAG,CAAC,CAAC;;MAEtD;AACZ;AACA;AACA;AACA;AACA;MACY,IAAMC,YAAY,GAAG,IAAAC,iBAAU,EAACN,cAAc,EAAElG,KAAK,CAACC,KAAK,CAACwC,aAAa,CAAC;MAC1E,MAAMQ,OAAO,CAACC,GAAG,CACbqD,YAAY,CAAC1C,GAAG,CAAC,MAAO4C,UAAU,IAAK;QACnC,IAAMC,iBAAiB,GAAG,MAAMnG,kBAAkB,CAACoG,WAAW,CAACF,UAAU,CAAC;QAC1EC,iBAAiB,CAACvC,OAAO,CAACyC,WAAW,IAAI;UACrC,IAAMC,EAAE,GAAID,WAAW,CAAS5G,KAAK,CAACsE,WAAW,CAAC;UAClD8B,WAAW,CAACU,GAAG,CAACD,EAAE,CAAC;UACnBP,aAAa,CAACO,EAAE,CAAC,GAAGD,WAAW;QACnC,CAAC,CAAC;MACN,CAAC,CACL,CAAC;MAGD,IAAMG,kBAA4D,GAAG,EAAE;MAGvEhC,oBAAoB,CAACZ,OAAO,CAACE,KAAK,IAAI;QAClC,IAAI,CAAC+B,WAAW,CAACY,GAAG,CAAC3C,KAAK,CAAC,EAAE;UACzBrE,KAAK,CAACgC,MAAM,CAACiF,SAAS,CAACxG,EAAE,CAAC8C,IAAI,CAACuB,iBAAiB,CAACT,KAAK,CAAC,CAAC;UACxD0C,kBAAkB,CAACpF,IAAI,CAACqD,eAAe,CAACX,KAAK,CAAC,CAAC;QACnD;MACJ,CAAC,CAAC;MAEF,IAAI0C,kBAAkB,CAACpE,MAAM,GAAG,CAAC,EAAE;QAC/B,MAAM3C,KAAK,CAACC,KAAK,CAACiH,YAAY,CAACC,SAAS,CACpCJ,kBAAkB,EAClB,2BACJ,CAAC;QACD;MACJ;;MAEA;AACZ;AACA;AACA;AACA;AACA;MACY,IAAIK,iBAAiB,GAAG,KAAK;MAC7B,IAAIhB,WAAW,CAACiB,IAAI,GAAG,CAAC,EAAE;QACtBrH,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAAC6G,2BAA2B,GAAGtH,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAAC6G,2BAA2B,GAAG,CAAC;QAC3F,IAAMC,iBAA4C,GAAG,EAAE;QACvD,IAAMC,iBAA6D,GAAG,CAAC,CAAC;QACxE,MAAMvE,OAAO,CAACC,GAAG,CACbwB,MAAM,CACD+C,OAAO,CAACnB,aAAa,CAAC,CACtBzC,GAAG,CAAC,CAAC,CAACQ,KAAK,EAAEoB,eAAe,CAAC,KAAK;UAC/B,IAAMiC,gBAAgB,GAAG5C,iBAAiB,CAACT,KAAK,CAAC;UACjD,IAAMpE,KAAK,GAAG;YACVyF,gBAAgB,EAAEgC,gBAAgB,CAAChC,gBAAgB;YACnDd,kBAAkB,EAAE8C,gBAAgB,CAAC9C,kBAAkB;YACvDa;UACJ,CAAC;UACD,OAAO,IAAAkC,+BAAoB,EACvB3H,KAAK,EACLC,KAAK,EACLgF,aAAa,CAACZ,KAAK,CACvB,CAAC,CAAC3D,IAAI,CAACkH,QAAQ,IAAI;YACf,IAAIA,QAAQ,EAAE;cACV5H,KAAK,CAACgC,MAAM,CAAC6F,iBAAiB,CAACtE,IAAI,CAAC;gBAChCtD,KAAK;gBACL6H,MAAM,EAAEF,QAAQ,CAACE;cACrB,CAAC,CAAC;cACFP,iBAAiB,CAAC5F,IAAI,CAAC;gBACnBoG,QAAQ,EAAE9C,aAAa,CAACZ,KAAK,CAAC;gBAC9B2D,QAAQ,EAAEJ,QAAQ,CAACK;cACvB,CAAC,CAAC;cACF,IAAM7C,gBAAgB,GAAGR,kBAAkB,CAACP,KAAK,CAAC;cAClDmD,iBAAiB,CAACnD,KAAK,CAAC,GAAG,IAAA4B,6BAAe,EACtCjG,KAAK,EACL,IAAA+C,qBAAc,EAAC0C,eAAe,CAAC,EAC/BL,gBAAgB,GAAGA,gBAAgB,CAACC,YAAY,GAAGW,SAAS,EAC5D4B,QAAQ,CAACK,WAAW,CAAC1C,IACzB,CAAC;YACL;UACJ,CAAC,CAAC;QACN,CAAC,CACT,CAAC;QAED,IAAIgC,iBAAiB,CAAC5E,MAAM,GAAG,CAAC,EAAE;UAC9ByE,iBAAiB,GAAG,IAAI;UAExBpH,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAACyH,6BAA6B,GAAGlI,KAAK,CAACyB,KAAK,CAAChB,EAAE,CAACyH,6BAA6B,GAAG,CAAC;UAC/F,IAAMC,eAAe,GAAG,MAAMnI,KAAK,CAACC,KAAK,CAACgB,YAAY,CAACkG,SAAS,CAC5DI,iBAAiB,EACjB,+BACJ,CAAC;UACD;AACpB;AACA;AACA;AACA;AACA;UACoB,IAAMa,aAAuD,GAAG,EAAE;UAClE1D,MAAM,CACDC,IAAI,CAACwD,eAAe,CAACE,OAAO,CAAC,CAC7BlE,OAAO,CAAEE,KAAK,IAAK;YAChB+D,aAAa,CAACzG,IAAI,CACd6F,iBAAiB,CAACnD,KAAK,CAC3B,CAAC;UACL,CAAC,CAAC;UACN,IAAI+D,aAAa,CAACzF,MAAM,GAAG,CAAC,EAAE;YAC1B,MAAM3C,KAAK,CAACC,KAAK,CAACiH,YAAY,CAACC,SAAS,CACpCiB,aAAa,EACb,oCACJ,CAAC;UACL;UACA;QACJ;MACJ;;MAEA;AACZ;AACA;AACA;AACA;MACYpI,KAAK,CAACoC,eAAe,GAAGpC,KAAK,CAACoC,eAAe,CAAC1B,IAAI,CAAC,MAAM,IAAAJ,yBAAa,EAClEN,KAAK,EACL,IAAI,EACJwE,aACJ,CAAC,CAAC;MAEF,OAAO4C,iBAAiB;IAC5B,CAAC,CAAC,CAACkB,KAAK,CAACC,cAAc,IAAI;MACvBvI,KAAK,CAACgC,MAAM,CAACwG,KAAK,CAACjF,IAAI,CAACgF,cAAc,CAAC;MACvC,OAAO,KAAK;IAChB,CAAC,CAAC;IAEF,OAAOtE,gBAAgB;EAC3B;AACJ"} \ No newline at end of file diff --git a/dist/lib/rx-query.js b/dist/lib/rx-query.js index 5303daf984d..f445983ee7f 100644 --- a/dist/lib/rx-query.js +++ b/dist/lib/rx-query.js @@ -20,6 +20,7 @@ var _hooks = require("./hooks"); var _eventReduce = require("./event-reduce"); var _queryCache = require("./query-cache"); var _rxQueryHelper = require("./rx-query-helper"); +var _ohash = require("ohash"); var _queryCount = 0; var newQueryID = function () { return ++_queryCount; @@ -53,6 +54,8 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { this._lastExecEnd = 0; this._limitBufferSize = null; this._limitBufferResults = null; + this.PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS = 1_000; + this._persistedQueryCacheResult = undefined; this._ensureEqualQueue = _utils.PROMISE_RESOLVE_FALSE; this.op = op; this.mangoQuery = mangoQuery; @@ -79,6 +82,7 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { if (typeof newResultData === 'number') { this._result = { docsData: [], + docsKeys: [], docsMap: new Map(), docsDataMap: new Map(), count: newResultData, @@ -98,13 +102,16 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { * 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. */ + var docsKeys = []; var docsData = docs.map(doc => { docsDataMap.set(doc.primary, doc._data); docsMap.set(doc.primary, doc); + docsKeys.push(doc.primary); return doc._data; }); this._result = { docsData, + docsKeys, docsMap, docsDataMap, count: docsData.length, @@ -121,6 +128,11 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { this._execOverDatabaseCount = this._execOverDatabaseCount + 1; this._lastExecStart = (0, _utils.now)(); if (this.op === 'count') { + // if we have a persisted query cache result, use the result + if (this._persistedQueryCacheResult) { + // TODO: correct this number, but how? + return Number(this._persistedQueryCacheResult); + } var preparedQuery = this.getPreparedQuery(); var result = await this.collection.storageInstance.count(preparedQuery); if (result.mode === 'slow' && !this.collection.database.allowSlowCount) { @@ -159,7 +171,7 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { return ret; } var docsPromise = queryCollection(this); - return docsPromise.then(docs => { + return await docsPromise.then(docs => { this._lastExecEnd = (0, _utils.now)(); return docs; }); @@ -170,7 +182,7 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { * To have an easier implementations, * just subscribe and use the first result */; - _proto.exec = function exec(throwIfMissing) { + _proto.exec = async function exec(throwIfMissing) { if (throwIfMissing && this.op !== 'findOne') { throw (0, _rxError.newRxError)('QU9', { collection: this.collection.name, @@ -184,17 +196,17 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { * this will make sure that errors in the query which throw inside of the RxStorage, * will be thrown at this execution context and not in the background. */ - return _ensureEqual(this).then(() => (0, _rxjs.firstValueFrom)(this.$)).then(result => { - if (!result && throwIfMissing) { - throw (0, _rxError.newRxError)('QU10', { - collection: this.collection.name, - query: this.mangoQuery, - op: this.op - }); - } else { - return result; - } - }); + await _ensureEqual(this); + var result = await (0, _rxjs.firstValueFrom)(this.$); + if (!result && throwIfMissing) { + throw (0, _rxError.newRxError)('QU10', { + collection: this.collection.name, + query: this.mangoQuery, + op: this.op + }); + } else { + return result; + } } /** @@ -218,7 +230,7 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { /** * returns the prepared query - * which can be send to the storage instance to query for documents. + * which can be sent to the storage instance to query for documents. * @overwrites itself with the actual value. */; _proto.getPreparedQuery = function getPreparedQuery() { @@ -275,7 +287,7 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { } // we only set some methods of query-builder here - // because the others depend on these ones + // because the others depend on these ; _proto.where = function where(_queryObj) { throw (0, _utils.pluginMissing)('query-builder'); @@ -305,6 +317,38 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { this._limitBufferSize = bufferSize; return this; }; + _proto.enablePersistentQueryCache = function enablePersistentQueryCache(backend, limit = this.PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS) { + this._queryCacheBackend = backend; + this._queryCacheLimit = limit; + this._persistedQueryCacheLoaded = this._loadPersistedResultsIfTheyExist(); + return this; + }; + _proto._loadPersistedResultsIfTheyExist = async function _loadPersistedResultsIfTheyExist() { + if (!this._queryCacheBackend) { + return; + } + if (this._persistedQueryCacheResult) { + return; + } + var key = _queryKey(this); + var value = await this._queryCacheBackend.getItem("qc:" + key); + this._persistedQueryCacheResult = value ?? undefined; + + // if this is a regular query, also load documents into cache + if (Array.isArray(value) && value.length > 0) { + this._persistedQueryCacheResult = value; + + /** + * TODO: try to move this to where the query execution happens, maybe lazy loading is even better, but if + * query definition and data loading is almost happening at the same time, this is the more simple + * implementation. + */ + var documents = await this.collection.storageInstance.findDocumentsById(value, false); + for (var document of Object.values(documents)) { + this.collection._docCache.getCachedRxDocument(document); + } + } + }; (0, _createClass2.default)(RxQueryBase, [{ key: "$", get: function () { @@ -317,10 +361,10 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { (0, _operators.filter)(changeEvent => !changeEvent.isLocal), /** * Start once to ensure the querying also starts - * when there where no changes. + * when there were no changes. */ (0, _operators.startWith)(null), - // ensure query results are up to date. + // ensure query results are up-to-date. (0, _operators.mergeMap)(() => _ensureEqual(this)), // use the current result set, written by _ensureEqual(). (0, _operators.map)(() => this._result), @@ -328,11 +372,7 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { (0, _operators.shareReplay)(_utils.RXJS_SHARE_REPLAY_DEFAULTS), // do not proceed if result set has not changed. (0, _operators.distinctUntilChanged)((prev, curr) => { - if (prev && prev.time === (0, _utils.ensureNotFalsy)(curr).time) { - return true; - } else { - return false; - } + return Boolean(prev && prev.time === (0, _utils.ensureNotFalsy)(curr).time); }), (0, _operators.filter)(result => !!result), /** * Map the result set to a single RxDocument or an array, @@ -349,7 +389,7 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { return useResult.docsMap; } else { // find()-queries emit RxDocument[] - // Flat copy the array so it won't matter if the user modifies it. + // Flat copy the array, so it won't matter if the user modifies it. return useResult.docs.slice(0); } })); @@ -368,6 +408,7 @@ var RxQueryBase = exports.RxQueryBase = /*#__PURE__*/function () { // time stamps on when the last full exec over the database has run // used to properly handle events that happen while the find-query is running // Fields used for the Limit Buffer when enabled: + // Fields used for the persistent query cache when enabled: /** * ensures that the exec-runs * are not run in parallel @@ -410,28 +451,25 @@ function createRxQuery(op, queryObj, collection, other) { // ensure when created with same params, only one is created ret = tunnelQueryCache(ret); + // TODO: clear persistent query cache as well (0, _queryCache.triggerCacheReplacement)(collection); return ret; } /** * Check if the current results-state is in sync with the database - * which means that no write event happened since the last run. + * which means that no writes event happened since the last run. * @return false if not which means it should re-execute */ function _isResultsInSync(rxQuery) { var currentLatestEventNumber = rxQuery.asRxQuery.collection._changeEventBuffer.counter; - if (rxQuery._latestChangeEvent >= currentLatestEventNumber) { - return true; - } else { - return false; - } + return rxQuery._latestChangeEvent >= currentLatestEventNumber; } /** * wraps __ensureEqual() * to ensure it does not run in parallel - * @return true if has changed, false if not + * @return true if it has changed, false if not */ function _ensureEqual(rxQuery) { // Optimisation shortcut @@ -446,7 +484,8 @@ function _ensureEqual(rxQuery) { * ensures that the results of this query is equal to the results which a query over the database would give * @return true if results have changed */ -function __ensureEqual(rxQuery) { +async function __ensureEqual(rxQuery) { + await rxQuery._persistedQueryCacheLoaded; rxQuery._lastEnsureEqual = (0, _utils.now)(); /** @@ -478,7 +517,7 @@ function __ensureEqual(rxQuery) { rxQuery._latestChangeEvent = rxQuery.asRxQuery.collection._changeEventBuffer.counter; var runChangeEvents = rxQuery.asRxQuery.collection._changeEventBuffer.reduceByLastOfDoc(missedChangeEvents); if (rxQuery._limitBufferResults !== null) { - var _loop = function (cE) { + var _loop = async function (cE) { if (rxQuery._limitBufferResults.find(doc => doc[rxQuery.collection.schema.primaryPath] === cE.documentId)) { // If so, the limit buffer is potential invalid -- let's just blow it up // TODO: could we instead update the documents in the limit buffer? @@ -488,7 +527,7 @@ function __ensureEqual(rxQuery) { }; // Check if any item in our limit buffer was modified by a change event for (var cE of runChangeEvents) { - if (_loop(cE)) break; + if (await _loop(cE)) break; } } if (rxQuery.op === 'count') { @@ -508,6 +547,7 @@ function __ensureEqual(rxQuery) { if (newCount !== previousCount) { ret = true; // true because results changed rxQuery._setResultData(newCount); + await _updatePersistentQueryCache(rxQuery); } } else { // 'find' or 'findOne' query @@ -519,12 +559,13 @@ function __ensureEqual(rxQuery) { // we got the new results, we do not have to re-execute, mustReExec stays false ret = true; // true because results changed rxQuery._setResultData(eventReduceResult.newResults); + await _updatePersistentQueryCache(rxQuery); } } } } - // oh no we have to re-execute the whole query over the database + // oh, no we have to re-execute the whole query over the database if (mustReExec) { // counter can change while _execOverDatabase() is running so we save it here var latestAfter = rxQuery.collection._changeEventBuffer.counter; @@ -544,9 +585,58 @@ function __ensureEqual(rxQuery) { rxQuery._setResultData(newResultData); } return ret; + }).then(async returnValue => { + await _updatePersistentQueryCache(rxQuery); + return returnValue; + }); + } + return ret; // true if results have changed +} + +function _queryKey(rxQuery) { + return String((0, _ohash.murmurHash)(rxQuery.toString(), 42)); +} +async function _updatePersistentQueryCache(rxQuery) { + if (!rxQuery._queryCacheBackend) { + return; + } + var backend = rxQuery._queryCacheBackend; + var isCount = rxQuery._result?.docs.length === 0 && rxQuery._result.count > 0; + var key = _queryKey(rxQuery); + var value = isCount ? rxQuery._result?.count?.toString() ?? '0' : rxQuery._result?.docsKeys ?? []; + + // update _persistedQueryCacheResult + rxQuery._persistedQueryCacheResult = value; + + // persist query cache + var lwt = rxQuery._result?.time ?? 0; + await backend.setItem("qc:" + String(key), value); + await backend.setItem("qc:" + String(key) + ":lwt", lwt.toString()); +} + +// Refactored out of `queryCollection`: modifies the docResults array to fill it with data +async function _queryCollectionByIds(rxQuery, docResults, docIds) { + var collection = rxQuery.collection; + docIds = docIds.filter(docId => { + // first try to fill from docCache + var docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId); + if (docData) { + if (!docData._deleted) { + docResults.push(docData); + } + return false; + } else { + return true; + } + }); + + // otherwise get from storage + if (docIds.length > 0) { + var docsMap = await collection.storageInstance.findDocumentsById(docIds, false); + Object.values(docsMap).forEach(docData => { + docResults.push(docData); }); } - return Promise.resolve(ret); // true if results have changed } /** @@ -556,6 +646,7 @@ function __ensureEqual(rxQuery) { * when specific queries are used. */ async function queryCollection(rxQuery) { + await rxQuery._persistedQueryCacheLoaded; var docs = []; var collection = rxQuery.collection; @@ -567,26 +658,7 @@ async function queryCollection(rxQuery) { */ if (rxQuery.isFindOneByIdQuery) { if (Array.isArray(rxQuery.isFindOneByIdQuery)) { - var docIds = rxQuery.isFindOneByIdQuery; - docIds = docIds.filter(docId => { - // first try to fill from docCache - var docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId); - if (docData) { - if (!docData._deleted) { - docs.push(docData); - } - return false; - } else { - return true; - } - }); - // otherwise get from storage - if (docIds.length > 0) { - var docsMap = await collection.storageInstance.findDocumentsById(docIds, false); - Object.values(docsMap).forEach(docData => { - docs.push(docData); - }); - } + await _queryCollectionByIds(rxQuery, docs, rxQuery.isFindOneByIdQuery); } else { var docId = rxQuery.isFindOneByIdQuery; @@ -594,25 +666,66 @@ async function queryCollection(rxQuery) { var docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId); if (!docData) { // otherwise get from storage - var _docsMap = await collection.storageInstance.findDocumentsById([docId], false); - if (_docsMap.hasOwnProperty(docId)) { - docData = _docsMap[docId]; + var docsMap = await collection.storageInstance.findDocumentsById([docId], false); + if (docsMap.hasOwnProperty(docId)) { + docData = docsMap[docId]; } } if (docData && !docData._deleted) { docs.push(docData); } } - } else { - var preparedQuery = rxQuery.getPreparedQuery(); - var queryResult = await collection.storageInstance.query(preparedQuery); - if (rxQuery._limitBufferSize !== null && rxQuery.mangoQuery.limit && queryResult.documents.length > rxQuery.mangoQuery.limit) { - // If there are more than query.limit results, we pull out our buffer items from the - // last rxQuery._limitBufferSize items of the results. - rxQuery._limitBufferResults = queryResult.documents.splice(rxQuery.mangoQuery.limit); + return docs; + } + if ((rxQuery.mangoQuery.limit && !rxQuery.mangoQuery.skip || !rxQuery.mangoQuery.limit) && rxQuery._queryCacheBackend && Array.isArray(rxQuery._persistedQueryCacheResult)) { + var persistedQueryCacheIds = new Set(rxQuery._persistedQueryCacheResult); + var primaryPath = rxQuery.collection.schema.primaryPath; + var queryKeyValue = _queryKey(rxQuery); + var lwt = (await rxQuery._queryCacheBackend.getItem("qc:" + queryKeyValue + ":lwt")) ?? 0; + var previousDocCount = rxQuery._result?.docsKeys?.length ?? persistedQueryCacheIds.size; + + // query all docs updated > last persisted, limit to an arbitrary 1_000_000 (10x of what we consider our largest library) + var { + documents: changedDocs + } = await collection.storageInstance.getChangedDocumentsSince(1_000_000, { + id: '', + lwt + }); + for (var changedDoc of changedDocs) { + /* + * no need to fetch again, we already got the doc from the list of changed docs, and therefore we filter + * deleted docs as well + */ + persistedQueryCacheIds.delete(changedDoc[primaryPath]); + + // ignore deleted docs or docs that do not match the query + if (!rxQuery.doesDocumentDataMatch(changedDoc)) { + continue; + } + + // doc should be in result + docs.push(changedDoc); } - docs = queryResult.documents; + + // If doc count does not match the number of docs previously returned by the query + // 1. try to pull data from the limit buffer + // 2. re-query data + // TODO: How can I utilize the limitBuffer? + var newDocCount = docs.length + persistedQueryCacheIds.size; + if (newDocCount === previousDocCount) { + // fetch remaining doc ids and add to result + await _queryCollectionByIds(rxQuery, docs, Array.from(persistedQueryCacheIds)); + return docs; + } + } + var preparedQuery = rxQuery.getPreparedQuery(); + var queryResult = await collection.storageInstance.query(preparedQuery); + if (rxQuery._limitBufferSize !== null && rxQuery.mangoQuery.limit && queryResult.documents.length > rxQuery.mangoQuery.limit) { + // If there are more than query.limit results, we pull out our buffer items from the + // last rxQuery._limitBufferSize items of the results. + rxQuery._limitBufferResults = queryResult.documents.splice(rxQuery.mangoQuery.limit); } + docs = queryResult.documents; return docs; } @@ -627,18 +740,18 @@ async function queryCollection(rxQuery) { function isFindOneByIdQuery(primaryPath, query) { // must have exactly one operator which must be $eq || $in if (!query.skip && query.selector && Object.keys(query.selector).length === 1 && query.selector[primaryPath]) { - var value = query.selector[primaryPath]; - if (typeof value === 'string') { - return value; - } else if (Object.keys(value).length === 1 && typeof value.$eq === 'string') { - return value.$eq; + var _value = query.selector[primaryPath]; + if (typeof _value === 'string') { + return _value; + } else if (Object.keys(_value).length === 1 && typeof _value.$eq === 'string') { + return _value.$eq; } // same with $in string arrays - if (Object.keys(value).length === 1 && Array.isArray(value.$eq) && + if (Object.keys(_value).length === 1 && Array.isArray(_value.$eq) && // must only contain strings - !value.$eq.find(r => typeof r !== 'string')) { - return value.$eq; + !_value.$eq.find(r => typeof r !== 'string')) { + return _value.$eq; } } return false; diff --git a/dist/lib/rx-query.js.map b/dist/lib/rx-query.js.map index 2d7d92c3ed4..83469220572 100644 --- a/dist/lib/rx-query.js.map +++ b/dist/lib/rx-query.js.map @@ -1 +1 @@ -{"version":3,"file":"rx-query.js","names":["_rxjs","require","_operators","_utils","_rxError","_hooks","_eventReduce","_queryCache","_rxQueryHelper","_queryCount","newQueryID","RxQueryBase","exports","op","mangoQuery","collection","other","id","_execOverDatabaseCount","_creationTime","now","_lastEnsureEqual","uncached","refCount$","BehaviorSubject","_result","_latestChangeEvent","_lastExecStart","_lastExecEnd","_limitBufferSize","_limitBufferResults","_ensureEqualQueue","PROMISE_RESOLVE_FALSE","_getDefaultQuery","isFindOneByIdQuery","schema","primaryPath","_proto","prototype","_setResultData","newResultData","docsData","docsMap","Map","docsDataMap","count","docs","time","Array","from","values","map","docData","_docCache","getCachedRxDocument","doc","set","primary","_data","length","_execOverDatabase","preparedQuery","getPreparedQuery","result","storageInstance","mode","database","allowSlowCount","newRxError","queryObj","ids","ensureNotFalsy","selector","$in","ret","mustBeQueried","forEach","getLatestDocumentDataIfExists","_deleted","push","findDocumentsById","Object","docsPromise","queryCollection","then","exec","throwIfMissing","name","query","_ensureEqual","firstValueFrom","$","toString","stringObj","sortObject","value","JSON","stringify","stringifyFilter","hookInput","rxQuery","normalizeMangoQuery","jsonSchema","limit","runPluginHooks","storage","statics","prepareQuery","doesDocumentDataMatch","queryMatcher","remove","isArray","Promise","all","update","_updateObj","pluginMissing","where","_queryObj","sort","_params","skip","_amount","enableLimitBuffer","bufferSize","console","error","_createClass2","default","key","get","_$","results$","pipe","filter","changeEvent","isLocal","startWith","mergeMap","shareReplay","RXJS_SHARE_REPLAY_DEFAULTS","distinctUntilChanged","prev","curr","useResult","slice","merge","normalizedQuery","overwriteGetterForCaching","getQueryMatcher","tunnelQueryCache","getByQuery","createRxQuery","triggerCacheReplacement","_isResultsInSync","currentLatestEventNumber","asRxQuery","_changeEventBuffer","counter","destroyed","__ensureEqual","mustReExec","missedChangeEvents","getFrom","runChangeEvents","reduceByLastOfDoc","_loop","cE","find","documentId","previousCount","newCount","didMatchBefore","previousDocumentData","doesMatchNow","documentData","eventReduceResult","calculateNewResults","runFullQueryAgain","changed","newResults","latestAfter","areRxDocumentArraysEqual","resolve","docIds","docId","hasOwnProperty","queryResult","documents","splice","keys","$eq","r","isRxQuery","obj"],"sources":["../../src/rx-query.ts"],"sourcesContent":["import {\n BehaviorSubject,\n firstValueFrom,\n Observable,\n merge\n} from 'rxjs';\nimport {\n mergeMap,\n filter,\n map,\n startWith,\n distinctUntilChanged,\n shareReplay\n} from 'rxjs/operators';\nimport {\n sortObject,\n stringifyFilter,\n pluginMissing,\n overwriteGetterForCaching,\n now,\n PROMISE_RESOLVE_FALSE,\n RXJS_SHARE_REPLAY_DEFAULTS,\n ensureNotFalsy,\n areRxDocumentArraysEqual\n} from './plugins/utils';\nimport {\n newRxError\n} from './rx-error';\nimport {\n runPluginHooks\n} from './hooks';\nimport type {\n RxCollection,\n RxDocument,\n RxQueryOP,\n RxQuery,\n MangoQuery,\n MangoQuerySortPart,\n MangoQuerySelector,\n PreparedQuery,\n RxChangeEvent,\n RxDocumentWriteData,\n RxDocumentData,\n QueryMatcher\n} from './types';\nimport { calculateNewResults } from './event-reduce';\nimport { triggerCacheReplacement } from './query-cache';\nimport { getQueryMatcher, normalizeMangoQuery } from './rx-query-helper';\n\nlet _queryCount = 0;\nconst newQueryID = function (): number {\n return ++_queryCount;\n};\n\nexport class RxQueryBase<\n RxDocType,\n // TODO also pass DocMethods here\n RxQueryResult = RxDocument[] | RxDocument\n> {\n\n public id: number = newQueryID();\n\n /**\n * Some stats then are used for debugging and cache replacement policies\n */\n public _execOverDatabaseCount: number = 0;\n public _creationTime = now();\n\n // used in the query-cache to determine if the RxQuery can be cleaned up.\n public _lastEnsureEqual = 0;\n\n public uncached = false;\n\n // used to count the subscribers to the query\n public refCount$ = new BehaviorSubject(null);\n\n public isFindOneByIdQuery: false | string | string[];\n\n\n /**\n * Contains the current result state\n * or null if query has not run yet.\n */\n public _result: {\n docsData: RxDocumentData[];\n // A key->document map, used in the event reduce optimization.\n docsDataMap: Map;\n docsMap: Map>;\n docs: RxDocument[];\n count: number;\n /**\n * Time at which the current _result state was created.\n * Used to determine if the result set has changed since X\n * so that we do not emit the same result multiple times on subscription.\n */\n time: number;\n } | null = null;\n\n\n constructor(\n public op: RxQueryOP,\n public mangoQuery: Readonly>,\n public collection: RxCollection,\n // used by some plugins\n public other: any = {}\n ) {\n if (!mangoQuery) {\n this.mangoQuery = _getDefaultQuery();\n }\n\n this.isFindOneByIdQuery = isFindOneByIdQuery(\n this.collection.schema.primaryPath as string,\n mangoQuery\n );\n }\n get $(): BehaviorSubject {\n if (!this._$) {\n\n const results$ = this.collection.$.pipe(\n /**\n * Performance shortcut.\n * Changes to local documents are not relevant for the query.\n */\n filter(changeEvent => !changeEvent.isLocal),\n /**\n * Start once to ensure the querying also starts\n * when there where no changes.\n */\n startWith(null),\n // ensure query results are up to date.\n mergeMap(() => _ensureEqual(this as any)),\n // use the current result set, written by _ensureEqual().\n map(() => this._result),\n // do not run stuff above for each new subscriber, only once.\n shareReplay(RXJS_SHARE_REPLAY_DEFAULTS),\n // do not proceed if result set has not changed.\n distinctUntilChanged((prev, curr) => {\n if (prev && prev.time === ensureNotFalsy(curr).time) {\n return true;\n } else {\n return false;\n }\n }),\n filter(result => !!result),\n /**\n * Map the result set to a single RxDocument or an array,\n * depending on query type\n */\n map((result) => {\n const useResult = ensureNotFalsy(result);\n if (this.op === 'count') {\n return useResult.count;\n } else if (this.op === 'findOne') {\n // findOne()-queries emit RxDocument or null\n return useResult.docs.length === 0 ? null : useResult.docs[0];\n } else if (this.op === 'findByIds') {\n return useResult.docsMap;\n } else {\n // find()-queries emit RxDocument[]\n // Flat copy the array so it won't matter if the user modifies it.\n return useResult.docs.slice(0);\n }\n })\n );\n\n this._$ = merge(\n results$,\n /**\n * Also add the refCount$ to the query observable\n * to allow us to count the amount of subscribers.\n */\n this.refCount$.pipe(\n filter(() => false)\n )\n );\n }\n return this._$ as any;\n }\n\n\n // stores the changeEvent-number of the last handled change-event\n public _latestChangeEvent: -1 | number = -1;\n\n // time stamps on when the last full exec over the database has run\n // used to properly handle events that happen while the find-query is running\n public _lastExecStart: number = 0;\n public _lastExecEnd: number = 0;\n\n // Fields used for the Limit Buffer when enabled:\n public _limitBufferSize: number | null = null;\n public _limitBufferResults: RxDocumentData[] | null = null;\n\n /**\n * ensures that the exec-runs\n * are not run in parallel\n */\n public _ensureEqualQueue: Promise = PROMISE_RESOLVE_FALSE;\n\n /**\n * Returns an observable that emits the results\n * This should behave like an rxjs-BehaviorSubject which means:\n * - Emit the current result-set on subscribe\n * - Emit the new result-set when an RxChangeEvent comes in\n * - Do not emit anything before the first result-set was created (no null)\n */\n public _$?: Observable;\n\n /**\n * set the new result-data as result-docs of the query\n * @param newResultData json-docs that were received from the storage\n */\n _setResultData(newResultData: RxDocumentData[] | number | Map>): void {\n if (typeof newResultData === 'number') {\n this._result = {\n docsData: [],\n docsMap: new Map(),\n docsDataMap: new Map(),\n count: newResultData,\n docs: [],\n time: now()\n };\n return;\n } else if (newResultData instanceof Map) {\n newResultData = Array.from((newResultData as Map>).values());\n }\n\n const docsDataMap = new Map();\n const docsMap = new Map();\n\n\n const docs = newResultData.map(docData => this.collection._docCache.getCachedRxDocument(docData));\n\n /**\n * Instead of using the newResultData in the result cache,\n * we directly use the objects that are stored in the RxDocument\n * to ensure we do not store the same data twice and fill up the memory.\n */\n const docsData = docs.map(doc => {\n docsDataMap.set(doc.primary, doc._data);\n docsMap.set(doc.primary, doc);\n return doc._data;\n });\n\n this._result = {\n docsData,\n docsMap,\n docsDataMap,\n count: docsData.length,\n docs,\n time: now()\n };\n }\n\n /**\n * executes the query on the database\n * @return results-array with document-data\n */\n async _execOverDatabase(): Promise[] | number> {\n this._execOverDatabaseCount = this._execOverDatabaseCount + 1;\n this._lastExecStart = now();\n\n\n if (this.op === 'count') {\n const preparedQuery = this.getPreparedQuery();\n const result = await this.collection.storageInstance.count(preparedQuery);\n if (result.mode === 'slow' && !this.collection.database.allowSlowCount) {\n throw newRxError('QU14', {\n collection: this.collection,\n queryObj: this.mangoQuery\n });\n } else {\n return result.count;\n }\n }\n\n if (this.op === 'findByIds') {\n const ids: string[] = ensureNotFalsy(this.mangoQuery.selector as any)[this.collection.schema.primaryPath].$in;\n const ret = new Map>();\n const mustBeQueried: string[] = [];\n // first try to fill from docCache\n ids.forEach(id => {\n const docData = this.collection._docCache.getLatestDocumentDataIfExists(id);\n if (docData) {\n if (!docData._deleted) {\n const doc = this.collection._docCache.getCachedRxDocument(docData);\n ret.set(id, doc);\n }\n } else {\n mustBeQueried.push(id);\n }\n });\n // everything which was not in docCache must be fetched from the storage\n if (mustBeQueried.length > 0) {\n const docs = await this.collection.storageInstance.findDocumentsById(mustBeQueried, false);\n Object.values(docs).forEach(docData => {\n const doc = this.collection._docCache.getCachedRxDocument(docData);\n ret.set(doc.primary, doc);\n });\n }\n return ret as any;\n }\n\n\n const docsPromise = queryCollection(this as any);\n return docsPromise.then(docs => {\n this._lastExecEnd = now();\n return docs;\n });\n }\n\n /**\n * Execute the query\n * To have an easier implementations,\n * just subscribe and use the first result\n */\n public exec(throwIfMissing: true): Promise>;\n public exec(): Promise;\n public exec(throwIfMissing?: boolean): Promise {\n if (throwIfMissing && this.op !== 'findOne') {\n throw newRxError('QU9', {\n collection: this.collection.name,\n query: this.mangoQuery,\n op: this.op\n });\n }\n\n\n /**\n * run _ensureEqual() here,\n * this will make sure that errors in the query which throw inside of the RxStorage,\n * will be thrown at this execution context and not in the background.\n */\n return _ensureEqual(this)\n .then(() => firstValueFrom(this.$))\n .then(result => {\n if (!result && throwIfMissing) {\n throw newRxError('QU10', {\n collection: this.collection.name,\n query: this.mangoQuery,\n op: this.op\n });\n } else {\n return result;\n }\n });\n }\n\n\n\n /**\n * cached call to get the queryMatcher\n * @overwrites itself with the actual value\n */\n get queryMatcher(): QueryMatcher> {\n const schema = this.collection.schema.jsonSchema;\n const normalizedQuery = normalizeMangoQuery(\n this.collection.schema.jsonSchema,\n this.mangoQuery\n );\n return overwriteGetterForCaching(\n this,\n 'queryMatcher',\n getQueryMatcher(\n schema,\n normalizedQuery\n ) as any\n );\n }\n\n /**\n * returns a string that is used for equal-comparisons\n * @overwrites itself with the actual value\n */\n toString(): string {\n const stringObj = sortObject({\n op: this.op,\n query: this.mangoQuery,\n other: this.other\n }, true);\n const value = JSON.stringify(stringObj, stringifyFilter);\n this.toString = () => value;\n return value;\n }\n\n /**\n * returns the prepared query\n * which can be send to the storage instance to query for documents.\n * @overwrites itself with the actual value.\n */\n getPreparedQuery(): PreparedQuery {\n const hookInput = {\n rxQuery: this,\n // can be mutated by the hooks so we have to deep clone first.\n mangoQuery: normalizeMangoQuery(\n this.collection.schema.jsonSchema,\n this.mangoQuery\n )\n };\n\n if (this._limitBufferSize !== null && hookInput.mangoQuery.limit) {\n hookInput.mangoQuery.limit = hookInput.mangoQuery.limit + this._limitBufferSize;\n }\n\n runPluginHooks('prePrepareQuery', hookInput);\n\n const value = this.collection.database.storage.statics.prepareQuery(\n this.collection.schema.jsonSchema,\n hookInput.mangoQuery\n );\n\n this.getPreparedQuery = () => value;\n return value;\n }\n\n /**\n * returns true if the document matches the query,\n * does not use the 'skip' and 'limit'\n */\n doesDocumentDataMatch(docData: RxDocType | any): boolean {\n // if doc is deleted, it cannot match\n if (docData._deleted) {\n return false;\n }\n\n return this.queryMatcher(docData);\n }\n\n /**\n * deletes all found documents\n * @return promise with deleted documents\n */\n remove(): Promise {\n return this\n .exec()\n .then(docs => {\n if (Array.isArray(docs)) {\n // TODO use a bulk operation instead of running .remove() on each document\n return Promise.all(docs.map(doc => doc.remove()));\n } else {\n return (docs as any).remove();\n }\n });\n }\n\n\n /**\n * helper function to transform RxQueryBase to RxQuery type\n */\n get asRxQuery(): RxQuery {\n return this as any;\n }\n\n /**\n * updates all found documents\n * @overwritten by plugin (optional)\n */\n update(_updateObj: any): Promise {\n throw pluginMissing('update');\n }\n\n\n // we only set some methods of query-builder here\n // because the others depend on these ones\n where(_queryObj: MangoQuerySelector | keyof RxDocType | string): RxQuery {\n throw pluginMissing('query-builder');\n }\n sort(_params: string | MangoQuerySortPart): RxQuery {\n throw pluginMissing('query-builder');\n }\n skip(_amount: number | null): RxQuery {\n throw pluginMissing('query-builder');\n }\n limit(_amount: number | null): RxQuery {\n throw pluginMissing('query-builder');\n }\n\n enableLimitBuffer(bufferSize: number) {\n if (this._limitBufferSize !== null) {\n // Limit buffer has already been enabled, do nothing:\n return this;\n }\n if (this._lastExecStart !== 0) {\n console.error('Can\\'t use limit buffer if query has already executed');\n return this;\n }\n if (this.mangoQuery.skip || !this.mangoQuery.limit) {\n console.error('Right now, limit buffer only works on non-skip, limit queries.');\n return this;\n }\n this._limitBufferSize = bufferSize;\n return this;\n }\n}\n\nexport function _getDefaultQuery(): MangoQuery {\n return {\n selector: {}\n };\n}\n\n/**\n * run this query through the QueryCache\n */\nexport function tunnelQueryCache(\n rxQuery: RxQueryBase\n): RxQuery {\n return rxQuery.collection._queryCache.getByQuery(rxQuery as any);\n}\n\nexport function createRxQuery(\n op: RxQueryOP,\n queryObj: MangoQuery,\n collection: RxCollection,\n other?: any\n) {\n runPluginHooks('preCreateRxQuery', {\n op,\n queryObj,\n collection,\n other\n });\n\n let ret = new RxQueryBase(op, queryObj, collection, other);\n\n // ensure when created with same params, only one is created\n ret = tunnelQueryCache(ret);\n triggerCacheReplacement(collection);\n\n return ret;\n}\n\n/**\n * Check if the current results-state is in sync with the database\n * which means that no write event happened since the last run.\n * @return false if not which means it should re-execute\n */\nfunction _isResultsInSync(rxQuery: RxQueryBase): boolean {\n const currentLatestEventNumber = rxQuery.asRxQuery.collection._changeEventBuffer.counter;\n if (rxQuery._latestChangeEvent >= currentLatestEventNumber) {\n return true;\n } else {\n return false;\n }\n}\n\n\n/**\n * wraps __ensureEqual()\n * to ensure it does not run in parallel\n * @return true if has changed, false if not\n */\nfunction _ensureEqual(rxQuery: RxQueryBase): Promise {\n // Optimisation shortcut\n if (\n rxQuery.collection.database.destroyed ||\n _isResultsInSync(rxQuery)\n ) {\n return PROMISE_RESOLVE_FALSE;\n }\n\n rxQuery._ensureEqualQueue = rxQuery._ensureEqualQueue\n .then(() => __ensureEqual(rxQuery));\n return rxQuery._ensureEqualQueue;\n}\n\n/**\n * ensures that the results of this query is equal to the results which a query over the database would give\n * @return true if results have changed\n */\nfunction __ensureEqual(rxQuery: RxQueryBase): Promise {\n rxQuery._lastEnsureEqual = now();\n\n /**\n * Optimisation shortcuts\n */\n if (\n // db is closed\n rxQuery.collection.database.destroyed ||\n // nothing happened since last run\n _isResultsInSync(rxQuery)\n ) {\n return PROMISE_RESOLVE_FALSE;\n }\n\n let ret = false;\n let mustReExec = false; // if this becomes true, a whole execution over the database is made\n if (rxQuery._latestChangeEvent === -1) {\n // have not executed yet -> must run\n mustReExec = true;\n }\n\n /**\n * try to use EventReduce to calculate the new results\n */\n if (!mustReExec) {\n const missedChangeEvents = rxQuery.asRxQuery.collection._changeEventBuffer.getFrom(rxQuery._latestChangeEvent + 1);\n if (missedChangeEvents === null) {\n // changeEventBuffer is of bounds -> we must re-execute over the database\n mustReExec = true;\n } else {\n rxQuery._latestChangeEvent = rxQuery.asRxQuery.collection._changeEventBuffer.counter;\n\n const runChangeEvents: RxChangeEvent[] = rxQuery.asRxQuery.collection\n ._changeEventBuffer\n .reduceByLastOfDoc(missedChangeEvents);\n\n if (rxQuery._limitBufferResults !== null) {\n // Check if any item in our limit buffer was modified by a change event\n for (const cE of runChangeEvents) {\n if (rxQuery._limitBufferResults.find((doc) => doc[rxQuery.collection.schema.primaryPath] === cE.documentId)) {\n // If so, the limit buffer is potential invalid -- let's just blow it up\n // TODO: could we instead update the documents in the limit buffer?\n rxQuery._limitBufferResults = null;\n break;\n }\n }\n }\n\n if (rxQuery.op === 'count') {\n // 'count' query\n const previousCount = ensureNotFalsy(rxQuery._result).count;\n let newCount = previousCount;\n runChangeEvents.forEach(cE => {\n const didMatchBefore = cE.previousDocumentData && rxQuery.doesDocumentDataMatch(cE.previousDocumentData);\n const doesMatchNow = rxQuery.doesDocumentDataMatch(cE.documentData);\n\n if (!didMatchBefore && doesMatchNow) {\n newCount++;\n }\n if (didMatchBefore && !doesMatchNow) {\n newCount--;\n }\n });\n if (newCount !== previousCount) {\n ret = true; // true because results changed\n rxQuery._setResultData(newCount as any);\n }\n } else {\n // 'find' or 'findOne' query\n const eventReduceResult = calculateNewResults(\n rxQuery as any,\n runChangeEvents\n );\n if (eventReduceResult.runFullQueryAgain) {\n // could not calculate the new results, execute must be done\n mustReExec = true;\n } else if (eventReduceResult.changed) {\n // we got the new results, we do not have to re-execute, mustReExec stays false\n ret = true; // true because results changed\n rxQuery._setResultData(eventReduceResult.newResults as any);\n }\n }\n }\n }\n\n\n\n // oh no we have to re-execute the whole query over the database\n if (mustReExec) {\n // counter can change while _execOverDatabase() is running so we save it here\n const latestAfter: number = (rxQuery as any).collection._changeEventBuffer.counter;\n return rxQuery._execOverDatabase()\n .then(newResultData => {\n rxQuery._latestChangeEvent = latestAfter;\n\n // A count query needs a different has-changed check.\n if (typeof newResultData === 'number') {\n if (\n !rxQuery._result ||\n newResultData !== rxQuery._result.count\n ) {\n ret = true;\n rxQuery._setResultData(newResultData as any);\n }\n return ret;\n }\n if (\n !rxQuery._result ||\n !areRxDocumentArraysEqual(\n rxQuery.collection.schema.primaryPath,\n newResultData,\n rxQuery._result.docsData\n )\n ) {\n ret = true; // true because results changed\n rxQuery._setResultData(newResultData as any);\n }\n return ret;\n });\n }\n return Promise.resolve(ret); // true if results have changed\n}\n\n/**\n * Runs the query over the storage instance\n * of the collection.\n * Does some optimizations to ensure findById is used\n * when specific queries are used.\n */\nexport async function queryCollection(\n rxQuery: RxQuery | RxQueryBase\n): Promise[]> {\n let docs: RxDocumentData[] = [];\n const collection = rxQuery.collection;\n\n /**\n * Optimizations shortcut.\n * If query is find-one-document-by-id,\n * then we do not have to use the slow query() method\n * but instead can use findDocumentsById()\n */\n if (rxQuery.isFindOneByIdQuery) {\n if (Array.isArray(rxQuery.isFindOneByIdQuery)) {\n let docIds = rxQuery.isFindOneByIdQuery;\n docIds = docIds.filter(docId => {\n // first try to fill from docCache\n const docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId);\n if (docData) {\n if (!docData._deleted) {\n docs.push(docData);\n }\n return false;\n } else {\n return true;\n }\n });\n // otherwise get from storage\n if (docIds.length > 0) {\n const docsMap = await collection.storageInstance.findDocumentsById(docIds, false);\n Object.values(docsMap).forEach(docData => {\n docs.push(docData);\n });\n }\n } else {\n const docId = rxQuery.isFindOneByIdQuery;\n\n // first try to fill from docCache\n let docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId);\n if (!docData) {\n // otherwise get from storage\n const docsMap = await collection.storageInstance.findDocumentsById([docId], false);\n if (docsMap.hasOwnProperty(docId)) {\n docData = docsMap[docId];\n }\n }\n if (docData && !docData._deleted) {\n docs.push(docData);\n }\n }\n } else {\n const preparedQuery = rxQuery.getPreparedQuery();\n const queryResult = await collection.storageInstance.query(preparedQuery);\n if (rxQuery._limitBufferSize !== null && rxQuery.mangoQuery.limit && queryResult.documents.length > rxQuery.mangoQuery.limit) {\n // If there are more than query.limit results, we pull out our buffer items from the\n // last rxQuery._limitBufferSize items of the results.\n rxQuery._limitBufferResults = queryResult.documents.splice(rxQuery.mangoQuery.limit);\n }\n docs = queryResult.documents;\n }\n return docs;\n\n}\n\n/**\n * Returns true if the given query\n * selects exactly one document by its id.\n * Used to optimize performance because these kind of\n * queries do not have to run over an index and can use get-by-id instead.\n * Returns false if no query of that kind.\n * Returns the document id otherwise.\n */\nexport function isFindOneByIdQuery(\n primaryPath: string,\n query: MangoQuery\n): false | string | string[] {\n // must have exactly one operator which must be $eq || $in\n if (\n !query.skip &&\n query.selector &&\n Object.keys(query.selector).length === 1 &&\n query.selector[primaryPath]\n ) {\n const value: any = query.selector[primaryPath];\n if (typeof value === 'string') {\n return value;\n } else if (\n Object.keys(value).length === 1 &&\n typeof value.$eq === 'string'\n ) {\n return value.$eq;\n }\n\n // same with $in string arrays\n if (\n Object.keys(value).length === 1 &&\n Array.isArray(value.$eq) &&\n // must only contain strings\n !(value.$eq as any[]).find(r => typeof r !== 'string')\n ) {\n return value.$eq;\n }\n }\n return false;\n}\n\n\n\nexport function isRxQuery(obj: any): boolean {\n return obj instanceof RxQueryBase;\n}\n"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"rx-query.js","names":["_rxjs","require","_operators","_utils","_rxError","_hooks","_eventReduce","_queryCache","_rxQueryHelper","_ohash","_queryCount","newQueryID","RxQueryBase","exports","op","mangoQuery","collection","other","id","_execOverDatabaseCount","_creationTime","now","_lastEnsureEqual","uncached","refCount$","BehaviorSubject","_result","_latestChangeEvent","_lastExecStart","_lastExecEnd","_limitBufferSize","_limitBufferResults","PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS","_persistedQueryCacheResult","undefined","_ensureEqualQueue","PROMISE_RESOLVE_FALSE","_getDefaultQuery","isFindOneByIdQuery","schema","primaryPath","_proto","prototype","_setResultData","newResultData","docsData","docsKeys","docsMap","Map","docsDataMap","count","docs","time","Array","from","values","map","docData","_docCache","getCachedRxDocument","doc","set","primary","_data","push","length","_execOverDatabase","Number","preparedQuery","getPreparedQuery","result","storageInstance","mode","database","allowSlowCount","newRxError","queryObj","ids","ensureNotFalsy","selector","$in","ret","mustBeQueried","forEach","getLatestDocumentDataIfExists","_deleted","findDocumentsById","Object","docsPromise","queryCollection","then","exec","throwIfMissing","name","query","_ensureEqual","firstValueFrom","$","toString","stringObj","sortObject","value","JSON","stringify","stringifyFilter","hookInput","rxQuery","normalizeMangoQuery","jsonSchema","limit","runPluginHooks","storage","statics","prepareQuery","doesDocumentDataMatch","queryMatcher","remove","isArray","Promise","all","update","_updateObj","pluginMissing","where","_queryObj","sort","_params","skip","_amount","enableLimitBuffer","bufferSize","console","error","enablePersistentQueryCache","backend","_queryCacheBackend","_queryCacheLimit","_persistedQueryCacheLoaded","_loadPersistedResultsIfTheyExist","key","_queryKey","getItem","documents","document","_createClass2","default","get","_$","results$","pipe","filter","changeEvent","isLocal","startWith","mergeMap","shareReplay","RXJS_SHARE_REPLAY_DEFAULTS","distinctUntilChanged","prev","curr","Boolean","useResult","slice","merge","normalizedQuery","overwriteGetterForCaching","getQueryMatcher","tunnelQueryCache","getByQuery","createRxQuery","triggerCacheReplacement","_isResultsInSync","currentLatestEventNumber","asRxQuery","_changeEventBuffer","counter","destroyed","__ensureEqual","mustReExec","missedChangeEvents","getFrom","runChangeEvents","reduceByLastOfDoc","_loop","cE","find","documentId","previousCount","newCount","didMatchBefore","previousDocumentData","doesMatchNow","documentData","_updatePersistentQueryCache","eventReduceResult","calculateNewResults","runFullQueryAgain","changed","newResults","latestAfter","areRxDocumentArraysEqual","returnValue","String","murmurHash","isCount","lwt","setItem","_queryCollectionByIds","docResults","docIds","docId","hasOwnProperty","persistedQueryCacheIds","Set","queryKeyValue","previousDocCount","size","changedDocs","getChangedDocumentsSince","changedDoc","delete","newDocCount","queryResult","splice","keys","$eq","r","isRxQuery","obj"],"sources":["../../src/rx-query.ts"],"sourcesContent":["import {\n BehaviorSubject,\n firstValueFrom,\n Observable,\n merge\n} from 'rxjs';\nimport {\n mergeMap,\n filter,\n map,\n startWith,\n distinctUntilChanged,\n shareReplay\n} from 'rxjs/operators';\nimport {\n sortObject,\n stringifyFilter,\n pluginMissing,\n overwriteGetterForCaching,\n now,\n PROMISE_RESOLVE_FALSE,\n RXJS_SHARE_REPLAY_DEFAULTS,\n ensureNotFalsy,\n areRxDocumentArraysEqual\n} from './plugins/utils';\nimport {\n newRxError\n} from './rx-error';\nimport {\n runPluginHooks\n} from './hooks';\nimport type {\n RxCollection,\n RxDocument,\n RxQueryOP,\n RxQuery,\n MangoQuery,\n MangoQuerySortPart,\n MangoQuerySelector,\n PreparedQuery,\n RxChangeEvent,\n RxDocumentWriteData,\n RxDocumentData,\n QueryMatcher\n} from './types';\nimport { calculateNewResults } from './event-reduce';\nimport { triggerCacheReplacement } from './query-cache';\nimport { getQueryMatcher, normalizeMangoQuery } from './rx-query-helper';\nimport {murmurHash} from 'ohash';\n\nexport interface QueryCacheBackend {\n getItem(key: string): Promise;\n setItem(key: string, value: T): Promise;\n}\n\nlet _queryCount = 0;\nconst newQueryID = function (): number {\n return ++_queryCount;\n};\n\nexport class RxQueryBase<\n RxDocType,\n // TODO also pass DocMethods here\n RxQueryResult = RxDocument[] | RxDocument\n> {\n public id: number = newQueryID();\n\n /**\n * Some stats then are used for debugging and cache replacement policies\n */\n public _execOverDatabaseCount: number = 0;\n public _creationTime = now();\n\n // used in the query-cache to determine if the RxQuery can be cleaned up.\n public _lastEnsureEqual = 0;\n\n public uncached = false;\n\n // used to count the subscribers to the query\n public refCount$ = new BehaviorSubject(null);\n\n public isFindOneByIdQuery: false | string | string[];\n\n /**\n * Contains the current result state\n * or null if query has not run yet.\n */\n public _result: {\n docsData: RxDocumentData[];\n // A key->document map, used in the event reduce optimization.\n docsDataMap: Map;\n docsKeys: string[];\n docsMap: Map>;\n docs: RxDocument[];\n count: number;\n /**\n * Time at which the current _result state was created.\n * Used to determine if the result set has changed since X\n * so that we do not emit the same result multiple times on subscription.\n */\n time: number;\n } | null = null;\n\n\n constructor(\n public op: RxQueryOP,\n public mangoQuery: Readonly>,\n public collection: RxCollection,\n // used by some plugins\n public other: any = {}\n ) {\n if (!mangoQuery) {\n this.mangoQuery = _getDefaultQuery();\n }\n\n this.isFindOneByIdQuery = isFindOneByIdQuery(\n this.collection.schema.primaryPath as string,\n mangoQuery\n );\n }\n get $(): BehaviorSubject {\n if (!this._$) {\n\n const results$ = this.collection.$.pipe(\n /**\n * Performance shortcut.\n * Changes to local documents are not relevant for the query.\n */\n filter(changeEvent => !changeEvent.isLocal),\n /**\n * Start once to ensure the querying also starts\n * when there were no changes.\n */\n startWith(null),\n // ensure query results are up-to-date.\n mergeMap(() => _ensureEqual(this as any)),\n // use the current result set, written by _ensureEqual().\n map(() => this._result),\n // do not run stuff above for each new subscriber, only once.\n shareReplay(RXJS_SHARE_REPLAY_DEFAULTS),\n // do not proceed if result set has not changed.\n distinctUntilChanged((prev, curr) => {\n return Boolean(prev && prev.time === ensureNotFalsy(curr).time);\n }),\n filter(result => !!result),\n /**\n * Map the result set to a single RxDocument or an array,\n * depending on query type\n */\n map((result) => {\n const useResult = ensureNotFalsy(result);\n if (this.op === 'count') {\n return useResult.count;\n } else if (this.op === 'findOne') {\n // findOne()-queries emit RxDocument or null\n return useResult.docs.length === 0 ? null : useResult.docs[0];\n } else if (this.op === 'findByIds') {\n return useResult.docsMap;\n } else {\n // find()-queries emit RxDocument[]\n // Flat copy the array, so it won't matter if the user modifies it.\n return useResult.docs.slice(0);\n }\n })\n );\n\n this._$ = merge(\n results$,\n /**\n * Also add the refCount$ to the query observable\n * to allow us to count the amount of subscribers.\n */\n this.refCount$.pipe(\n filter(() => false)\n )\n );\n }\n return this._$ as any;\n }\n\n\n // stores the changeEvent-number of the last handled change-event\n public _latestChangeEvent: -1 | number = -1;\n\n // time stamps on when the last full exec over the database has run\n // used to properly handle events that happen while the find-query is running\n public _lastExecStart: number = 0;\n public _lastExecEnd: number = 0;\n\n // Fields used for the Limit Buffer when enabled:\n public _limitBufferSize: number | null = null;\n public _limitBufferResults: RxDocumentData[] | null = null;\n\n // Fields used for the persistent query cache when enabled:\n private PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS = 1_000;\n public _persistedQueryCacheResult?: string[] | string = undefined;\n public _persistedQueryCacheLoaded?: Promise;\n public _queryCacheLimit?: number;\n public _queryCacheBackend?: QueryCacheBackend;\n\n /**\n * ensures that the exec-runs\n * are not run in parallel\n */\n public _ensureEqualQueue: Promise = PROMISE_RESOLVE_FALSE;\n\n /**\n * Returns an observable that emits the results\n * This should behave like an rxjs-BehaviorSubject which means:\n * - Emit the current result-set on subscribe\n * - Emit the new result-set when an RxChangeEvent comes in\n * - Do not emit anything before the first result-set was created (no null)\n */\n public _$?: Observable;\n\n /**\n * set the new result-data as result-docs of the query\n * @param newResultData json-docs that were received from the storage\n */\n _setResultData(newResultData: RxDocumentData[] | number | Map>): void {\n if (typeof newResultData === 'number') {\n this._result = {\n docsData: [],\n docsKeys: [],\n docsMap: new Map(),\n docsDataMap: new Map(),\n count: newResultData,\n docs: [],\n time: now()\n };\n return;\n } else if (newResultData instanceof Map) {\n newResultData = Array.from((newResultData as Map>).values());\n }\n\n const docsDataMap = new Map();\n const docsMap = new Map();\n\n\n const docs = newResultData.map(docData => this.collection._docCache.getCachedRxDocument(docData));\n\n /**\n * Instead of using the newResultData in the result cache,\n * we directly use the objects that are stored in the RxDocument\n * to ensure we do not store the same data twice and fill up the memory.\n */\n const docsKeys: string[] = [];\n const docsData = docs.map(doc => {\n docsDataMap.set(doc.primary, doc._data);\n docsMap.set(doc.primary, doc);\n docsKeys.push(doc.primary);\n return doc._data;\n });\n\n this._result = {\n docsData,\n docsKeys,\n docsMap,\n docsDataMap,\n count: docsData.length,\n docs,\n time: now()\n };\n }\n\n /**\n * executes the query on the database\n * @return results-array with document-data\n */\n async _execOverDatabase(): Promise[] | number> {\n this._execOverDatabaseCount = this._execOverDatabaseCount + 1;\n this._lastExecStart = now();\n\n if (this.op === 'count') {\n // if we have a persisted query cache result, use the result\n if (this._persistedQueryCacheResult) {\n // TODO: correct this number, but how?\n return Number(this._persistedQueryCacheResult);\n }\n\n const preparedQuery = this.getPreparedQuery();\n const result = await this.collection.storageInstance.count(preparedQuery);\n if (result.mode === 'slow' && !this.collection.database.allowSlowCount) {\n throw newRxError('QU14', {\n collection: this.collection,\n queryObj: this.mangoQuery\n });\n } else {\n return result.count;\n }\n }\n\n if (this.op === 'findByIds') {\n const ids: string[] = ensureNotFalsy(this.mangoQuery.selector as any)[this.collection.schema.primaryPath].$in;\n const ret = new Map>();\n const mustBeQueried: string[] = [];\n // first try to fill from docCache\n ids.forEach(id => {\n const docData = this.collection._docCache.getLatestDocumentDataIfExists(id);\n if (docData) {\n if (!docData._deleted) {\n const doc = this.collection._docCache.getCachedRxDocument(docData);\n ret.set(id, doc);\n }\n } else {\n mustBeQueried.push(id);\n }\n });\n // everything which was not in docCache must be fetched from the storage\n if (mustBeQueried.length > 0) {\n const docs = await this.collection.storageInstance.findDocumentsById(mustBeQueried, false);\n Object.values(docs).forEach(docData => {\n const doc = this.collection._docCache.getCachedRxDocument(docData);\n ret.set(doc.primary, doc);\n });\n }\n return ret as any;\n }\n\n const docsPromise = queryCollection(this as any);\n return await docsPromise.then(docs => {\n this._lastExecEnd = now();\n return docs;\n });\n }\n\n /**\n * Execute the query\n * To have an easier implementations,\n * just subscribe and use the first result\n */\n public exec(throwIfMissing: true): Promise>;\n public exec(): Promise;\n public async exec(throwIfMissing?: boolean): Promise {\n if (throwIfMissing && this.op !== 'findOne') {\n throw newRxError('QU9', {\n collection: this.collection.name,\n query: this.mangoQuery,\n op: this.op\n });\n }\n\n /**\n * run _ensureEqual() here,\n * this will make sure that errors in the query which throw inside of the RxStorage,\n * will be thrown at this execution context and not in the background.\n */\n await _ensureEqual(this);\n const result = await firstValueFrom(this.$);\n if (!result && throwIfMissing) {\n throw newRxError('QU10', {\n collection: this.collection.name,\n query: this.mangoQuery,\n op: this.op\n });\n } else {\n return result;\n }\n }\n\n /**\n * cached call to get the queryMatcher\n * @overwrites itself with the actual value\n */\n get queryMatcher(): QueryMatcher> {\n const schema = this.collection.schema.jsonSchema;\n const normalizedQuery = normalizeMangoQuery(\n this.collection.schema.jsonSchema,\n this.mangoQuery\n );\n return overwriteGetterForCaching(\n this,\n 'queryMatcher',\n getQueryMatcher(\n schema,\n normalizedQuery\n ) as any\n );\n }\n\n /**\n * returns a string that is used for equal-comparisons\n * @overwrites itself with the actual value\n */\n toString(): string {\n const stringObj = sortObject({\n op: this.op,\n query: this.mangoQuery,\n other: this.other\n }, true);\n const value = JSON.stringify(stringObj, stringifyFilter);\n this.toString = () => value;\n return value;\n }\n\n /**\n * returns the prepared query\n * which can be sent to the storage instance to query for documents.\n * @overwrites itself with the actual value.\n */\n getPreparedQuery(): PreparedQuery {\n const hookInput = {\n rxQuery: this,\n // can be mutated by the hooks so we have to deep clone first.\n mangoQuery: normalizeMangoQuery(\n this.collection.schema.jsonSchema,\n this.mangoQuery\n )\n };\n\n if (this._limitBufferSize !== null && hookInput.mangoQuery.limit) {\n hookInput.mangoQuery.limit = hookInput.mangoQuery.limit + this._limitBufferSize;\n }\n\n runPluginHooks('prePrepareQuery', hookInput);\n\n const value = this.collection.database.storage.statics.prepareQuery(\n this.collection.schema.jsonSchema,\n hookInput.mangoQuery\n );\n\n this.getPreparedQuery = () => value;\n return value;\n }\n\n /**\n * returns true if the document matches the query,\n * does not use the 'skip' and 'limit'\n */\n doesDocumentDataMatch(docData: RxDocType | any): boolean {\n // if doc is deleted, it cannot match\n if (docData._deleted) {\n return false;\n }\n\n return this.queryMatcher(docData);\n }\n\n /**\n * deletes all found documents\n * @return promise with deleted documents\n */\n remove(): Promise {\n return this\n .exec()\n .then(docs => {\n if (Array.isArray(docs)) {\n // TODO use a bulk operation instead of running .remove() on each document\n return Promise.all(docs.map(doc => doc.remove()));\n } else {\n return (docs as any).remove();\n }\n });\n }\n\n /**\n * helper function to transform RxQueryBase to RxQuery type\n */\n get asRxQuery(): RxQuery {\n return this as any;\n }\n\n /**\n * updates all found documents\n * @overwritten by plugin (optional)\n */\n update(_updateObj: any): Promise {\n throw pluginMissing('update');\n }\n\n // we only set some methods of query-builder here\n // because the others depend on these\n where(_queryObj: MangoQuerySelector | keyof RxDocType | string): RxQuery {\n throw pluginMissing('query-builder');\n }\n sort(_params: string | MangoQuerySortPart): RxQuery {\n throw pluginMissing('query-builder');\n }\n skip(_amount: number | null): RxQuery {\n throw pluginMissing('query-builder');\n }\n limit(_amount: number | null): RxQuery {\n throw pluginMissing('query-builder');\n }\n\n enableLimitBuffer(bufferSize: number) {\n if (this._limitBufferSize !== null) {\n // Limit buffer has already been enabled, do nothing:\n return this;\n }\n if (this._lastExecStart !== 0) {\n console.error('Can\\'t use limit buffer if query has already executed');\n return this;\n }\n if (this.mangoQuery.skip || !this.mangoQuery.limit) {\n console.error('Right now, limit buffer only works on non-skip, limit queries.');\n return this;\n }\n this._limitBufferSize = bufferSize;\n return this;\n }\n\n enablePersistentQueryCache(backend: QueryCacheBackend, limit: number = this.PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS) {\n this._queryCacheBackend = backend;\n this._queryCacheLimit = limit;\n\n this._persistedQueryCacheLoaded = this._loadPersistedResultsIfTheyExist();\n return this;\n }\n\n private async _loadPersistedResultsIfTheyExist() {\n if (!this._queryCacheBackend) {\n return;\n }\n\n if (this._persistedQueryCacheResult) {\n return;\n }\n\n const key = _queryKey(this);\n const value = await this._queryCacheBackend.getItem(`qc:${key}`);\n\n this._persistedQueryCacheResult = value ?? undefined;\n\n // if this is a regular query, also load documents into cache\n if (Array.isArray(value) && value.length > 0) {\n this._persistedQueryCacheResult = value;\n\n /**\n * TODO: try to move this to where the query execution happens, maybe lazy loading is even better, but if\n * query definition and data loading is almost happening at the same time, this is the more simple\n * implementation.\n */\n const documents = await this.collection.storageInstance\n .findDocumentsById(value, false);\n for (const document of Object.values(documents)) {\n this.collection._docCache.getCachedRxDocument(document);\n }\n }\n }\n}\n\nexport function _getDefaultQuery(): MangoQuery {\n return {\n selector: {}\n };\n}\n\n/**\n * run this query through the QueryCache\n */\nexport function tunnelQueryCache(\n rxQuery: RxQueryBase\n): RxQuery {\n return rxQuery.collection._queryCache.getByQuery(rxQuery as any);\n}\n\nexport function createRxQuery(\n op: RxQueryOP,\n queryObj: MangoQuery,\n collection: RxCollection,\n other?: any\n) {\n runPluginHooks('preCreateRxQuery', {\n op,\n queryObj,\n collection,\n other\n });\n\n let ret = new RxQueryBase(op, queryObj, collection, other);\n\n // ensure when created with same params, only one is created\n ret = tunnelQueryCache(ret);\n // TODO: clear persistent query cache as well\n triggerCacheReplacement(collection);\n\n return ret;\n}\n\n/**\n * Check if the current results-state is in sync with the database\n * which means that no writes event happened since the last run.\n * @return false if not which means it should re-execute\n */\nfunction _isResultsInSync(rxQuery: RxQueryBase): boolean {\n const currentLatestEventNumber = rxQuery.asRxQuery.collection._changeEventBuffer.counter;\n return rxQuery._latestChangeEvent >= currentLatestEventNumber;\n}\n\n/**\n * wraps __ensureEqual()\n * to ensure it does not run in parallel\n * @return true if it has changed, false if not\n */\nfunction _ensureEqual(rxQuery: RxQueryBase): Promise {\n // Optimisation shortcut\n if (\n rxQuery.collection.database.destroyed ||\n _isResultsInSync(rxQuery)\n ) {\n return PROMISE_RESOLVE_FALSE;\n }\n\n rxQuery._ensureEqualQueue = rxQuery._ensureEqualQueue\n .then(() => __ensureEqual(rxQuery));\n return rxQuery._ensureEqualQueue;\n}\n\n/**\n * ensures that the results of this query is equal to the results which a query over the database would give\n * @return true if results have changed\n */\nasync function __ensureEqual(rxQuery: RxQueryBase): Promise {\n await rxQuery._persistedQueryCacheLoaded;\n\n rxQuery._lastEnsureEqual = now();\n\n /**\n * Optimisation shortcuts\n */\n if (\n // db is closed\n rxQuery.collection.database.destroyed ||\n // nothing happened since last run\n _isResultsInSync(rxQuery)\n ) {\n return PROMISE_RESOLVE_FALSE;\n }\n\n let ret = false;\n let mustReExec = false; // if this becomes true, a whole execution over the database is made\n if (rxQuery._latestChangeEvent === -1) {\n // have not executed yet -> must run\n mustReExec = true;\n }\n\n /**\n * try to use EventReduce to calculate the new results\n */\n if (!mustReExec) {\n const missedChangeEvents = rxQuery.asRxQuery.collection._changeEventBuffer.getFrom(rxQuery._latestChangeEvent + 1);\n if (missedChangeEvents === null) {\n // changeEventBuffer is of bounds -> we must re-execute over the database\n mustReExec = true;\n } else {\n rxQuery._latestChangeEvent = rxQuery.asRxQuery.collection._changeEventBuffer.counter;\n\n const runChangeEvents: RxChangeEvent[] = rxQuery.asRxQuery.collection\n ._changeEventBuffer\n .reduceByLastOfDoc(missedChangeEvents);\n\n if (rxQuery._limitBufferResults !== null) {\n // Check if any item in our limit buffer was modified by a change event\n for (const cE of runChangeEvents) {\n if (rxQuery._limitBufferResults.find((doc) => doc[rxQuery.collection.schema.primaryPath] === cE.documentId)) {\n // If so, the limit buffer is potential invalid -- let's just blow it up\n // TODO: could we instead update the documents in the limit buffer?\n rxQuery._limitBufferResults = null;\n break;\n }\n }\n }\n\n if (rxQuery.op === 'count') {\n // 'count' query\n const previousCount = ensureNotFalsy(rxQuery._result).count;\n let newCount = previousCount;\n runChangeEvents.forEach(cE => {\n const didMatchBefore = cE.previousDocumentData && rxQuery.doesDocumentDataMatch(cE.previousDocumentData);\n const doesMatchNow = rxQuery.doesDocumentDataMatch(cE.documentData);\n\n if (!didMatchBefore && doesMatchNow) {\n newCount++;\n }\n if (didMatchBefore && !doesMatchNow) {\n newCount--;\n }\n });\n if (newCount !== previousCount) {\n ret = true; // true because results changed\n rxQuery._setResultData(newCount as any);\n await _updatePersistentQueryCache(rxQuery);\n }\n } else {\n // 'find' or 'findOne' query\n const eventReduceResult = calculateNewResults(\n rxQuery as any,\n runChangeEvents\n );\n if (eventReduceResult.runFullQueryAgain) {\n // could not calculate the new results, execute must be done\n mustReExec = true;\n } else if (eventReduceResult.changed) {\n // we got the new results, we do not have to re-execute, mustReExec stays false\n ret = true; // true because results changed\n rxQuery._setResultData(eventReduceResult.newResults as any);\n await _updatePersistentQueryCache(rxQuery);\n }\n }\n }\n }\n\n // oh, no we have to re-execute the whole query over the database\n if (mustReExec) {\n // counter can change while _execOverDatabase() is running so we save it here\n const latestAfter: number = (rxQuery as any).collection._changeEventBuffer.counter;\n return rxQuery._execOverDatabase()\n .then((newResultData) => {\n rxQuery._latestChangeEvent = latestAfter;\n\n // A count query needs a different has-changed check.\n if (typeof newResultData === 'number') {\n if (\n !rxQuery._result ||\n newResultData !== rxQuery._result.count\n ) {\n ret = true;\n rxQuery._setResultData(newResultData as any);\n }\n return ret;\n }\n if (\n !rxQuery._result ||\n !areRxDocumentArraysEqual(\n rxQuery.collection.schema.primaryPath,\n newResultData,\n rxQuery._result.docsData\n )\n ) {\n ret = true; // true because results changed\n rxQuery._setResultData(newResultData as any);\n }\n return ret;\n })\n .then(async (returnValue) => {\n await _updatePersistentQueryCache(rxQuery);\n return returnValue;\n });\n }\n\n return ret; // true if results have changed\n}\n\nfunction _queryKey(rxQuery: RxQueryBase) {\n return String(murmurHash(rxQuery.toString(), 42));\n}\n\nasync function _updatePersistentQueryCache(rxQuery: RxQueryBase) {\n if (!rxQuery._queryCacheBackend) {\n return;\n }\n\n const backend = rxQuery._queryCacheBackend;\n\n const isCount = rxQuery._result?.docs.length === 0 && rxQuery._result.count > 0;\n\n const key = _queryKey(rxQuery);\n const value = isCount\n ? rxQuery._result?.count?.toString() ?? '0'\n : rxQuery._result?.docsKeys ?? [];\n\n // update _persistedQueryCacheResult\n rxQuery._persistedQueryCacheResult = value;\n\n // persist query cache\n const lwt = rxQuery._result?.time ?? 0;\n await backend.setItem(`qc:${String(key)}`, value);\n await backend.setItem(`qc:${String(key)}:lwt`, lwt.toString());\n}\n\n// Refactored out of `queryCollection`: modifies the docResults array to fill it with data\nasync function _queryCollectionByIds(rxQuery: RxQuery | RxQueryBase, docResults: RxDocumentData[], docIds: string[]) {\n const collection = rxQuery.collection;\n docIds = docIds.filter(docId => {\n // first try to fill from docCache\n const docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId);\n if (docData) {\n if (!docData._deleted) {\n docResults.push(docData);\n }\n return false;\n } else {\n return true;\n }\n });\n\n // otherwise get from storage\n if (docIds.length > 0) {\n const docsMap = await collection.storageInstance.findDocumentsById(docIds, false);\n Object.values(docsMap).forEach(docData => {\n docResults.push(docData);\n });\n }\n}\n\n/**\n * Runs the query over the storage instance\n * of the collection.\n * Does some optimizations to ensure findById is used\n * when specific queries are used.\n */\nexport async function queryCollection(\n rxQuery: RxQuery | RxQueryBase\n): Promise[]> {\n await rxQuery._persistedQueryCacheLoaded;\n\n let docs: RxDocumentData[] = [];\n const collection = rxQuery.collection;\n\n /**\n * Optimizations shortcut.\n * If query is find-one-document-by-id,\n * then we do not have to use the slow query() method\n * but instead can use findDocumentsById()\n */\n if (rxQuery.isFindOneByIdQuery) {\n if (Array.isArray(rxQuery.isFindOneByIdQuery)) {\n await _queryCollectionByIds(rxQuery, docs, rxQuery.isFindOneByIdQuery);\n } else {\n const docId = rxQuery.isFindOneByIdQuery;\n\n // first try to fill from docCache\n let docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId);\n if (!docData) {\n // otherwise get from storage\n const docsMap = await collection.storageInstance.findDocumentsById([docId], false);\n if (docsMap.hasOwnProperty(docId)) {\n docData = docsMap[docId];\n }\n }\n if (docData && !docData._deleted) {\n docs.push(docData);\n }\n }\n return docs;\n }\n\n if (((rxQuery.mangoQuery.limit && !rxQuery.mangoQuery.skip) || !rxQuery.mangoQuery.limit)\n && rxQuery._queryCacheBackend\n && Array.isArray(rxQuery._persistedQueryCacheResult)) {\n const persistedQueryCacheIds = new Set(rxQuery._persistedQueryCacheResult);\n const primaryPath = rxQuery.collection.schema.primaryPath;\n const queryKeyValue = _queryKey(rxQuery as RxQueryBase);\n const lwt = (await rxQuery._queryCacheBackend.getItem(`qc:${queryKeyValue}:lwt`)) as string ?? 0;\n const previousDocCount = rxQuery._result?.docsKeys?.length ?? persistedQueryCacheIds.size;\n\n // query all docs updated > last persisted, limit to an arbitrary 1_000_000 (10x of what we consider our largest library)\n const {documents: changedDocs} = await collection.storageInstance.getChangedDocumentsSince(\n 1_000_000,\n {id: '', lwt }\n );\n for (const changedDoc of changedDocs) {\n /*\n * no need to fetch again, we already got the doc from the list of changed docs, and therefore we filter\n * deleted docs as well\n */\n persistedQueryCacheIds.delete(changedDoc[primaryPath] as string);\n\n // ignore deleted docs or docs that do not match the query\n if (!rxQuery.doesDocumentDataMatch(changedDoc)) {\n continue;\n }\n\n // doc should be in result\n docs.push(changedDoc);\n }\n\n // If doc count does not match the number of docs previously returned by the query\n // 1. try to pull data from the limit buffer\n // 2. re-query data\n // TODO: How can I utilize the limitBuffer?\n const newDocCount = docs.length + persistedQueryCacheIds.size;\n if (newDocCount === previousDocCount) {\n // fetch remaining doc ids and add to result\n await _queryCollectionByIds(rxQuery, docs, Array.from(persistedQueryCacheIds));\n return docs;\n }\n }\n\n const preparedQuery = rxQuery.getPreparedQuery();\n const queryResult = await collection.storageInstance.query(preparedQuery);\n if (rxQuery._limitBufferSize !== null && rxQuery.mangoQuery.limit && queryResult.documents.length > rxQuery.mangoQuery.limit) {\n // If there are more than query.limit results, we pull out our buffer items from the\n // last rxQuery._limitBufferSize items of the results.\n rxQuery._limitBufferResults = queryResult.documents.splice(rxQuery.mangoQuery.limit);\n }\n docs = queryResult.documents;\n\n return docs;\n\n}\n\n/**\n * Returns true if the given query\n * selects exactly one document by its id.\n * Used to optimize performance because these kind of\n * queries do not have to run over an index and can use get-by-id instead.\n * Returns false if no query of that kind.\n * Returns the document id otherwise.\n */\nexport function isFindOneByIdQuery(\n primaryPath: string,\n query: MangoQuery\n): false | string | string[] {\n // must have exactly one operator which must be $eq || $in\n if (\n !query.skip &&\n query.selector &&\n Object.keys(query.selector).length === 1 &&\n query.selector[primaryPath]\n ) {\n const value: any = query.selector[primaryPath];\n if (typeof value === 'string') {\n return value;\n } else if (\n Object.keys(value).length === 1 &&\n typeof value.$eq === 'string'\n ) {\n return value.$eq;\n }\n\n // same with $in string arrays\n if (\n Object.keys(value).length === 1 &&\n Array.isArray(value.$eq) &&\n // must only contain strings\n !(value.$eq as any[]).find(r => typeof r !== 'string')\n ) {\n return value.$eq;\n }\n }\n return false;\n}\n\nexport function isRxQuery(obj: any): boolean {\n return obj instanceof RxQueryBase;\n}\n"],"mappings":"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"} \ No newline at end of file diff --git a/dist/types/rx-query.d.ts b/dist/types/rx-query.d.ts index 810999e7b11..2232f28d5f4 100644 --- a/dist/types/rx-query.d.ts +++ b/dist/types/rx-query.d.ts @@ -1,5 +1,6 @@ import { BehaviorSubject, Observable } from 'rxjs'; import type { RxCollection, RxDocument, RxQueryOP, RxQuery, MangoQuery, MangoQuerySortPart, MangoQuerySelector, PreparedQuery, RxDocumentWriteData, RxDocumentData, QueryMatcher } from './types'; +import {QueryCacheBackend} from "../../src"; export declare class RxQueryBase[] | RxDocument> { op: RxQueryOP; mangoQuery: Readonly>; @@ -109,6 +110,7 @@ export declare class RxQueryBase; limit(_amount: number | null): RxQuery; enableLimitBuffer(bufferSize: number): this; + enablePersistentQueryCache(backend: QueryCacheBackend): this; } export declare function _getDefaultQuery(): MangoQuery; /** diff --git a/dist/types/types/rx-query.d.ts b/dist/types/types/rx-query.d.ts index d8736d83269..03a685465ae 100644 --- a/dist/types/types/rx-query.d.ts +++ b/dist/types/types/rx-query.d.ts @@ -129,6 +129,8 @@ export type MangoQuery = MangoQueryNoLimit & { export type RxQueryOP = 'find' | 'findOne' | 'count' | 'findByIds'; export declare class RxQuery extends RxQueryBase { + [x: string]: any; + [x: string]: any; equals(queryObj: any): RxQuery; eq(queryObj: any): RxQuery; or(queryObj: keyof RxDocumentType | string | any[]): RxQuery; diff --git a/src/plugins/storage-memory/memory-types.ts b/src/plugins/storage-memory/memory-types.ts index cd10be861f0..99f9a9d78d6 100644 --- a/src/plugins/storage-memory/memory-types.ts +++ b/src/plugins/storage-memory/memory-types.ts @@ -31,7 +31,7 @@ export type MemoryStorageInternalsByIndex = { */ export type MemoryStorageInternals = { /** - * We re-use the memory state when multiple instances + * We reuse the memory state when multiple instances * are created with the same params. * If refCount becomes 0, we can delete the state. */ diff --git a/src/plugins/utils/utils-object.ts b/src/plugins/utils/utils-object.ts index c443f703e83..d48d955b185 100644 --- a/src/plugins/utils/utils-object.ts +++ b/src/plugins/utils/utils-object.ts @@ -31,7 +31,7 @@ export function deepFreeze(o: T): T { * RxDB normally uses the 'dot-prop' npm module. * But when performance is really relevant, this is not fast enough. * Instead we use a monad that can prepare some stuff up front - * and we can re-use the generated function. + * and we can reuse the generated function. */ export type ObjectPathMonadFunction = (obj: T) => R; export function objectPathMonad(objectPath: string): ObjectPathMonadFunction { diff --git a/src/rx-query.ts b/src/rx-query.ts index 0dc319b4137..1c750ac5175 100644 --- a/src/rx-query.ts +++ b/src/rx-query.ts @@ -46,6 +46,12 @@ import type { import { calculateNewResults } from './event-reduce'; import { triggerCacheReplacement } from './query-cache'; import { getQueryMatcher, normalizeMangoQuery } from './rx-query-helper'; +import {murmurHash} from 'ohash'; + +export interface QueryCacheBackend { + getItem(key: string): Promise; + setItem(key: string, value: T): Promise; +} let _queryCount = 0; const newQueryID = function (): number { @@ -57,7 +63,6 @@ export class RxQueryBase< // TODO also pass DocMethods here RxQueryResult = RxDocument[] | RxDocument > { - public id: number = newQueryID(); /** @@ -76,7 +81,6 @@ export class RxQueryBase< public isFindOneByIdQuery: false | string | string[]; - /** * Contains the current result state * or null if query has not run yet. @@ -85,6 +89,7 @@ export class RxQueryBase< docsData: RxDocumentData[]; // A key->document map, used in the event reduce optimization. docsDataMap: Map; + docsKeys: string[]; docsMap: Map>; docs: RxDocument[]; count: number; @@ -124,10 +129,10 @@ export class RxQueryBase< filter(changeEvent => !changeEvent.isLocal), /** * Start once to ensure the querying also starts - * when there where no changes. + * when there were no changes. */ startWith(null), - // ensure query results are up to date. + // ensure query results are up-to-date. mergeMap(() => _ensureEqual(this as any)), // use the current result set, written by _ensureEqual(). map(() => this._result), @@ -135,11 +140,7 @@ export class RxQueryBase< shareReplay(RXJS_SHARE_REPLAY_DEFAULTS), // do not proceed if result set has not changed. distinctUntilChanged((prev, curr) => { - if (prev && prev.time === ensureNotFalsy(curr).time) { - return true; - } else { - return false; - } + return Boolean(prev && prev.time === ensureNotFalsy(curr).time); }), filter(result => !!result), /** @@ -157,7 +158,7 @@ export class RxQueryBase< return useResult.docsMap; } else { // find()-queries emit RxDocument[] - // Flat copy the array so it won't matter if the user modifies it. + // Flat copy the array, so it won't matter if the user modifies it. return useResult.docs.slice(0); } }) @@ -190,6 +191,13 @@ export class RxQueryBase< public _limitBufferSize: number | null = null; public _limitBufferResults: RxDocumentData[] | null = null; + // Fields used for the persistent query cache when enabled: + private PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS = 1_000; + public _persistedQueryCacheResult?: string[] | string = undefined; + public _persistedQueryCacheLoaded?: Promise; + public _queryCacheLimit?: number; + public _queryCacheBackend?: QueryCacheBackend; + /** * ensures that the exec-runs * are not run in parallel @@ -213,6 +221,7 @@ export class RxQueryBase< if (typeof newResultData === 'number') { this._result = { docsData: [], + docsKeys: [], docsMap: new Map(), docsDataMap: new Map(), count: newResultData, @@ -235,14 +244,17 @@ export class RxQueryBase< * 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. */ + const docsKeys: string[] = []; const docsData = docs.map(doc => { docsDataMap.set(doc.primary, doc._data); docsMap.set(doc.primary, doc); + docsKeys.push(doc.primary); return doc._data; }); this._result = { docsData, + docsKeys, docsMap, docsDataMap, count: docsData.length, @@ -259,8 +271,13 @@ export class RxQueryBase< this._execOverDatabaseCount = this._execOverDatabaseCount + 1; this._lastExecStart = now(); - if (this.op === 'count') { + // if we have a persisted query cache result, use the result + if (this._persistedQueryCacheResult) { + // TODO: correct this number, but how? + return Number(this._persistedQueryCacheResult); + } + const preparedQuery = this.getPreparedQuery(); const result = await this.collection.storageInstance.count(preparedQuery); if (result.mode === 'slow' && !this.collection.database.allowSlowCount) { @@ -300,9 +317,8 @@ export class RxQueryBase< return ret as any; } - const docsPromise = queryCollection(this as any); - return docsPromise.then(docs => { + return await docsPromise.then(docs => { this._lastExecEnd = now(); return docs; }); @@ -315,7 +331,7 @@ export class RxQueryBase< */ public exec(throwIfMissing: true): Promise>; public exec(): Promise; - public exec(throwIfMissing?: boolean): Promise { + public async exec(throwIfMissing?: boolean): Promise { if (throwIfMissing && this.op !== 'findOne') { throw newRxError('QU9', { collection: this.collection.name, @@ -324,29 +340,24 @@ export class RxQueryBase< }); } - /** * run _ensureEqual() here, * this will make sure that errors in the query which throw inside of the RxStorage, * will be thrown at this execution context and not in the background. */ - return _ensureEqual(this) - .then(() => firstValueFrom(this.$)) - .then(result => { - if (!result && throwIfMissing) { - throw newRxError('QU10', { - collection: this.collection.name, - query: this.mangoQuery, - op: this.op - }); - } else { - return result; - } + await _ensureEqual(this); + const result = await firstValueFrom(this.$); + if (!result && throwIfMissing) { + throw newRxError('QU10', { + collection: this.collection.name, + query: this.mangoQuery, + op: this.op }); + } else { + return result; + } } - - /** * cached call to get the queryMatcher * @overwrites itself with the actual value @@ -384,7 +395,7 @@ export class RxQueryBase< /** * returns the prepared query - * which can be send to the storage instance to query for documents. + * which can be sent to the storage instance to query for documents. * @overwrites itself with the actual value. */ getPreparedQuery(): PreparedQuery { @@ -442,7 +453,6 @@ export class RxQueryBase< }); } - /** * helper function to transform RxQueryBase to RxQuery type */ @@ -458,9 +468,8 @@ export class RxQueryBase< throw pluginMissing('update'); } - // we only set some methods of query-builder here - // because the others depend on these ones + // because the others depend on these where(_queryObj: MangoQuerySelector | keyof RxDocType | string): RxQuery { throw pluginMissing('query-builder'); } @@ -490,6 +499,45 @@ export class RxQueryBase< this._limitBufferSize = bufferSize; return this; } + + enablePersistentQueryCache(backend: QueryCacheBackend, limit: number = this.PERSISTENT_QUERY_CACHE_DEFAULT_MAX_ITEMS) { + this._queryCacheBackend = backend; + this._queryCacheLimit = limit; + + this._persistedQueryCacheLoaded = this._loadPersistedResultsIfTheyExist(); + return this; + } + + private async _loadPersistedResultsIfTheyExist() { + if (!this._queryCacheBackend) { + return; + } + + if (this._persistedQueryCacheResult) { + return; + } + + const key = _queryKey(this); + const value = await this._queryCacheBackend.getItem(`qc:${key}`); + + this._persistedQueryCacheResult = value ?? undefined; + + // if this is a regular query, also load documents into cache + if (Array.isArray(value) && value.length > 0) { + this._persistedQueryCacheResult = value; + + /** + * TODO: try to move this to where the query execution happens, maybe lazy loading is even better, but if + * query definition and data loading is almost happening at the same time, this is the more simple + * implementation. + */ + const documents = await this.collection.storageInstance + .findDocumentsById(value, false); + for (const document of Object.values(documents)) { + this.collection._docCache.getCachedRxDocument(document); + } + } + } } export function _getDefaultQuery(): MangoQuery { @@ -524,6 +572,7 @@ export function createRxQuery( // ensure when created with same params, only one is created ret = tunnelQueryCache(ret); + // TODO: clear persistent query cache as well triggerCacheReplacement(collection); return ret; @@ -531,23 +580,18 @@ export function createRxQuery( /** * Check if the current results-state is in sync with the database - * which means that no write event happened since the last run. + * which means that no writes event happened since the last run. * @return false if not which means it should re-execute */ function _isResultsInSync(rxQuery: RxQueryBase): boolean { const currentLatestEventNumber = rxQuery.asRxQuery.collection._changeEventBuffer.counter; - if (rxQuery._latestChangeEvent >= currentLatestEventNumber) { - return true; - } else { - return false; - } + return rxQuery._latestChangeEvent >= currentLatestEventNumber; } - /** * wraps __ensureEqual() * to ensure it does not run in parallel - * @return true if has changed, false if not + * @return true if it has changed, false if not */ function _ensureEqual(rxQuery: RxQueryBase): Promise { // Optimisation shortcut @@ -567,7 +611,9 @@ function _ensureEqual(rxQuery: RxQueryBase): Promise { * ensures that the results of this query is equal to the results which a query over the database would give * @return true if results have changed */ -function __ensureEqual(rxQuery: RxQueryBase): Promise { +async function __ensureEqual(rxQuery: RxQueryBase): Promise { + await rxQuery._persistedQueryCacheLoaded; + rxQuery._lastEnsureEqual = now(); /** @@ -634,6 +680,7 @@ function __ensureEqual(rxQuery: RxQueryBase): Promise(rxQuery: RxQueryBase): Promise { + .then((newResultData) => { rxQuery._latestChangeEvent = latestAfter; // A count query needs a different has-changed check. @@ -686,9 +732,66 @@ function __ensureEqual(rxQuery: RxQueryBase): Promise { + await _updatePersistentQueryCache(rxQuery); + return returnValue; }); } - return Promise.resolve(ret); // true if results have changed + + return ret; // true if results have changed +} + +function _queryKey(rxQuery: RxQueryBase) { + return String(murmurHash(rxQuery.toString(), 42)); +} + +async function _updatePersistentQueryCache(rxQuery: RxQueryBase) { + if (!rxQuery._queryCacheBackend) { + return; + } + + const backend = rxQuery._queryCacheBackend; + + const isCount = rxQuery._result?.docs.length === 0 && rxQuery._result.count > 0; + + const key = _queryKey(rxQuery); + const value = isCount + ? rxQuery._result?.count?.toString() ?? '0' + : rxQuery._result?.docsKeys ?? []; + + // update _persistedQueryCacheResult + rxQuery._persistedQueryCacheResult = value; + + // persist query cache + const lwt = rxQuery._result?.time ?? 0; + await backend.setItem(`qc:${String(key)}`, value); + await backend.setItem(`qc:${String(key)}:lwt`, lwt.toString()); +} + +// Refactored out of `queryCollection`: modifies the docResults array to fill it with data +async function _queryCollectionByIds(rxQuery: RxQuery | RxQueryBase, docResults: RxDocumentData[], docIds: string[]) { + const collection = rxQuery.collection; + docIds = docIds.filter(docId => { + // first try to fill from docCache + const docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId); + if (docData) { + if (!docData._deleted) { + docResults.push(docData); + } + return false; + } else { + return true; + } + }); + + // otherwise get from storage + if (docIds.length > 0) { + const docsMap = await collection.storageInstance.findDocumentsById(docIds, false); + Object.values(docsMap).forEach(docData => { + docResults.push(docData); + }); + } } /** @@ -700,6 +803,8 @@ function __ensureEqual(rxQuery: RxQueryBase): Promise( rxQuery: RxQuery | RxQueryBase ): Promise[]> { + await rxQuery._persistedQueryCacheLoaded; + let docs: RxDocumentData[] = []; const collection = rxQuery.collection; @@ -711,26 +816,7 @@ export async function queryCollection( */ if (rxQuery.isFindOneByIdQuery) { if (Array.isArray(rxQuery.isFindOneByIdQuery)) { - let docIds = rxQuery.isFindOneByIdQuery; - docIds = docIds.filter(docId => { - // first try to fill from docCache - const docData = rxQuery.collection._docCache.getLatestDocumentDataIfExists(docId); - if (docData) { - if (!docData._deleted) { - docs.push(docData); - } - return false; - } else { - return true; - } - }); - // otherwise get from storage - if (docIds.length > 0) { - const docsMap = await collection.storageInstance.findDocumentsById(docIds, false); - Object.values(docsMap).forEach(docData => { - docs.push(docData); - }); - } + await _queryCollectionByIds(rxQuery, docs, rxQuery.isFindOneByIdQuery); } else { const docId = rxQuery.isFindOneByIdQuery; @@ -747,16 +833,60 @@ export async function queryCollection( docs.push(docData); } } - } else { - const preparedQuery = rxQuery.getPreparedQuery(); - const queryResult = await collection.storageInstance.query(preparedQuery); - if (rxQuery._limitBufferSize !== null && rxQuery.mangoQuery.limit && queryResult.documents.length > rxQuery.mangoQuery.limit) { - // If there are more than query.limit results, we pull out our buffer items from the - // last rxQuery._limitBufferSize items of the results. - rxQuery._limitBufferResults = queryResult.documents.splice(rxQuery.mangoQuery.limit); + return docs; + } + + if (((rxQuery.mangoQuery.limit && !rxQuery.mangoQuery.skip) || !rxQuery.mangoQuery.limit) + && rxQuery._queryCacheBackend + && Array.isArray(rxQuery._persistedQueryCacheResult)) { + const persistedQueryCacheIds = new Set(rxQuery._persistedQueryCacheResult); + const primaryPath = rxQuery.collection.schema.primaryPath; + const queryKeyValue = _queryKey(rxQuery as RxQueryBase); + const lwt = (await rxQuery._queryCacheBackend.getItem(`qc:${queryKeyValue}:lwt`)) as string ?? 0; + const previousDocCount = rxQuery._result?.docsKeys?.length ?? persistedQueryCacheIds.size; + + // query all docs updated > last persisted, limit to an arbitrary 1_000_000 (10x of what we consider our largest library) + const {documents: changedDocs} = await collection.storageInstance.getChangedDocumentsSince( + 1_000_000, + {id: '', lwt } + ); + for (const changedDoc of changedDocs) { + /* + * no need to fetch again, we already got the doc from the list of changed docs, and therefore we filter + * deleted docs as well + */ + persistedQueryCacheIds.delete(changedDoc[primaryPath] as string); + + // ignore deleted docs or docs that do not match the query + if (!rxQuery.doesDocumentDataMatch(changedDoc)) { + continue; + } + + // doc should be in result + docs.push(changedDoc); + } + + // If doc count does not match the number of docs previously returned by the query + // 1. try to pull data from the limit buffer + // 2. re-query data + // TODO: How can I utilize the limitBuffer? + const newDocCount = docs.length + persistedQueryCacheIds.size; + if (newDocCount === previousDocCount) { + // fetch remaining doc ids and add to result + await _queryCollectionByIds(rxQuery, docs, Array.from(persistedQueryCacheIds)); + return docs; } - docs = queryResult.documents; } + + const preparedQuery = rxQuery.getPreparedQuery(); + const queryResult = await collection.storageInstance.query(preparedQuery); + if (rxQuery._limitBufferSize !== null && rxQuery.mangoQuery.limit && queryResult.documents.length > rxQuery.mangoQuery.limit) { + // If there are more than query.limit results, we pull out our buffer items from the + // last rxQuery._limitBufferSize items of the results. + rxQuery._limitBufferResults = queryResult.documents.splice(rxQuery.mangoQuery.limit); + } + docs = queryResult.documents; + return docs; } @@ -803,8 +933,6 @@ export function isFindOneByIdQuery( return false; } - - export function isRxQuery(obj: any): boolean { return obj instanceof RxQueryBase; } diff --git a/test/helper/cache.ts b/test/helper/cache.ts new file mode 100644 index 00000000000..c166c1e730c --- /dev/null +++ b/test/helper/cache.ts @@ -0,0 +1,22 @@ +import {QueryCacheBackend} from '../../src'; + +export class Cache implements QueryCacheBackend { + private items = new Map(); + + getItem(key: string) { + return this.items.get(key); + } + + async setItem(key: string, value: T) { + this.items.set(key, value); + return await Promise.resolve(value); + } + + get size() { + return this.items.size; + } + + getItems() { + return this.items; + } +} diff --git a/test/unit/rx-query.test.ts b/test/unit/rx-query.test.ts index a15de7e9060..34caa2d3d33 100644 --- a/test/unit/rx-query.test.ts +++ b/test/unit/rx-query.test.ts @@ -6,6 +6,7 @@ import clone from 'clone'; import * as humansCollection from './../helper/humans-collection'; import * as schemaObjects from '../helper/schema-objects'; import * as schemas from './../helper/schemas'; +import {Cache} from '../helper/cache'; import { isRxQuery, @@ -18,6 +19,7 @@ import { import { firstValueFrom } from 'rxjs'; import type { HumanDocumentType } from './../helper/schemas'; +import {QueryCacheBackend} from '../../src'; describe('rx-query.test.ts', () => { config.parallel('.constructor', () => { @@ -1378,7 +1380,7 @@ describe('rx-query.test.ts', () => { }); }); - async function setUpLimitBufferCollectionAndQuery(enableLimitBufferSize?: number, numRowsTotal=20, skipRows?: number) { + async function setUpLimitBufferCollectionAndQuery(enableLimitBufferSize?: number, numRowsTotal= 20, skipRows?: number) { const limitRows = 10; const collection = await humansCollection.create(numRowsTotal); @@ -1582,4 +1584,51 @@ describe('rx-query.test.ts', () => { collection.database.destroy(); }); }); + + async function setUpPersistentQueryCacheCollection(queryCacheBackend?: QueryCacheBackend) { + const collection = await humansCollection.create(0); + + return {collection}; + } + + config.parallel.only('Persistent Query Cache', () => { + it('query fills cache', async () => { + const cache = new Cache(); + const {collection} = await setUpPersistentQueryCacheCollection(cache); + + const query = collection.find({ limit: 1 }); + query.enablePersistentQueryCache(cache); + + const human1 = schemaObjects.human(); + const human2 = schemaObjects.human(); + + await collection.bulkInsert([human1, human2]); + await query.exec(); + + assert.strictEqual(cache.size, 2); + + collection.database.destroy(); + }); + + it('removing an element still returns query result from cache', async () => { + const cache = new Cache(); + const {collection} = await setUpPersistentQueryCacheCollection(cache); + + const human1 = schemaObjects.human(); + const human2 = schemaObjects.human(); + + const {success: docs} = await collection.bulkInsert([human1, human2]); + + const query = collection.find({ limit: 2 }); + query.enablePersistentQueryCache(cache); + + const result1 = await query.exec(); + console.log(cache.getItems()); + await docs[0].remove(); + + const result2 = await query.exec(); + console.log(cache.getItems()); + collection.database.destroy(); + }); + }); });