v0.3.0
livestorejs/livestorev0.3.0May 22, 2025by schickling
AI Summary
This release introduces a new git-like push/pull sync implementation, new adapters for Node and Solid, and the ElectricSQL sync backend, alongside a major restructuring of the schema API to separate state and events.
Key Highlights
- New git-style push/pull sync semantics with configurable initial sync behavior.
- New Node adapter (@livestore/adapter-node) and Solid integration (@livestore/solid).
- New ElectricSQL sync backend (@livestore/sync-electric).
- Schema API restructure separating state and events.
- React 19 support.
Breaking Changes
- Adapter packages renamed (@livestore/web -> @livestore/adapter-web).
- @livestore/db-schema moved to @livestore/common/schema.
- store.mutate renamed to store.commit.
- Schema API split into state and events (materializers introduced).
- useScopedQuery removed, replaced by useQuery.
- makeAdapter renamed to makePersistedAdapter.
- localOnly renamed to clientOnly.
- makeBackend renamed to backend.
- Query execution methods changed (query$.run() -> store.query()).
- Removed store.__execute, globalReactivityGraph, and persisted option.
- Storage format version bumped to 3.
- GraphQL moved to @livestore/graphql.
- Devtools path changed to /_livestore.
New Features
- Write queries in query builder (insert, update, delete).
- @livestore/peer-deps package for simplified dependency management.
- SQLite WAL mode for improved performance.
- Improved devtools with sync and eventlog views.
- Expo adapter syncing support.
Full Release Notes
### New features
- New sync implementation (based on git-like push/pull semantics)
- See [Syncing docs page](https://livestore.dev/docs/reference/syncing/syncing/) for more details
- `sync-cf` backend: More reliable websocket connection handling
- Configurable sync semantics when app starts (either skip initial sync or block with timeout)
- New: Node adapter `@livestore/adapter-node` (experimental)
- Note: Currently uses the `@livestore/sqlite-wasm` build but the plan is to move to a native SQLite build in the future to improve performance and reduce bundle size.
- Still lacks a few devtools-related flows (e.g. graceful import/reset)
- New: `@livestore/sync-electric` backend (experimental)
- See [docs page](https://livestore.dev/docs/reference/syncing/electricsql/) for more details
- New: `@livestore/adapter-expo` now supports syncing (requires Expo 53 or later):
```ts
const adapter = makePersistedAdapter({
sync: { backend: makeCfSync({ url: `https://...` }) },
})
```
- New: Solid integration `@livestore/solid` (experimental)
- Still very early stage and probably lacks some features. Feedback wanted!
- Thank you to [@kulshekhar](https://github.com/kulshekhar) for the initial implementation! (See [PR #225](https://github.com/livestorejs/livestore/pull/225))
- There are is still a lot of work to be done - contributions welcome!
### Breaking changes
- Breaking: Renamed adapter packages:
- `@livestore/web` now is `@livestore/adapter-web`
- `@livestore/expo` now is `@livestore/adapter-expo`
- Breaking: Removed `@livestore/db-schema` package and moved to `@livestore/common/schema`
- Breaking: Renamed `store.mutate` to `store.commit`
- Reason: Make it more clear that committing mutations is also syncing them across other clients
- Breaking: Adjusted schema API
- The new API aims to separate the schema into state and events
- Mutations are now split up into event definitions and materializer functions
Before:
```ts
// mutations.ts
import { Schema, defineMutation } from '@livestore/livestore'
// Mutations are now split up into event definitions and materializer functions
export const todoCreated = defineMutation('todoCreated',
Schema.Struct({
id: DbSchema.text(),
text: DbSchema.text(),
}),
sql`INSERT INTO todos (id, text) VALUES (${id}, ${text})`,
)
// schema.ts
import { DbSchema, makeSchema } from '@livestore/livestore'
import * as mutations from './mutations.js'
const todos = DbSchema.table('todos', {
id: DbSchema.text({ primaryKey: true }),
text: DbSchema.text(),
})
const uiState = DbSchema.table('uiState', {
id: DbSchema.text({ primaryKey: true }),
newTodoText: DbSchema.text(),
filter: DbSchema.text({ }),
}, {
derivedMutations: { clientOnly: true }
})
const tables = { todos, uiState }
const schema = makeSchema({ tables, mutations })
```
After:
```ts
// events.ts
import { Events, Schema } from '@livestore/livestore'
export const todoCreated = Events.synced({
name: 'todoCreated',
schema: Schema.Struct({ id: Schema.String, text: Schema.String, }),
})
// schema.ts
import { State, Schema, makeSchema } from '@livestore/livestore'
import * as events from './events.js'
const todos = State.SQLite.table({
name: 'todos',
columns: {
id: State.SQLite.text({ primaryKey: true }),
text: State.SQLite.text(),
}
})
// tables with `deriveMutations` are now called `clientDocuments`
const uiState = State.SQLite.clientDocument({
name: 'uiState',
schema: Schema.Struct({
newTodoText: Schema.String,
filter: Schema.String,
}),
})
const tables = { todos, uiState }
// Materalizers let you materialize events into the state
const materializers = State.SQLite.materializers(events, {
'v1.TodoCreated': ({ id, text }) => todos.insert({ id, text }),
})
// Currently SQLite is the only supported state implementation but there might be more in the future (e.g. pure in-memory JS, DuckDB, ...)
const state = State.SQLite.makeState({ tables, materializers })
// Schema is now more clearly separated into state and events
const schema = makeSchema({ state, events })
```
- Breaking `@livestore/react`: Removed `useScopedQuery` in favour of `useQuery`. Migration example:
```ts
// before
const query$ = useScopedQuery(() => queryDb(tables.issues.query.where({ id: issueId }).first()), ['issue', issueId])
// after
const query$ = useQuery(queryDb(tables.issues.query.where({ id: issueId }).first(), { deps: `issue-${issueId}` }))
```
- Breaking `@livestore/adapter-web`: Renamed `makeAdapter` to `makePersistedAdapter`
- Breaking `@livestore/adapter-expo`: Renamed `makeAdapter` to `makePersistedAdapter`
- Breaking: Renamed `localOnly` to `clientOnly` in table/mutation definitions.
- Breaking: Renamed `makeBackend` to `backend` in sync options.
- Breaking `@livestore/react`: `useClientDocument` now only works with for tables with client-only derived mutations.
- Breaking: Instead of calling `query$.run()` / `query$.runAndDestroy()`, please use `store.query(query$)` instead.
- Breaking: Removed `store.__execute` from `Store`.
- Breaking: Removed `globalReactivityGraph` and explicit passing of `reactivityGraph` to queries.
- Breaking: Removed `persisted` option from `store.commit`. This will be superceded by [eventlog compaction](https://github.com/livestorejs/livestore/issues/136) in the future.
- Breaking: The new syncing implementation required some changes to the storage format. The `liveStoreStorageFormatVersion` has been bumped to `3` which will create new database files.
- Breaking: Moved `queryGraphQL` to `@livestore/graphql` and thus removing `graphql` from peer dependencies of `@livestore/livestore`.
- Moved dev helper methods from e.g. `store.__devDownloadDb()` to `store._dev.downloadDb()`
- Breaking `@livestore/sync-cf`: Renamed `makeWsSync` to `makeCfSync`
### Notable improvements & fixes
- Added support for write queries in the query builder
```ts
table.query.insert({ id: '123', name: 'Alice' })
table.query.insert({ id: '123', name: 'Alice' }).onConflict('id', 'ignore')
table.query.insert({ id: '123', name: 'Alice' }).returning('id')
table.query.update({ name: 'Bob' }).where({ id: '123' })
table.query.delete().where({ id: '123' })
```
- Introduced `@livestore/peer-deps` package to simplify dependency management for Livestore packages if you don't want to manually install all the peer dependencies yourself.
- Improved [documentation](https://livestore.dev/) (still a lot of work to do here)
- Shows a browser dialog when trying to close a tab/window with unsaved changes
- The SQLite leader database now uses the WAL mode to improve performance and reliability. (Thanks [@IGassmann](https://github.com/IGassmann) for the contribution #259.)
- Improve Otel tracing integration
- Fix: The query builder now correctly handles `IN` and `NOT IN` where operations
- Fix: LiveStore crashes when using reserved keywords as a column name (`from`) #245
### Devtools
- Changed devtools path from `/_devtools.html` to `/_livestore`
- General connection stability improvements
- Improved sync view:
- See sync heads in real-time
- Connect/disconnect button
- Improved eventlog view:
- Client-only mutations are now highlighted
- Added `clientId` / `sessionId` columns
- Grouped slow queries and live queries under new queries tab
- Added SQLite query playground
- Fix: Data browser now more clearly highlights selected table #239
### Examples
- Reworked the Linearlite React example. (Thanks [@lukaswiesehan](https://github.com/lukaswiesehan) for the contribution #248.)
- Adjusted mutation names to use past-tense
- Added Otel to `todomvc` and `todomvc-sync-cf` example
### Internal changes
- Embraced git-style push/pull semantics to sync mutations across the system
- Added node syncing integration tests
- Got rid of the coordinator abstraction in favour of a clear separation between leader and client sessions
- Renamed from `EventId.local` to `EventSequenceNumber.client`
- Added `@livestore/sqlite-wasm` package which wraps `@livestore/wa-sqlite` and exposes web and Node.js compatible VFS implementations
- New devtools protocol via webmesh
- Should improve reliability of devtools connection (particularly during app reloads)
- Large refactoring to share more code between adapters
- Renamed `SynchronousDatabase` to `SqliteDb`
- Upgrade to TypeScript 5.8
- Upgraded dependencies
- Now supports React 19
- `effect` (needs to be 3.15.2 or higher)
- `@livestore/wa-sqlite` (needs to be 1.0.5-dev.2)