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)