Using A MergeableStore
The basic building block of TinyBase's synchronization system is the MergeableStore
interface.
The Anatomy Of A MergeableStore
The MergeableStore
interface is a sub-type of the regular Store
- and it shares its underlying implementation.
This means that if you want to add synchronization to your app, all of your existing calls to the Store
methods will be unchanged - you just need to use the createMergeableStore
function to instantiate it, instead of the classic createStore
function.
import {createMergeableStore} from 'tinybase';
const store1 = createMergeableStore('store1');
store1.setCell('pets', 'fido', 'species', 'dog');
console.log(store1.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {}]
The difference, though, is that a MergeableStore
records additional metadata as the data is changed so that potential conflicts between it and another instance can be reconciled. This metadata is intended to be opaque, but you can see it if you call the getMergeableContent
method:
console.log(store1.getMergeableContent());
// ->
[
[
{
pets: [
{
fido: [
{species: ['dog', 'Nn1JUF-----FnHIC', 290599168]},
'',
2682656941,
],
},
'',
2102515304,
],
},
'',
3506229770,
],
[{}, '', 0],
];
Without going into the detail of this, the main point to understand is that each update gets a timestamp, based on a hybrid logical clock (HLC), and a hash. As a result, TinyBase is able to understand which parts of the data have changed, and which changes are the most recent. The resulting 'last write wins' (LWW) approach allows the MergeableStore
to act as a Conflict-Free Replicated Data Type (CRDT).
(Notice we provided an explicit uniqueId
when we initialized the MergeableStore
: this is not normally required, but here it just ensures the hashes in the example are deterministic).
We can of course, create a second MergeableStore
with different data:
const store2 = createMergeableStore();
store2.setCell('pets', 'felix', 'species', 'cat');
And now merge them together with the convenient merge
method:
store1.merge(store2);
console.log(store1.getContent());
// -> [{pets: {felix: {species: 'cat'}, fido: {species: 'dog'}}}, {}]
console.log(store2.getContent());
// -> [{pets: {felix: {species: 'cat'}, fido: {species: 'dog'}}}, {}]
Magic!
This all said, it's very unlikely you will need to use the numerous extra methods available on a MergeableStore
(compared to a Store
) since most of them exist to support synchronization behind the scenes.
In general, you'll just use a MergeableStore
in the same was as you would have used a Store
, and instead rely on the more approachable Synchronizer
API for synchronization. We'll discuss this next in the Using A Synchronizer guide.
Persisting A MergeableStore
Once important thing that you need to be aware of is that a MergeableStore
cannot currently be persisted by every type of Persister
available to a regular Store
. This is partly because some are already designed to work with alternative third-party CRDT systems (like the YjsPersister
and AutomergePersister
), and partly because this extra metadata cannot be easily stored in a plain SQLite database.
The following Persister
types can be used to persist a MergeableStore
:
Persister | Storage |
---|---|
SessionPersister | Browser session storage |
LocalPersister | Browser local storage |
FilePersister | Local file (where possible) |
The following database-oriented Persister
types can be used to persist a MergeableStore
, but only in the 'JSON-serialization' mode:
Persister | Storage |
---|---|
Sqlite3Persister | SQLite in Node, via sqlite3 |
SqliteWasmPersister | SQLite in a browser, via sqlite-wasm |
ExpoSqlitePersister | SQLite in React Native, via expo-sqlite |
PostgresPersister | PostgreSQL, via postgres |
PglitePersister | PostgreSQL, via PGlite |
The following database-oriented Persister
types cannot currently be used to persist a MergeableStore
:
Persister | Storage |
---|---|
IndexedDbPersister | Browser IndexedDB |
RemotePersister | Remote server |
CrSqliteWasmPersister | SQLite CRDTs, via cr-sqlite-wasm |
ElectricSqlPersister | Electric SQL, via electric-sql |
LibSqlPersister | LibSQL for Turso, via libsql-client |
PowerSyncPersister | PowerSync, via powersync-sdk |
YjsPersister | Yjs CRDTs, via yjs |
AutomergePersister | Automerge CRDTs, via automerge-repo |
PartyKitPersister | PartyKit, via the persister-partykit-server module |
Next, let's see how to synchronize MergeableStore
objects together with the synchronizers
module. Please continue on to the Using A Synchronizer guide.