liveQuery()
Since 3.1.0-beta.1
Remarks
Turns a Promise-returning function that queries Dexie into an Observable.
Syntax
querier
Function that returns a final result (Promise)
Svelte and Angular
Svelte and Angular supports Observables natively so liveQuery() can be used directly.
The Svelte Store Contract implements a subset of the Ecmascript Observable specification draft which makes the return value of liveQuery() a fully valid Svelte Store by itself.
Angular supports Rx observables natively, and Rx Observables also are compliant with the Ecmascript Observable specification.
Svelte: Use the dollar prefix ($liveQueryReturnValue
)
Angular: Use the AsyncPipe (liveQueryReturnValue | async
).
React and Vue
For React apps, we provide a hook, useLiveQuery() that allows components to consume live queries.
For Vue, we still haven't implemented any specific hook, but the observable returned from liveQuery() can be consumed using useObservable() from @vueuse/rxjs.
Examples
Vanilla JS
React
Svelte
Other frameworks
On dexie.org we also show examples for Angular and Vue.
Deep Dive
The following vanilla JS example should explain how the observable works by looking at the print outs.
This sample is also available in Dexie.js/samples/liveQuery/liveQuery.html
The following output will be seen:
Whenever a database change is made that would affect the result of your querier, your querier callback will be re-executed and your observable will emit the new result.
If you wonder how we can possibly detect whether a change would affect your querier, the details are:
Any call to any Dexie API done during querier execution will be tracked
The tracking is done using an efficient data structure for range collision detection, a range tree
Every index being queried is tracked with the given range it queries. This makes it possible to detect whether an added object would fit within the range or not, also whether an update of an indexed property would make it become included or not.
Whenever a write-transaction commits successfully, mutated parts (keys and ranges) are matched against active live queries using the range tree structure.
Add-mutations: every indexed property is matched against active live queries
Update-mutations: if an indexed property is updated, we detect whether it would become included into any live query where it was previously not included, or excluded from a query it was previously included in, or whether it updates properties on a result that was part of the query result.
Delete-mutations: queries that have the same primary keys in their results will be triggered
Whenever the querier is triggered, the subscribed ranges are cleared, the querier re-executed and the ranges or keys being queried this time will be tracked.
Mutated rangesets are also broadcast across browsing contexts to wake up liveQueries in other tabs or workers
This is a simplified explanation of how the algorithm works. The raw details can be found here. There are edge cases we also take care of, and optimizations to preserve write performance of large bulk mutations. However, the optimizations does not affect the functionality else than that liveQueries may be triggered as false positives in certain times.
Rules for the querier function
Don't call non-Dexie asynchronous API:s directly from it.
If you really need to call a non-Dexie asynchronous API (such as webCrypto), wrap the returned promise through `Promise.resolve()` or Dexie.waitFor() before awaiting it.
Sample: Await non-dexie async calls in querier
Fine grained observation
The observation is as fine-grained as it can possibly be - queries that would be affected by a modification will rerender - others not (with some exceptions - false positives happen but never false negatives). This is also true if your querier callback performs a series of awaited queries or multiple in parallel using Promise.all(). It can even contain if-statements or other conditional paths within it, determining additional queries to make before returning a final result - still, observation will function and never miss an update. No matter how simple or complex the query is - it will be monitored in detail so that if a single part of the query is affected by a change, the querier will be executed and the component will rerender.
Once again, the rule is that:
If a database change would affect the result of your querier, your querier callback will be re-executed and your observable will emit the new result.