Releases: tinyplex/tinybase
v4.1.3
This makes use of the newly-ubiquitous nested CSS syntax to reduce the size of the StoreInspector stylesheet.
v4.1.2
Upgrades to developer dependencies and documentation/sponsors
v4.1.1
This adds error handling for the Inspector in the case where it's accidentally used outside a Provider.
See the v4.1 release notes for the main improvements in the release.
v4.1.0
This release introduces the new ui-react-dom module. This provides pre-built components for tabular display of your data in a web application.
New DOM Components
The following is the list of all the components released in v4.1:
| Component | Purpose | |
|---|---|---|
| ValuesInHtmlTable | Renders Values. | demo |
| TableInHtmlTable | Renders a Table. | demo |
| SortedTableInHtmlTable | Renders a sorted Table, with optional interactivity. | demo |
| SliceInHtmlTable | Renders a Slice from an Index. | demo |
| RelationshipInHtmlTable | Renders the local and remote Tables of a relationship | demo |
| ResultTableInHtmlTable | Renders a ResultTable. | demo |
| ResultSortedTableInHtmlTable | Renders a sorted ResultTable, with optional interactivity. | demo |
| EditableCellView | Renders a Cell and lets you change its type and value. | demo |
| EditableValueView | Renders a Value and lets you change its type and value. | demo |
These pre-built components are showcased in the UI Components demos. Using them should be very familiar if you have used the more abstract ui-react module:
const App = ({ store }) => (
<SortedTableInHtmlTable tableId='pets' cellId='species' store={store} />
);
const store = createStore().setTables({
pets: {
fido: { species: 'dog' },
felix: { species: 'cat' },
},
});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />); // !act
console.log(app.innerHTML);
// ->
`
<table>
<thead>
<tr><th>Id</th><th class="sorted ascending">β species</th></tr>
</thead>
<tbody>
<tr><th>felix</th><td>cat</td></tr>
<tr><th>fido</th><td>dog</td></tr>
</tbody>
</table>
`;
root.unmount(); // !actThe EditableCellView component and EditableValueView component are interactive input controls for updating Cell and Value content respectively. You can generally use them across your table views by adding the editable prop to your table component.
The new StoreInspector
The new StoreInspector component allows you to view, understand, and edit the content of a Store in a debug web environment. Try it out in most of the demos on the site, including the Movie Database demo, pictured. This requires a debug build of the new ui-react-dom module, which is now also included in the UMD distribution.
Also in this release, the getResultTableCellIds method and addResultTableCellIdsListener method have been added to the Queries object. The equivalent useResultTableCellIds hook and useResultTableCellIdsListener hook have also been added to ui-react module. A number of other minor React hooks have been added to support the components above.
Demos have been updated to demonstrate the ui-react-dom module and the StoreInspector component where appropriate.
v4.0.4
This release adds two useful debugging features to Persister objects, and improves SQLite transactions.
Every Persister creation method now takes an optional onIgnoredError argument that lets you log or handle errors encountered when loading or saving data. See the createLocalPersister documentation, for example.
Every database-oriented Persister creation method also now takes an optional onSqlCommand argument that lets you log commands and queries made to the underlying database. See the createSqlite3Persister documentation, for example.
Additionally, SQLite loading and saving methods are now wrapped (and sequentially sequenced) in SQL transactions, improving the integrity in situations where multiple processes might be accessing the database.
And with that, v4.0.4 is not "Not Found"!
v4.0.3
This release includes an experimental expo-sqlite persister, which allows you to use TinyBase and persist data to SQLite in React Native.
See https://expo.dev/changelog/2023/08-10-cr-sqlite for background.
v4.0.2
This release includes a significant optimization for saving TinyBase to a SQLite database. When in autoSave mode, a small change to the Store will result in just the corresponding small change to the database. (Previously any change to a Table could result in the rewriting of the whole database table). Suffice to say this has good performance characteristics for large TinyBase Stores.
Note that there is no equivalent optimization for synchronizing changes in from SQLite yet, since the reactivity of SQLite is not sufficient to know what changed at a detailed level.
v4.0.1
This optimizes TinyBase's JSON stringification and updates dev and integration dependencies.
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.
v3.3.0
This release allows you to track the Cell Ids used across a whole Table, regardless of which Row they are in.
In a Table (particularly in a Store without a TablesSchema), different Rows can use different Cells. Consider this Store, where each pet has a different set of Cell Ids:
const store = createStore();
store.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat', friendly: true},
cujo: {legs: 4},
});Prior to v3.3, you could only get the Cell Ids used in each Row at a time (with the getCellIds method). But you can now use the getTableCellIds method to get the union of all the Cell Ids used across the Table:
console.log(store.getCellIds('pets', 'fido')); // previously available
// -> ['species']
console.log(store.getTableCellIds('pets')); // new in v3.3
// -> ['species', 'friendly', 'legs']You can register a listener to track the Cell Ids used across a Table with the new addTableCellIdsListener method. Use cases for this might include knowing which headers to render when displaying a sparse Table in a user interface, or synchronizing data with relational or column-oriented database system.
There is also a corresponding useTableCellIds hook in the optional ui-react module for accessing these Ids reactively, and a useTableCellIdsListener hook for more advanced purposes.
Note that the bookkeeping behind these new accessors and listeners is efficient and should not be slowed by the number of Rows in the Table.
This release also passes a getIdChanges function to every Id-related listener that, when called, returns information about the Id changes, both additions and removals, during a transaction. See the TableIdsListener type, for example.
let listenerId = store.addRowIdsListener(
'pets',
(store, tableId, getIdChanges) => {
console.log(getIdChanges());
},
);
store.setRow('pets', 'lowly', {species: 'worm'});
// -> {lowly: 1}
store.delRow('pets', 'felix');
// -> {felix: -1}
store.delListener(listenerId).delTables();
