https://github.com/vlcn-io/js/assets/1009003/bd6f235b-ca94-46d0-a996-872c1cfc27e6 I've implemented [Linearlite](https://github.com/electric-sql/electric/tree/main/examples/linearlite) in both [LiveStore](https://github.com/livestorejs/livestore) and [Vulcan](https://vlcn.io/) to compare APIs with Materialite next up. Below are diffs of each component of Linearite, implemented with LiveStore then Vulcan. ```diff - LiveStore code is in RED + Vulcan code is in GREEN ``` # Some high level differences: 1. Cross tab sync: 1. Vulcan automatically supports cross tab sync 2. LiveStore lacks cross tab sync 2. Limits 1. Vulcan pages data in as needed. We can support several gigs of issues and data and donly use 32MB of RAM. 2. LiveStore requires all data to fit into memory. I.e., several gigs of issues -> several gigs of RAM + more for additional indices 3. Perf 1. Vulcan queries are async, not blocking the UI thread if desired. However we're limited to 1,000 writes per second. 2. LiveStore can do 10x the writes per second given everything is in-memory 4. Async 1. For Vulcan, the DB can be in the main thread or in a worker and the user facing APIs remain the same. 2. Given the nature of React controlled inputs, Vulcan requires an extra `useState` for text inputs to not cursor-jump 3. N/a for LiveStore 5. Type Safety 1. Vulcan schema definition is normal SQL with types generated via https://github.com/vlcn-io/typed-sql. Read and writes are fully typed as typed-sql generates types for each query. 2. LiveStore schema definition is a custom DSL and type safety is lacking on both read and write paths 6. Migrations 1. Vulcan auto-migrates your tables on changes to them, retaining your data 2. LiveStore drops all data and re-creates all tables of schema change 7. Vulcan supports fractional indexing inside of SQLite itself as a new index type, thus the [kanban board](https://gist.github.com/tantaman/f5faa1c627b69aa68f45066f17eae2a1#file-issueboard-tsx-diff) is almost no code in Vulcan. This extension could be ported to LiveStore. # Reactivity Difference - Vulcan embraces React's reactivity system. - LiveStore bolts on its own. This mean Vulcan just has one API: `useQuery` and LiveStore has `useQuery`, `querySQL`, `get`, `pipe`, `useTemporaryQuery`. To make this concrete, lets look at the issue list. In **LiveStore**: ```ts const filterClause$ = querySQL(`select * from app_state WHERE key = 'filter_state';`) .pipe((filterStates) => { if (filterStates.length === 0) return '' const filterStateObj = JSON.parse(filterStates[0]!.value) return filterStateToWhere(filterStateObj) + ' ' + filterStateToOrder(filterStateObj) }) const visibleIssues$ = querySQL((get) => sql`select * from issue ${get(filterClause$)}`) function List({ showSearch = false }) { const issues = useQuery(visibleIssues$) ... ``` In **Vulcan**: ```ts function List({ showSearch = false }) { const ctx = useDB(DBName) const filterState = first(useQuery(ctx, `SELECT * FROM filter_state`).data) const issues = useQuery( ctx, `SELECT * FROM issues ${filterStateToWhere(filterState} ${filterStateToOrder(filterState)}` ).data ?? [] ... ``` When `filterState` changes, the `issues` query will re-run by virtue of React's reactivity system. **LiveStore** does have a huge advantage here, however. React's reactivity systems does not allow for atomic updates. The filter state changes, the component renders, then the issues changes, the component renders again. This leads to some flickering and UI artifacting. E.g., the readout about "X issues of Y issues" goes away before the issue list changes since the readout depends on filter state and issue list on issue state. LiveStore doesn't render until both are updated since LiveStore controls the reactivity.