v4.0.0
This is the fifth and final beta for v4.0, a major release that provides Persister modules that connect TinyBase to SQLite databases (in both browser and server contexts), and CRDT frameworks that can provide synchronization and local-first reconciliation:
| Module | Function | Storage |
|---|---|---|
| persister-sqlite3 | createSqlite3Persister | SQLite in Node, via sqlite3 |
| persister-sqlite-wasm | createSqliteWasmPersister | SQLite in a browser, via sqlite-wasm |
| persister-cr-sqlite-wasm | createCrSqliteWasmPersister | SQLite CRDTs, via cr-sqlite-wasm |
| persister-yjs | createYjsPersister | Yjs CRDTs, via yjs |
| persister-automerge | createSqliteWasmPersister | Automerge CRDTs, via automerge-repo |
SQLite databases
You can persist Store data to a database with either a JSON serialization or tabular mapping. (See the DatabasePersisterConfig documentation for more details).
For example, this creates a Persister object and saves and loads the Store to and from a local SQLite database. It uses an explicit tabular one-to-one mapping for the 'pets' table:
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(store, sqlite3, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(db.exec('SELECT * FROM pets;', {rowMode: 'object'}));
// -> [{_id: 'fido', species: 'dog'}]
db.exec(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();CRDT Frameworks
CRDTs allow complex reconciliation and synchronization between clients. Yjs and Automerge are two popular examples. The API should be familiar! The following will persist a TinyBase Store to a Yjs document:
store.setTables({pets: {fido: {species: 'dog'}}});
const doc = new Y.Doc();
const yJsPersister = createYjsPersister(store, doc);
await yJsPersister.save();
// Store will be saved to the document.
console.log(doc.toJSON());
// -> {tinybase: {t: {pets: {fido: {species: 'dog'}}}, v: {}}}
yJsPersister.destroy();The following is the equivalent for an Automerge document that will sync over the broadcast channel:
const docHandler = new AutomergeRepo.Repo({
network: [new BroadcastChannelNetworkAdapter()],
}).create();
const automergePersister = createAutomergePersister(store, docHandler);
await automergePersister.save();
// Store will be saved to the document.
console.log(docHandler.doc);
// -> {tinybase: {t: {pets: {fido: {species: 'dog'}}}, v: {}}}
automergePersister.destroy();
store.delTables();New methods
There are three new methods on the Store object. The getContent method lets you get the Store's Tables and Values in one call. The corresponding setContent method lets you set them simultaneously.
The new setTransactionChanges method lets you replay TransactionChanges (received at the end of a transaction via listeners) into a Store, allowing you to take changes from one Store and apply them to another.
Persisters now provide a schedule method that lets you queue up asynchronous tasks, such as when persisting data that requires complex sequences of actions.
Breaking changes
The way that data is provided to the DoRollback and TransactionListener callbacks at the end of a transaction has changed. Where previously they directly received content about changed Cell and Value content, they now receive functions that they can choose to call to receive that same data. This has a performance improvement, and your callback or listener can choose between concise TransactionChanges or more verbose TransactionLog structures for that data.
If you have build a custom persister, you will need to update your implementation. Most notably, the setPersisted function parameter is provided with a getContent function to get the content from the Store itself, rather than being passed pre-serialized JSON. It also receives information about the changes made during a transaction. The getPersisted function must return the content (or nothing) rather than JSON. startListeningToPersisted has been renamed addPersisterListener, and stopListeningToPersisted has been renamed delPersisterListener.