UNPKG

tinybase

Version:

A reactive data store and sync engine.

342 lines (258 loc) 145 kB
<link rel="preload" as="image" href="https://beta.tinybase.org/inspector.webp"><link rel="preload" as="image" href="https://beta.tinybase.org/partykit.gif"><link rel="preload" as="image" href="https://beta.tinybase.org/ui-react-dom.webp"><link rel="preload" as="image" href="https://beta.tinybase.org/store-inspector.webp"><link rel="preload" as="image" href="https://beta.tinybase.org/car-analysis.webp"><link rel="preload" as="image" href="https://beta.tinybase.org/movie-database.webp"><p>This is a reverse chronological list of the major TinyBase releases, with highlighted features.</p><hr><h1 id="v7-3">v7.3</h1><h2 id="introducing-state-hooks">Introducing State Hooks</h2><p>This release introduces a new family of convenience hooks that follow React&#x27;s <code>useState</code> pattern, making it even easier to read and write TinyBase data in your React components.</p><p>Each state hook returns a tuple containing both the current value and a setter function, eliminating the need to use separate getter and setter hooks.</p><p>State hooks combine the functionality of getter hooks (like the <a href="https://beta.tinybase.org/api/the-essentials/using-react/userow/"><code>useRow</code></a> hook) and setter callback hooks (like the <a href="https://beta.tinybase.org/api/ui-react/functions/store-hooks/usesetrowcallback/"><code>useSetRowCallback</code></a> hook) into a single, convenient API that feels just like React&#x27;s <code>useState</code>:</p> ```jsx import {createStore} from 'tinybase'; import {useCellState} from 'tinybase/ui-react'; const store = createStore().setRow('pets', 'fido', { species: 'dog', color: 'brown', }); const PetEditor = () => { const [color, setColor] = useCellState('pets', 'fido', 'color', store); return ( <div> <div>Color: {color}</div> <button onClick={() => setColor('black')}>Change Color</button> </div> ); }; ``` <h2 id="available-state-hooks">Available State Hooks</h2><p>This release includes eight new state hooks covering the most common data access patterns:</p><ul><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/usetablesstate/"><code>useTablesState</code></a> hook for reading and writing all <a href="https://beta.tinybase.org/api/store/type-aliases/store/tables/"><code>Tables</code></a></li><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/usetablestate/"><code>useTableState</code></a> hook for reading and writing a single <a href="https://beta.tinybase.org/api/store/type-aliases/store/table/"><code>Table</code></a></li><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/userowstate/"><code>useRowState</code></a> hook for reading and writing a single <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a></li><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/usecellstate/"><code>useCellState</code></a> hook for reading and writing a single <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a></li><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/usevaluesstate/"><code>useValuesState</code></a> hook for reading and writing all <a href="https://beta.tinybase.org/api/store/type-aliases/store/values/"><code>Values</code></a></li><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/usevaluestate/"><code>useValueState</code></a> hook for reading and writing a single <a href="https://beta.tinybase.org/api/store/type-aliases/store/value/"><code>Value</code></a></li><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/useparamvaluesstate/"><code>useParamValuesState</code></a> hook for reading and writing all query <a href="https://beta.tinybase.org/api/queries/type-aliases/params/paramvalues/"><code>ParamValues</code></a></li><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/useparamvaluestate/"><code>useParamValueState</code></a> hook for reading and writing a single query <a href="https://beta.tinybase.org/api/queries/type-aliases/params/paramvalue/"><code>ParamValue</code></a></li></ul><p>These hopefully mean less boilerplate, are particularly useful when building forms, editors, or any interactive UI that needs bidirectional data binding.</p><h2 id="demo-updates">Demo Updates</h2><p>We&#x27;ve updated a few of the demos to showcase these new state hooks:</p><ul><li>The <a href="https://beta.tinybase.org/demos/countries/">Countries</a> demo now uses the <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/usecellstate/"><code>useCellState</code></a> hook for the star/unstar toggle functionality.</li><li>The <a href="https://beta.tinybase.org/demos/todo-app/">Todo App</a> demo uses the <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/usecellstate/"><code>useCellState</code></a> hook to simplify todo completion toggling, and the <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/usevaluestate/"><code>useValueState</code></a> hook for managing the selected type filter.</li><li>The <a href="https://beta.tinybase.org/demos/car-analysis/">Car Analysis</a> demo uses the <a href="https://beta.tinybase.org/api/ui-react/functions/state-hooks/useparamvaluestate/"><code>useParamValueState</code></a> hook to manage query parameters for dimensions, measures, and aggregates. Much more elegant!</li></ul><p>Check out these demos to see the state hooks in action.</p><hr><h1 id="v7-2">v7.2</h1><h2 id="introducing-parameterized-queries">Introducing Parameterized <a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/"><code>Queries</code></a>!</h2><p>This release introduces parameterized queries to <a href="https://beta.tinybase.org/guides/using-queries/tinyql/">TinyQL</a> - finally!</p><p>These allow you to define queries using named &#x27;params&#x27; that you can then easily update to change the query&#x27;s results - without redefining the whole query each time.</p><p>Let&#x27;s take a look with a simple example:</p> ```js import {createQueries} from 'tinybase'; store.setTable('pets', { fido: {age: 2, species: 'dog'}, felix: {age: 1, species: 'cat'}, cujo: {age: 3, species: 'dog'}, }); const queries = createQueries(store).setQueryDefinition( 'petsBySpecies', 'pets', ({select, where, param}) => { select('age'); where('species', param('species')); }, {species: 'dog'}, // Initial param value ); console.log(queries.getResultTable('petsBySpecies')); // -> {fido: {age: 2}, cujo: {age: 3}} // Update the 'species' param to 'cat' to change the results: queries.setParamValue('petsBySpecies', 'species', 'cat'); console.log(queries.getResultTable('petsBySpecies')); // -> {felix: {age: 1}} ``` <p>You can of course have multiple params in a query definition, and use them in any part of the query definition that you would like. Listeners also work as expected - if you are listening to a query&#x27;s results, and you change a param that affects those results, your listener will be called accordingly.</p><p>This is TinyBase so you should not be too surprised... but params themselves are reactive! You can get and listen to their values with the <a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/methods/getter/getparamvalue/"><code>getParamValue</code></a> method and <a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/methods/listener/addparamvaluelistener/"><code>addParamValueListener</code></a> method, for example.</p><p>For React users, we also shipped a bunch of new hooks that cover params in exactly the way you would expect, including the <a href="https://beta.tinybase.org/api/ui-react/functions/queries-hooks/usesetparamvaluecallback/"><code>useSetParamValueCallback</code></a> hook and the <a href="https://beta.tinybase.org/api/ui-react/functions/queries-hooks/usesetparamvaluescallback/"><code>useSetParamValuesCallback</code></a> hook, which let you easily update param values from, say, an event handler in your application.</p><p>We know this has been a long-awaited feature, so we hope you enjoy it! See the <a href="https://beta.tinybase.org/guides/using-queries/tinyql/">TinyQL</a> guide for more details, and please let us know how it goes!</p><h2 id="demos"><a href="https://beta.tinybase.org/demos/">Demos</a></h2><p>We have updated the <a href="https://beta.tinybase.org/demos/movie-database/">Movie Database</a> demo to use parameterized queries, and as a result is more efficient and easier to (we think) understand. See the <code>yearGenreMovies</code>, <code>directedMovies</code>, and <code>appearedMovies</code> queries to see params in action.</p><p>We have also updated the <a href="https://beta.tinybase.org/demos/car-analysis/">Car Analysis</a> demo to use just one single parameterized query for the whole app!</p><h2 id="full-api-additions">Full API additions</h2><p>This release includes the following new <a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/"><code>Queries</code></a> interface methods:</p><ul><li><a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/methods/getter/getparamvalues/"><code>getParamValues</code></a> method</li><li><a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/methods/getter/getparamvalue/"><code>getParamValue</code></a> method</li><li><a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/methods/configuration/setparamvalues/"><code>setParamValues</code></a> method</li><li><a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/methods/configuration/setparamvalue/"><code>setParamValue</code></a> method</li><li><a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/methods/listener/addparamvalueslistener/"><code>addParamValuesListener</code></a> method</li><li><a href="https://beta.tinybase.org/api/queries/interfaces/queries/queries/methods/listener/addparamvaluelistener/"><code>addParamValueListener</code></a> method</li></ul><p>It also includes the following new React hooks:</p><ul><li><a href="https://beta.tinybase.org/api/ui-react/functions/queries-hooks/useparamvalues/"><code>useParamValues</code></a> hook</li><li><a href="https://beta.tinybase.org/api/ui-react/functions/queries-hooks/useparamvalue/"><code>useParamValue</code></a> hook</li><li><a href="https://beta.tinybase.org/api/ui-react/functions/queries-hooks/usesetparamvaluescallback/"><code>useSetParamValuesCallback</code></a> hook</li><li><a href="https://beta.tinybase.org/api/ui-react/functions/queries-hooks/usesetparamvaluecallback/"><code>useSetParamValueCallback</code></a> hook</li><li><a href="https://beta.tinybase.org/api/ui-react/functions/queries-hooks/useparamvalueslistener/"><code>useParamValuesListener</code></a> hook</li><li><a href="https://beta.tinybase.org/api/ui-react/functions/queries-hooks/useparamvaluelistener/"><code>useParamValueListener</code></a> hook</li></ul><p>Check out the API docs for each. They should seem very familiar.</p><p>Please check out this new release and let us know what you think!</p><hr><h1 id="v7-1">v7.1</h1><p>This release introduces <strong>Schematizers</strong>, a new system for converting schemas from popular validation libraries into TinyBase&#x27;s schema format.</p><h2 id="schematizers">Schematizers</h2><p>Schematizers provide a bridge between external schema validation libraries (like Zod, TypeBox, and Valibot) and TinyBase&#x27;s <a href="https://beta.tinybase.org/api/store/type-aliases/schema/tablesschema/"><code>TablesSchema</code></a> and <a href="https://beta.tinybase.org/api/store/type-aliases/schema/valuesschema/"><code>ValuesSchema</code></a> formats. Instead of manually writing TinyBase schemas, you can now convert existing schemas at runtime:</p> ```js import {createZodSchematizer} from 'tinybase/schematizers/schematizer-zod'; import {z} from 'zod'; const schematizer = createZodSchematizer(); const zodSchema = { pets: z.object({ species: z.string(), age: z.number(), sold: z.boolean().default(false), }), }; const schematizedStore = createStore().setTablesSchema( schematizer.toTablesSchema(zodSchema), ); schematizedStore.setRow('pets', 'fido', {species: 'dog', age: 3}); console.log(schematizedStore.getRow('pets', 'fido')); // -> {species: 'dog', age: 3, sold: false} ``` <p>Schematizers perform best-effort conversions, extracting basic type information (string, number, boolean), defaults, and nullable settings from your schemas.</p><p>This release includes support for:</p><ul><li><strong>Zod</strong> via the <a href="https://beta.tinybase.org/api/schematizer-zod/functions/creation/createzodschematizer/"><code>createZodSchematizer</code></a> function</li><li><strong>TypeBox</strong> via the <a href="https://beta.tinybase.org/api/schematizer-typebox/functions/creation/createtypeboxschematizer/"><code>createTypeBoxSchematizer</code></a> function</li><li><strong>Valibot</strong> via the <a href="https://beta.tinybase.org/api/schematizer-valibot/functions/creation/createvalibotschematizer/"><code>createValibotSchematizer</code></a> function</li><li><strong>ArkType</strong> via the <a href="https://beta.tinybase.org/api/schematizer-arktype/functions/creation/createarktypeschematizer/"><code>createArkTypeSchematizer</code></a> function</li><li><strong>Yup</strong> via the <a href="https://beta.tinybase.org/api/schematizer-yup/functions/creation/createyupschematizer/"><code>createYupSchematizer</code></a> function</li><li><strong>Effect Schema</strong> via the <a href="https://beta.tinybase.org/api/schematizer-effect/functions/creation/createeffectschematizer/"><code>createEffectSchematizer</code></a> function</li></ul><p>For more information, see the <a href="https://beta.tinybase.org/guides/schemas/using-schematizers/">Using Schematizers</a> guide.</p><hr><h1 id="v7-0">v7.0</h1><p>This important (and slightly breaking!) release adds support for <code>null</code> as a valid <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> and <a href="https://beta.tinybase.org/api/store/type-aliases/store/value/"><code>Value</code></a> type, alongside <code>string</code>, <code>number</code>, and <code>boolean</code>.</p><h2 id="null-type-support">Null Type Support</h2><p>You can now set Cells and <a href="https://beta.tinybase.org/api/store/type-aliases/store/values/"><code>Values</code></a> to <code>null</code>:</p> ```js store.setCell('pets', 'fido', 'species', 'dog'); store.setCell('pets', 'fido', 'color', null); console.log(store.getCell('pets', 'fido', 'color')); // -> null console.log(store.hasCell('pets', 'fido', 'color')); // -> true ``` <p>To allow <code>null</code> values in your schema, use the new <code>allowNull</code> property:</p> ```js store.setTablesSchema({ pets: { species: {type: 'string'}, color: {type: 'string', allowNull: true}, }, }); store.setCell('pets', 'fido', 'color', null); // Valid because allowNull is true store.setCell('pets', 'fido', 'species', null); // Invalid - species does not allow null store.delSchema(); ``` <h2 id="important-distinction-null-vs-undefined">Important Distinction: <code>null</code> vs <code>undefined</code></h2><p>It&#x27;s crucial to understand the difference between <code>null</code> and <code>undefined</code> in TinyBase:</p><ul><li><code>null</code> is an explicit value. A <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> set to <code>null</code> exists in the <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>.</li><li><code>undefined</code> means the <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> does not exist in the <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>.</li></ul><p>This means that the <a href="https://beta.tinybase.org/api/store/interfaces/store/store/methods/getter/hascell/"><code>hasCell</code></a> method will return <code>true</code> for a <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> with a <code>null</code> value:</p> ```js store.setCell('pets', 'fido', 'color', null); console.log(store.hasCell('pets', 'fido', 'color')); // -> true store.delCell('pets', 'fido', 'color'); console.log(store.hasCell('pets', 'fido', 'color')); // -> false store.delTables(); ``` <h2 id="breaking-change-database-persistence">Breaking Change: <a href="https://beta.tinybase.org/guides/persistence/database-persistence/">Database Persistence</a></h2><p><strong>Important:</strong> This release includes a breaking change for applications using database persisters (the <a href="https://beta.tinybase.org/api/persister-sqlite3/interfaces/persister/sqlite3persister/"><code>Sqlite3Persister</code></a>, <a href="https://beta.tinybase.org/api/persister-postgres/interfaces/persister/postgrespersister/"><code>PostgresPersister</code></a>, or <a href="https://beta.tinybase.org/api/persister-pglite/interfaces/persister/pglitepersister/"><code>PglitePersister</code></a> interfaces, for example).</p><p>SQL <code>NULL</code> values are now loaded as TinyBase <code>null</code> values. Previously, SQL <code>NULL</code> would result in Cells being absent from the <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>. Now, SQL <code>NULL</code> maps directly to TinyBase <code>null</code>, which means:</p><ul><li><a href="https://beta.tinybase.org/api/store/type-aliases/store/tables/"><code>Tables</code></a> loaded from SQL databases will be <strong>dense</strong> rather than <strong>sparse</strong></li><li>Every <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> will have every <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> present in the table schema</li><li>Cells that were SQL <code>NULL</code> will have the value <code>null</code></li></ul><p>Example of the roundtrip transformation via a SQLite database:</p> ```js import sqlite3InitModule from '@sqlite.org/sqlite-wasm'; import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm'; const sqlite3 = await sqlite3InitModule(); let db = new sqlite3.oo1.DB(':memory:', 'c'); store.setTable('pets', { fido: {species: 'dog'}, felix: {species: 'cat', color: 'black'}, }); const tabularPersister = createSqliteWasmPersister(store, sqlite3, db, { mode: 'tabular', tables: {save: {pets: 'pets'}, load: {pets: 'pets'}}, }); await tabularPersister.save(); // After saving the the SQL database: // SQL table: fido (species: 'dog', color: NULL) // felix (species: 'cat', color: 'black') await tabularPersister.load(); // After loading again, the Store now has a dense table with an explicit null: console.log(store.getRow('pets', 'fido')); // -> {species: 'dog', color: null} ``` <p>This is the correct semantic mapping since SQL databases have fixed schemas where every row must account for every column. See the <a href="https://beta.tinybase.org/guides/persistence/database-persistence/">Database Persistence</a> guide for more details.</p><h2 id="migration-guide">Migration Guide</h2><p>If you are using database persisters, you should:</p><ol><li><p><strong>Review your data access patterns</strong>: If you were checking <code>hasCell(...) === false</code> to detect missing data, you now need to check <code>getCell(...) === null</code> for null values.</p></li><li><p><strong>Update your schemas</strong>: Add <code>allowNull: true</code> to <a href="https://beta.tinybase.org/api/store/type-aliases/store/cell/"><code>Cell</code></a> definitions that should permit null values:</p></li></ol> ```js yolo store.setTablesSchema({ pets: { species: {type: 'string'}, color: {type: 'string', allowNull: true}, age: {type: 'number', allowNull: true}, }, }); ``` <ol start="3"><li><strong>Consider memory implications</strong>: Dense tables consume more memory than sparse tables. If you have large tables with many optional Cells, this could be significant.</li></ol><hr><h1 id="v6-7">v6.7</h1><p>This release includes support for the Origin Private File System (OPFS) in a browser. The <a href="https://beta.tinybase.org/api/persister-browser/functions/creation/createopfspersister/"><code>createOpfsPersister</code></a> function is the main entry point, and is available in the existing <a href="https://beta.tinybase.org/api/persister-browser/"><code>persister-browser</code></a> module:</p> ```js import {createOpfsPersister} from 'tinybase/persisters/persister-browser'; const opfs = await navigator.storage.getDirectory(); const handle = await opfs.getFileHandle('tinybase.json', {create: true}); store.delTables().setTables({pets: {fido: {species: 'dog'}}}); const opfsPersister = createOpfsPersister(store, handle); await opfsPersister.save(); // Store JSON will be saved to the OPFS file. await opfsPersister.load(); // Store JSON will be loaded from the OPFS file. await opfsPersister.destroy(); ``` <p>That&#x27;s it! If you&#x27;ve used other TinyBase persisters, this API should be easy and familiar to use.</p><p>One caveat: observability in OPFS is not yet standardized in browsers. This means that the auto-load functionality of the persister may not work as expected, although a best effort is made using the experimental FileSystemObserverAPI, so please let us know how that works!</p><hr><h1 id="v6-6">v6.6</h1><p>This release improves the Inspector tool, making it easier to debug, inspect, and mutate your TinyBase stores.</p><p><img src="https://beta.tinybase.org/inspector.webp" alt="Inspector" title="Inspector"></p><p>As well as a modernized UI, new in this release is the ability to create, duplicate, or delete tables, rows, values and cells directly within the Inspector. Press the &#x27;pencil&#x27; icon to start editing items, and then hover over the new icons to see how to manipulate the data.</p><p>See the <a href="https://beta.tinybase.org/guides/inspecting-data/">Inspecting Data</a> guide for more information about how to use the Inspector in your application during development.</p><h1 id="v6-5">v6.5</h1><p>This release includes the new <a href="https://beta.tinybase.org/api/persister-react-native-mmkv/"><code>persister-react-native-mmkv</code></a> module, which allows you to persist data in a React Native MMKV store via the <a href="https://github.com/mrousavy/react-native-mmkv">react-native-mmkv</a> library.</p><p>Usage should be as simple as this:</p> ```js yolo import {createMMKV} from 'react-native-mmkv'; import {createReactNativeMmkvPersister} from 'tinybase/persisters/persister-react-native-mmkv'; const storage = createMMKV(); store.setTables({pets: {fido: {species: 'dog'}}}); const persister = createReactNativeMmkvPersister(store, storage); await persister.save(); // Store will be saved to the MMKV store. ``` <p>A huge shout out to <a href="https://github.com/JeremyBarbet">Jérémy Barbet</a> for this new persister!</p><hr><h1 id="v6-4">v6.4</h1><p>This release includes the new <a href="https://beta.tinybase.org/api/persister-react-native-sqlite/"><code>persister-react-native-sqlite</code></a> module, which allows you to persist data in a React Native SQLite database via the <a href="https://github.com/andpor/react-native-sqlite-storage">react-native-sqlite-storage</a> library.</p><p>Usage should be as simple as this:</p> ```js yolo import {enablePromise, openDatabase} from 'react-native-sqlite-storage'; import {createStore} from 'tinybase'; import {createReactNativeSqlitePersister} from 'tinybase/persisters/persister-react-native-sqlite'; enablePromise(true); const db = await openDatabase({name: 'my.db', location: 'default'}); const store = createStore().setTables({pets: {fido: {species: 'dog'}}}); const persister = createReactNativeSqlitePersister(store, db); await persister.save(); // Store will be saved to the database. ``` <p>Please let us know how you get on with this new <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a>, and if you have any feedback or suggestions.</p><hr><h1 id="v6-3">v6.3</h1><p>This release includes the new <a href="https://beta.tinybase.org/api/persister-durable-object-sql-storage/"><code>persister-durable-object-sql-storage</code></a> module, which allows you to persist data in a Cloudflare Durable Object&#x27;s SQLite-based storage in conjunction with websocket-based synchronization (using the <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/wsserverdurableobject/"><code>WsServerDurableObject</code></a> class).</p><p>Huge thanks to <a href="https://github.com/acoreyj">Corey Jepperson</a> for implementing the entirety of this functionality!</p> ```js yolo import {createMergeableStore} from 'tinybase'; import {createDurableObjectSqlStoragePersister} from 'tinybase/persisters/persister-durable-object-sql-storage'; import {WsServerDurableObject} from 'tinybase/synchronizers/synchronizer-ws-server-durable-object'; const config = { mode: 'fragmented', storagePrefix: 'my_app_', }; export class MyDurableObject extends WsServerDurableObject { createPersister() { const store = createMergeableStore(); const persister = createDurableObjectSqlStoragePersister( store, this.ctx.storage.sql, config, ); return persister; } } ``` <p>Prior to this release, the only way to persist data in a Durable Object was to use the <a href="https://beta.tinybase.org/api/persister-durable-object-storage/"><code>persister-durable-object-storage</code></a> module, which uses CloudFlare&#x27;s key-value storage backend behind the scenes.</p><p>However, Cloudflare&#x27;s SQLite storage backend for Durable Objects offers significantly better pricing compared to the key-value storage backend. The SQLite storage backend is Cloudflare&#x27;s recommended storage option for new Durable Object namespaces.</p><p>Note that, before using this persister, you must configure your Durable Object class to use SQLite storage by adding a migration to your <code>wrangler.toml</code> or <code>wrangler.json</code> configuration file. Use <code>new_sqlite_classes</code> in your migration configuration to enable SQLite storage for your Durable Object class. See the module documentation for more information.</p><p>This release also addresses a local-storage persistence issue, #<a href="https://github.com/tinyplex/tinybase/issues/257">257</a>.</p><hr><h1 id="v6-2">v6.2</h1><p>This release contains various packaging improvements and exposes some internal HLC functions that are useful for people building their own persisters or synchronizers.</p><h2 id="new-omni-module">New <code>omni</code> module</h2><p>There is a new <code>omni</code> module that is an explicit superset of everything in the TinyBase ecosystem. It exports the features and functionality of every <code>tinybase/*</code> module, including every persister, every synchronizer, and every UI component. This is useful for applications that want to use multiple facets of the overall TinyBase ecosystem and also benefit from the fact they share a lot of code internally.</p> ```js yolo import {createStore, createSqliteBunPersister} from 'tinybase/omni'; ``` <p>However, it should go without saying that you should only use the <code>omni</code> module if you have an aggressive tree-shaking bundler that can remove all the persisters, synchronizers, and so on, that you do <em>not</em> use. Experiment with different bundler configurations to see what works best for your usage.</p><h2 id="with-schema-exports">with-schema exports</h2><p>This release changes the <code>package.json</code> exports slightly so that imports of both <code>/with-schema</code> and non-schema&#x27;d versions of the modules resolve to the same JavaScript file. This reduces bundle size for apps that use both schema and non-schema imports.</p><h2 id="hlc-hash-functions">HLC &amp; hash functions</h2><p>The <a href="https://beta.tinybase.org/api/common/"><code>common</code></a> module (and hence tinybase module) now export the <a href="https://beta.tinybase.org/api/common/functions/stamps/gethlcfunctions/"><code>getHlcFunctions</code></a> function. This returns set of seven functions that can be used to create and manipulate HLC (Hybrid Logical Clock) timestamps.</p> ```js import {getHlcFunctions} from 'tinybase'; const [getNextHlc, seenHlc, encodeHlc] = getHlcFunctions(); ``` <p>There are also several functions to help hash tabular and key-value data in a way that is compatible with the internal <a href="https://beta.tinybase.org/api/mergeable-store/interfaces/mergeable/mergeablestore/"><code>MergeableStore</code></a> implementation. These include the <a href="https://beta.tinybase.org/api/common/functions/hash/gethash/"><code>getHash</code></a> function and the <a href="https://beta.tinybase.org/api/common/functions/hash/getcellhash/"><code>getCellHash</code></a> function, for example.</p><p>These are for pretty advanced use-cases! But you can use these in your own systems to ensure the timestamps and hashes are compatible with the ones generated in TinyBase <a href="https://beta.tinybase.org/api/mergeable-store/interfaces/mergeable/mergeablestore/"><code>MergeableStore</code></a> objects.</p><h2 id="moved-types">Moved types</h2><p>The rarely-used <a href="https://beta.tinybase.org/api/common/type-aliases/stamps/getnow/"><code>GetNow</code></a> and <a href="https://beta.tinybase.org/api/common/type-aliases/stamps/hash/"><code>Hash</code></a> types have been moved from the <a href="https://beta.tinybase.org/api/mergeable-store/"><code>mergeable-store</code></a> module into the <a href="https://beta.tinybase.org/api/common/"><code>common</code></a> module.</p><hr><h1 id="v6-1">v6.1</h1><h2 id="in-summary">In Summary</h2><ul><li><a href="#bun-sqlite">A new Persister for Bun</a>&#x27;s embedded SQLite database.</li><li><a href="#subset-persistence">Subset persistence</a> to load subsets of tables into a <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>.</li><li><a href="#destructured-object-arguments-for-sorted-row-ids">Destructured object arguments</a> for sorted <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> <a href="https://beta.tinybase.org/api/common/type-aliases/identity/id/"><code>Id</code></a> methods and hooks.</li><li><a href="#new-startautopersisting-method">A new startAutoPersisting method</a>.</li></ul><p>And more!</p><h2 id="bun-sqlite">Bun SQLite</h2><p>This release includes a new <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> for the <a href="https://bun.sh/docs/api/sqlite">embedded SQLite database</a> available in the Bun runtime.</p><p>You use it by passing a reference to a Bun Database object into the <a href="https://beta.tinybase.org/api/persister-sqlite-bun/functions/creation/createsqlitebunpersister/"><code>createSqliteBunPersister</code></a> function:</p> ```js bun import {Database} from 'bun:sqlite'; import {createStore} from 'tinybase'; import {createSqliteBunPersister} from 'tinybase/persisters/persister-sqlite-bun'; const db = new Database(':memory:'); const store = createStore().setTables({pets: {fido: {species: 'dog'}}}); const persister = createSqliteBunPersister(store, db, 'my_tinybase'); await persister.save(); // Store will be saved to the database. console.log(db.query('SELECT * FROM my_tinybase;').all()); // -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}] db.query( 'UPDATE my_tinybase SET store = ' + `'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`, ).run(); await persister.load(); console.log(store.getTables()); // -> {pets: {felix: {species: 'cat'}}} await persister.destroy(); ``` <p>There&#x27;s more information the documentation for the new <a href="https://beta.tinybase.org/api/persister-sqlite-bun/"><code>persister-sqlite-bun</code></a> module.</p><h2 id="subset-persistence">Subset persistence</h2><p>Persisters that load and save data to an underlying database can now be configured to only load a <em>subset</em> of the rows in a table into a <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a>.</p><p>This is useful for reducing the amount of data that is loaded into memory, or for working with a subset of data that is relevant to the current user, for example.</p><p>Do this by specifying a <code>condition</code> in the <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> configuration. This is a single string argument which is used as a SQL <code>WHERE</code> clause when reading and observing data in the table.</p><p>For example, the following code will only load rows from the <code>pets</code> database table where the <code>sold</code> column is set to <code>0</code>:</p> ```js yolo const subsetPersister = createSqliteWasmPersister(store, sqlite3, db, { mode: 'tabular', tables: { load: {pets: {tableId: 'pets', condition: '$tableName.sold = 0'}}, save: {pets: {tableName: 'pets', condition: '$tableName.sold = 0'}}, }, }); ``` <p>See the &#x27;<a href="https://beta.tinybase.org/guides/persistence/database-persistence/#loading-subsets-of-database-tables">Loading subsets of database tables</a>&#x27; section of the <a href="https://beta.tinybase.org/guides/persistence/database-persistence/">Database Persistence</a> guide for more details. And a huge thank you to Jakub Riedl (<a href="https://github.com/jakubriedl">@jakubriedl</a>) for landing this functionality!</p><h2 id="destructured-object-arguments-for-sorted-row-ids">Destructured object arguments for sorted <a href="https://beta.tinybase.org/api/store/type-aliases/store/row/"><code>Row</code></a> <a href="https://beta.tinybase.org/api/common/type-aliases/identity/ids/"><code>Ids</code></a></h2><p>The <a href="https://beta.tinybase.org/api/store/interfaces/store/store/methods/getter/getsortedrowids/"><code>getSortedRowIds</code></a> method on the <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/store/"><code>Store</code></a> interface has a number of optional parameters and it can be tiresome to fill in the defaults if you only want to change the last one, for example. So this release introduces an override such that you can pass an object with the parameters as properties.</p><p>So instead of:</p> ```js yolo store.getSortedRowIds('pets', undefined, undefined, undefined, 10); ``` <p>You can now do:</p> ```js yolo store.getSortedRowIds({tableId: 'pets', limit: 10}); ``` <p>This pattern is also made available to the <a href="https://beta.tinybase.org/api/store/interfaces/store/store/methods/listener/addsortedrowidslistener/"><code>addSortedRowIdsListener</code></a> method, the <a href="https://beta.tinybase.org/api/ui-react/functions/store-hooks/usesortedrowids/"><code>useSortedRowIds</code></a> hook, and the <a href="https://beta.tinybase.org/api/ui-react/functions/store-hooks/usesortedrowidslistener/"><code>useSortedRowIdsListener</code></a> hook.</p><h2 id="new-startautopersisting-method">New <a href="https://beta.tinybase.org/api/persisters/interfaces/persister/persister/methods/lifecycle/startautopersisting/"><code>startAutoPersisting</code></a> method</h2><p>The new <a href="https://beta.tinybase.org/api/persisters/interfaces/persister/persister/methods/lifecycle/startautopersisting/"><code>startAutoPersisting</code></a> method and <a href="https://beta.tinybase.org/api/persisters/interfaces/persister/persister/methods/lifecycle/stopautopersisting/"><code>stopAutoPersisting</code></a> method on the <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> interface act as convenience methods for starting (and stopping) both the automatic loading and saving of data.</p><h2 id="new-createmergeablestore-getnow-parameter">New createMergeableStore getNow parameter</h2><p>The <a href="https://beta.tinybase.org/api/the-essentials/creating-stores/createmergeablestore/"><code>createMergeableStore</code></a> function now takes an optional <code>getNow</code> argument that lets you override the clock used to generate HLC timestamps.</p><h2 id="asynchronous-persister-synchronizer-methods">Asynchronous <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> &amp; <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> methods</h2><p>Please note that some methods in the <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> and <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> APIs are now asynchronous. Although most implementations of these methods are synchronous, some (particularly for Postgres-based databases) are no longer so and you are recommended to await them all.</p><p>The <a href="https://beta.tinybase.org/api/persisters/interfaces/persister/persister/methods/load/stopautoload/"><code>stopAutoLoad</code></a> method, the <a href="https://beta.tinybase.org/api/persisters/interfaces/persister/persister/methods/save/stopautosave/"><code>stopAutoSave</code></a> method, and the <a href="https://beta.tinybase.org/api/metrics/interfaces/metrics/metrics/methods/lifecycle/destroy/"><code>destroy</code></a> method in the base <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> interface have been marked asynchronous and return Promises. The <a href="https://beta.tinybase.org/api/synchronizers/interfaces/synchronizer/synchronizer/methods/synchronization/stopsync/"><code>stopSync</code></a> method in the <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> interface and the <a href="https://beta.tinybase.org/api/metrics/interfaces/metrics/metrics/methods/lifecycle/destroy/"><code>destroy</code></a> method in the <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> server interfaces should also be considered asynchronous.</p><hr><h1 id="v6-0">v6.0</h1><p>This major release is about updating dependencies and infrastructure rather than adding new features.</p><p>The most notable changes for users are:</p><ul><li>The package distribution only includes modern ESM packages (both minified and non-minified).</li><li>React 19 is now compatible as an optional peer dependency.</li><li>The tools module and TinyBase CLI have been removed.</li></ul><p>If you have been using CJS or UMD packages, you will need to update your bundling strategy for TinyBase (in the same way that you will have had to have done for React 19, for example) but this change should be compatible with most packaging tools. If you had been using the library directly a browser, you should consider the <a href="https://esm.sh/">esm.sh</a> CDN, as we have for our demos.</p><p>As a result of these changes, there have been some additional knock-on effects to the project and developer infrastructure as a whole. For example:</p><ul><li>The test suite has been updated to use <code>react-testing-library</code> instead of <code>react-test-renderer</code>.</li><li>The React <code>jsx-runtime</code> is used for JSX transformations.</li><li><a href="https://beta.tinybase.org/demos/">Demos</a> (and CodePen examples) have been updated to use an <code>importmap</code> mapping the modules to the <a href="https://esm.sh/">esm.sh</a> CDN.</li><li>ESLint has finally been upgraded to v9.</li></ul><p>Note that TinyBase v6.0 adds no new functionality, so you can afford to stay on v5.4.x for a while if these changes are somehow incompatible for you. However, all future functionality changes and bug fixes <em>will</em> take effect as v6.x releases (and probably won&#x27;t be back-ported to v5.4.x), so you should endeavor to upgrade as soon as you can.</p><p>Please let us know how these changes find you, and please file an issue on GitHub if you need help adapting to any of them.</p><hr><h1 id="v5-4">v5.4</h1><h2 id="durable-objects-synchronization">Durable Objects synchronization</h2><p>This release contains a new WebSocket synchronization server that runs on Cloudflare as a Durable Object.</p><p><embed src="https://beta.tinybase.org/durable.svg" title="Durable Objects"></p><p>It&#x27;s in the new <a href="https://beta.tinybase.org/api/synchronizer-ws-server-durable-object/"><code>synchronizer-ws-server-durable-object</code></a> module, and you use it by extending the <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/wsserverdurableobject/"><code>WsServerDurableObject</code></a> class. Use the <a href="https://beta.tinybase.org/api/synchronizer-ws-server-durable-object/functions/creation/getwsserverdurableobjectfetch/"><code>getWsServerDurableObjectFetch</code></a> function for conveniently binding your Cloudflare Worker to your Durable Object:</p> ```js yolo import { WsServerDurableObject, getWsServerDurableObjectFetch, } from 'tinybase/synchronizers/synchronizer-ws-server-durable-object'; export class MyDurableObject extends WsServerDurableObject {} export default {fetch: getWsServerDurableObjectFetch('MyDurableObjects')}; ``` <p>For the above code to work, you&#x27;ll need to have a Wrangler configuration that connects the <code>MyDurableObject</code> class to the <code>MyDurableObjects</code> namespace. In other words, you&#x27;ll have something like this in your <code>wrangler.toml</code> file:</p> ```toml [[durable_objects.bindings]] name = "MyDurableObjects" class_name = "MyDurableObject" ``` <p>With this you can now easily connect and synchronize clients that are using the <a href="https://beta.tinybase.org/api/synchronizer-ws-client/interfaces/synchronizer/wssynchronizer/"><code>WsSynchronizer</code></a> synchronizer.</p><h2 id="durable-objects-persistence">Durable Objects <a href="https://beta.tinybase.org/guides/persistence/">Persistence</a></h2><p>But wait! There&#x27;s more. Durable Objects also provide a storage mechanism, and sometimes you want TinyBase data to also be stored on the server (in case all the current clients disconnect and a new one joins, for example). So this release of TinyBase also includes a dedicated persister, the <a href="https://beta.tinybase.org/api/persister-durable-object-storage/interfaces/persister/durableobjectstoragepersister/"><code>DurableObjectStoragePersister</code></a>, that also synchronizes the data to the Durable Object storage layer.</p><p>You create it with the <a href="https://beta.tinybase.org/api/persister-durable-object-storage/functions/creation/createdurableobjectstoragepersister/"><code>createDurableObjectStoragePersister</code></a> function, and hook it into the Durable Object by returning it from the <a href="https://beta.tinybase.org/api/synchronizer-ws-server-durable-object/classes/creation/wsserverdurableobject/methods/creation/createpersister/"><code>createPersister</code></a> method of your <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/wsserverdurableobject/"><code>WsServerDurableObject</code></a>:</p> ```js yolo export class MyDurableObject extends WsServerDurableObject { createPersister() { return createDurableObjectStoragePersister( createMergeableStore(), this.ctx.storage, ); } } ``` <p>You can get started quickly with this architecture using the <a href="https://github.com/tinyplex/vite-tinybase-ts-react-sync-durable-object">new Vite template</a> that accompanies this release.</p><h2 id="server-reference-implementation">Server Reference Implementation</h2><p>Unrelated to Durable Objects, this release also includes the new <a href="https://beta.tinybase.org/api/synchronizer-ws-server-simple/"><code>synchronizer-ws-server-simple</code></a> module that contains a simple server implementation called <a href="https://beta.tinybase.org/api/synchronizer-ws-server-simple/interfaces/server/wsserversimple/"><code>WsServerSimple</code></a>. Without the complications of listeners, persistence, or statistics, this is more suitable to be used as a reference implementation for other server environments.</p><h2 id="architectural-guide">Architectural Guide</h2><p>To go with this release, we have added new documentation on ways in which you can use TinyBase in an app architecture. Check it out in the new <a href="https://beta.tinybase.org/guides/the-basics/architectural-options/">Architectural Options</a> guide.</p><p>We&#x27;ve also started a new section of documentation for describing integrations, of which the <a href="https://beta.tinybase.org/guides/integrations/cloudflare-durable-objects/">Cloudflare Durable Objects</a> guide, of course, is the first new entry!</p><hr><h1 id="v5-3">v5.3</h1><p>This release is focussed on a few API improvements and quality-of-life changes. These include:</p><h2 id="react-ssr-support">React SSR support</h2><p>Thanks to contributor <a href="https://github.com/muhajirdev">Muhammad Muhajir</a> for ensuring that TinyBase runs in server-side rendering environments!</p><h2 id="in-the-persisters-module">In the <a href="https://beta.tinybase.org/api/persisters/"><code>persisters</code></a> module...</h2><p>All <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> objects now expose information about whether they are loading or saving. To access this <a href="https://beta.tinybase.org/api/persisters/enumerations/lifecycle/status/"><code>Status</code></a>, use:</p><ul><li>The <a href="https://beta.tinybase.org/api/persisters/interfaces/persister/persister/methods/lifecycle/getstatus/"><code>getStatus</code></a> method, which will return 0 when it is idle, 1 when it is loading, and 2 when it is saving.</li><li>The <a href="https://beta.tinybase.org/api/persisters/interfaces/persister/persister/methods/listener/addstatuslistener/"><code>addStatusListener</code></a> method, which lets you add a <a href="https://beta.tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function and which is called whenever the status changes.</li></ul><p>These make it possible to track background load and save activities, so that, for example, you can show a status-bar spinner of asynchronous persistence activity.</p><h2 id="in-the-synchronizers-module">In the <a href="https://beta.tinybase.org/api/synchronizers/"><code>synchronizers</code></a> module...</h2><p>Synchronizers are a sub-class of <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a>, so all <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a> objects now also have:</p><ul><li>The <a href="https://beta.tinybase.org/api/persisters/interfaces/persister/persister/methods/lifecycle/getstatus/"><code>getStatus</code></a> method, which will return 0 when it is idle, 1 when it is &#x27;loading&#x27; (ie inbound syncing), and 2 when it is &#x27;saving&#x27; (ie outbound syncing).</li><li>The <a href="https://beta.tinybase.org/api/persisters/interfaces/persister/persister/methods/listener/addstatuslistener/"><code>addStatusListener</code></a> method, which lets you add a <a href="https://beta.tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function and which is called whenever the status changes.</li></ul><h2 id="in-the-ui-react-module">In the <a href="https://beta.tinybase.org/api/ui-react/"><code>ui-react</code></a> module...</h2><p>There are corresponding hooks so that you can build these status changes into a React UI easily:</p><ul><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/persister-hooks/usepersisterstatus/"><code>usePersisterStatus</code></a> hook, which will return the status for an explicitly provided, or context-derived <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a>.</li><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/persister-hooks/usepersisterstatuslistener/"><code>usePersisterStatusListener</code></a> hook, which lets you add your own <a href="https://beta.tinybase.org/api/persisters/type-aliases/listener/statuslistener/"><code>StatusListener</code></a> function to a <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a>.</li><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/persister-hooks/usepersister/"><code>usePersister</code></a> hook, which lets you get direct access to a <a href="https://beta.tinybase.org/api/the-essentials/persisting-stores/persister/"><code>Persister</code></a> from within your UI.</li></ul><p>And correspondingly for Synchronizers:</p><ul><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/synchronizer-hooks/usesynchronizerstatus/"><code>useSynchronizerStatus</code></a> hook, which will return the status for an explicitly provided, or context-derived <a href="https://beta.tinybase.org/api/the-essentials/synchronizing-stores/synchronizer/"><code>Synchronizer</code></a>.</li><li>The <a href="https://beta.tinybase.org/api/ui-react/functions/synchronizer-hooks/usesynchronizerstatuslistener/">