diff --git a/CHANGELOG-Unreleased.md b/CHANGELOG-Unreleased.md index 9d43e2bdd..416489f00 100644 --- a/CHANGELOG-Unreleased.md +++ b/CHANGELOG-Unreleased.md @@ -6,6 +6,8 @@ ### New features +- Added ability to refresh collection cache using results of query + ### Fixes - [LokiJS] Multitab sync issue fix diff --git a/src/Collection/index.d.ts b/src/Collection/index.d.ts index af47bb402..efee6222e 100644 --- a/src/Collection/index.d.ts +++ b/src/Collection/index.d.ts @@ -69,6 +69,19 @@ export default class Collection { // This is useful when you're adding online-only features to an otherwise offline-first app disposableFromDirtyRaw(dirtyRaw: DirtyRaw): Record + /** + * Executes the provided query against the database and uses the results to + * refresh the internal cache. + * + * Note: This is only required when changes were made outside of WatermelonDB. + * + * Any observers of the affected data will be notified of the change. + * + * Returns a collection of modified records that were sent as notifications to + * subscribers. + */ + refreshCache(clauses: Clause[]): Promise> + // *** Implementation details *** get table(): TableName diff --git a/src/Collection/index.js b/src/Collection/index.js index 5b8405e51..276d3e8f7 100644 --- a/src/Collection/index.js +++ b/src/Collection/index.js @@ -15,7 +15,7 @@ import type Database from '../Database' import type Model, { RecordId } from '../Model' import type { Clause } from '../QueryDescription' import { type TableName, type TableSchema } from '../Schema' -import { type DirtyRaw } from '../RawRecord' +import { type DirtyRaw, sanitizedRaw } from '../RawRecord' import RecordCache from './RecordCache' @@ -194,6 +194,46 @@ export default class Collection { return this.modelClass._disposableFromDirtyRaw(this, dirtyRaw) } + /** + * Executes the provided query against the database and uses the results to + * refresh the internal cache. + * + * Note: This is only required when changes were made outside of WatermelonDB. + * + * Any observers of the affected data will be notified of the change. + * + * Returns a collection of modified records that were sent as notifications to + * subscribers. + */ + refreshCache(clauses: Clause[]): Promise> { + return new Promise>((resolve) => { + this._unsafeFetchRaw(new Query(this, clauses), (results) => { + const updateCacheOperations: CollectionChangeSet = [] + const notifySubscribersOperations: CollectionChangeSet = [] + + results.value?.map((rawRecord) => { + rawRecord = sanitizedRaw(rawRecord, this.schema) + const record = this._cache.recordInsantiator(rawRecord) + + this._cache.delete(record) + updateCacheOperations.push({ record, type: 'created' }) + if ( + record._raw._status === 'created' || + record._raw._status === 'updated' || + record._raw._status === 'destroyed' + ) { + notifySubscribersOperations.push({ record, type: record._raw._status }) + } + }) + + this._applyChangesToCache(updateCacheOperations) + this._notify(notifySubscribersOperations) + + resolve(notifySubscribersOperations) + }) + }) + } + // *** Implementation details *** // See: Query.fetch diff --git a/src/Collection/test.js b/src/Collection/test.js index 3607aab9b..d1197589d 100644 --- a/src/Collection/test.js +++ b/src/Collection/test.js @@ -355,3 +355,41 @@ describe('Collection observation', () => { expect(subscriber).toHaveBeenCalledTimes(4) }) }) + +describe('refresh cache', () => { + it('refreshes cache using data from database', async () => { + const { db, comments } = mockDatabase() + + // Create a new record + let targetID = null + await db.write(async () => { + const newComment = await comments.create((r) => { + r.body = 'comment body' + }) + + targetID = newComment.id + }) + + // Confirm the value was persisted + const originalComment = await comments.find(targetID) + expect(originalComment.body).toBe('comment body') + + // Change the value by accessing the DB driver directly + db.adapter.underlyingAdapter._driver.loki + .getCollection('mock_comments') + .findAndUpdate({ id: targetID }, (c) => { + c.body = 'updated comment body' + }) + + // Confirm the cache is stale + const staleComment = await comments.find(targetID) + expect(staleComment.body).toBe('comment body') + + // Refresh the cache + await comments.refreshCache([Q.where('id', targetID)]) + + // Confirm the cache has been updated + const refreshedComment = await comments.find(targetID) + expect(refreshedComment.body).toBe('updated comment body') + }) +})