Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG-Unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

### New features

- Added ability to refresh collection cache using results of query

### Fixes

- [LokiJS] Multitab sync issue fix
Expand Down
13 changes: 13 additions & 0 deletions src/Collection/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ export default class Collection<Record extends Model> {
// 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<CollectionChangeSet<Record>>

// *** Implementation details ***

get table(): TableName<Record>
Expand Down
42 changes: 41 additions & 1 deletion src/Collection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -194,6 +194,46 @@ export default class Collection<Record: Model> {
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<CollectionChangeSet<Record>> {
return new Promise<CollectionChangeSet<Record>>((resolve) => {
this._unsafeFetchRaw(new Query(this, clauses), (results) => {
const updateCacheOperations: CollectionChangeSet<Record> = []
const notifySubscribersOperations: CollectionChangeSet<Record> = []

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
Expand Down
38 changes: 38 additions & 0 deletions src/Collection/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})