# Store

The `Store` is the most common way to interact with LiveStore from your application code. It provides a way to query data, commit events, and subscribe to data changes.

## Creating a store

For how to create a store in React, see the [React integration docs](/framework-integrations/react-integration). The following example shows how to create a store manually:


## `reference/store/create-store.ts`

```ts filename="reference/store/create-store.ts"
import { makeAdapter } from '@livestore/adapter-node'
import { createStorePromise } from '@livestore/livestore'

import { schema } from './schema.ts'

const adapter = makeAdapter({
  storage: { type: 'fs' },
  // sync: { backend: makeWsSync({ url: '...' }) },
})

export const bootstrap = async () => {
  const store = await createStorePromise({
    schema,
    adapter,
    storeId: 'some-store-id',
  })

  return store
}
```

### `reference/store/schema.ts`

```ts filename="reference/store/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, State } from '@livestore/livestore'

const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text(),
      completed: State.SQLite.boolean({ default: false }),
    },
  }),
} as const

const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
  }),
} as const

const materializers = State.SQLite.materializers(events, {
  [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
    tables.todos.insert({ id, text, completed: false }),
  ),
})

const state = State.SQLite.makeState({ tables, materializers })

export const schema = makeSchema({ events, state })
export const storeTables = tables
export const storeEvents = events
```

## Using a store

### Querying data


## `reference/store/query-data.ts`

```ts filename="reference/store/query-data.ts"
/** biome-ignore-all lint/correctness/noUnusedVariables: docs snippet shows query result */
// ---cut---
import type { Store } from '@livestore/livestore'

import { storeTables } from './schema.ts'

declare const store: Store

const todos = store.query(storeTables.todos)
console.log(todos)
```

### `reference/store/schema.ts`

```ts filename="reference/store/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, State } from '@livestore/livestore'

const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text(),
      completed: State.SQLite.boolean({ default: false }),
    },
  }),
} as const

const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
  }),
} as const

const materializers = State.SQLite.materializers(events, {
  [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
    tables.todos.insert({ id, text, completed: false }),
  ),
})

const state = State.SQLite.makeState({ tables, materializers })

export const schema = makeSchema({ events, state })
export const storeTables = tables
export const storeEvents = events
```

### Subscribing to data


## `reference/store/subscribe.ts`

```ts filename="reference/store/subscribe.ts"
import type { Store } from '@livestore/livestore'

import { storeTables } from './schema.ts'

declare const store: Store

const unsubscribe = store.subscribe(storeTables.todos, (todos) => {
  console.log(todos)
})

unsubscribe()
```

### `reference/store/schema.ts`

```ts filename="reference/store/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, State } from '@livestore/livestore'

const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text(),
      completed: State.SQLite.boolean({ default: false }),
    },
  }),
} as const

const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
  }),
} as const

const materializers = State.SQLite.materializers(events, {
  [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
    tables.todos.insert({ id, text, completed: false }),
  ),
})

const state = State.SQLite.makeState({ tables, materializers })

export const schema = makeSchema({ events, state })
export const storeTables = tables
export const storeEvents = events
```

### Committing events


## `reference/store/commit-event.ts`

```ts filename="reference/store/commit-event.ts"
import type { Store } from '@livestore/livestore'

import { storeEvents } from './schema.ts'

declare const store: Store

store.commit(storeEvents.todoCreated({ id: '1', text: 'Buy milk' }))
```

### `reference/store/schema.ts`

```ts filename="reference/store/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, State } from '@livestore/livestore'

const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text(),
      completed: State.SQLite.boolean({ default: false }),
    },
  }),
} as const

const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
  }),
} as const

const materializers = State.SQLite.materializers(events, {
  [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
    tables.todos.insert({ id, text, completed: false }),
  ),
})

const state = State.SQLite.makeState({ tables, materializers })

export const schema = makeSchema({ events, state })
export const storeTables = tables
export const storeEvents = events
```

### Streaming events

Currently only events confirmed by the sync backend are supported.


## `reference/store/stream-events.ts`

```ts filename="reference/store/stream-events.ts"
import type { Store } from '@livestore/livestore'

declare const store: Store

// Run once
for await (const event of store.events()) {
  console.log('event from leader', event)
}

// Continuos stream
const iterator = store.events()[Symbol.asyncIterator]()
try {
  while (true) {
    const { value, done } = await iterator.next()
    if (done === true) break
    console.log('event from stream:', value)
  }
} finally {
  await iterator.return?.()
}
```

### Shutting down a store

LiveStore provides two APIs for shutting down a store:


## `reference/store/shutdown.ts`

```ts filename="reference/store/shutdown.ts"
/** biome-ignore-all lint/correctness/noUnusedVariables: docs snippet demonstrates shutdown helpers */
// ---cut---

import { Effect } from 'effect'

import type { Store } from '@livestore/livestore'

declare const store: Store

const effectShutdown = Effect.gen(function* () {
  yield* Effect.log('Shutting down store')
  yield* store.shutdown()
})

const shutdownWithPromise = async () => {
  await store.shutdownPromise()
}
```

## Effect integration

For applications using [Effect](https://effect.website), LiveStore provides a type-safe way to access stores through the Effect layer system via `makeStoreContext()`.

### Creating a typed store context

Use `makeStoreContext()` to create a typed context that preserves your schema types:


## `reference/store/effect/make-store-context.ts`

```ts filename="reference/store/effect/make-store-context.ts"
import { makeAdapter } from '@livestore/adapter-node'
import { Store } from '@livestore/livestore/effect'

import { schema } from './schema.ts'

// ---cut---
// Define a typed store context with your schema
export const TodoStore = Store.Tag(schema, 'todos')

// Create a layer to initialize the store
const adapter = makeAdapter({ storage: { type: 'fs' } })

export const TodoStoreLayer = TodoStore.layer({
  adapter,
  batchUpdates: (cb) => cb(), // For Node.js; use React's unstable_batchedUpdates in React apps
})
```

### `reference/store/effect/schema.ts`

```ts filename="reference/store/effect/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, State } from '@livestore/livestore'

const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text(),
      completed: State.SQLite.boolean({ default: false }),
    },
  }),
} as const

const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
  }),
  todoCompleted: Events.synced({
    name: 'v1.TodoCompleted',
    schema: Schema.Struct({ id: Schema.String }),
  }),
} as const

const materializers = State.SQLite.materializers(events, {
  [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
    tables.todos.insert({ id, text, completed: false }),
  ),
  [events.todoCompleted.name]: defineMaterializer(events.todoCompleted, ({ id }) =>
    tables.todos.update({ completed: true }).where({ id }),
  ),
})

const state = State.SQLite.makeState({ tables, materializers })

export const schema = makeSchema({ events, state })
export const storeTables = tables
export const storeEvents = events
```

The factory takes your schema type as a generic parameter and returns a `StoreContext` with:
- `Tag` - Context tag for dependency injection
- `Layer` - Creates a layer that initializes the store
- `DeferredTag` - For async initialization patterns
- `DeferredLayer` - Layer providing the deferred context
- `fromDeferred` - Layer that waits for deferred initialization

### Using the store in Effect services

Access the store in Effect code with full type safety and autocomplete:


## `reference/store/effect/usage-in-service.ts`

```ts filename="reference/store/effect/usage-in-service.ts"
import { Effect } from 'effect'

import { TodoStore } from './make-store-context.ts'
import { storeEvents, storeTables } from './schema.ts'

// ---cut---
// Access the store in Effect code with full type safety
const _todoService = Effect.gen(function* () {
  // Yield the store directly (it's a Context.Tag)
  const { store } = yield* TodoStore

  // Query with autocomplete for tables
  const todos = store.query(storeTables.todos.select())

  // Commit events
  store.commit(storeEvents.todoCreated({ id: '1', text: 'Buy milk' }))

  return todos
})

// Or use static accessors for a more functional style
const _todoServiceAlt = Effect.gen(function* () {
  // Query using static accessor
  const todos = yield* TodoStore.query(storeTables.todos.select())

  // Commit using static accessor
  yield* TodoStore.commit(storeEvents.todoCreated({ id: '1', text: 'Buy milk' }))

  return todos
})
```

### `reference/store/effect/make-store-context.ts`

```ts filename="reference/store/effect/make-store-context.ts"
import { makeAdapter } from '@livestore/adapter-node'
import { Store } from '@livestore/livestore/effect'

import { schema } from './schema.ts'

// ---cut---
// Define a typed store context with your schema
export const TodoStore = Store.Tag(schema, 'todos')

// Create a layer to initialize the store
const adapter = makeAdapter({ storage: { type: 'fs' } })

export const TodoStoreLayer = TodoStore.layer({
  adapter,
  batchUpdates: (cb) => cb(), // For Node.js; use React's unstable_batchedUpdates in React apps
})
```

### `reference/store/effect/schema.ts`

```ts filename="reference/store/effect/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, State } from '@livestore/livestore'

const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text(),
      completed: State.SQLite.boolean({ default: false }),
    },
  }),
} as const

const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
  }),
  todoCompleted: Events.synced({
    name: 'v1.TodoCompleted',
    schema: Schema.Struct({ id: Schema.String }),
  }),
} as const

const materializers = State.SQLite.materializers(events, {
  [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
    tables.todos.insert({ id, text, completed: false }),
  ),
  [events.todoCompleted.name]: defineMaterializer(events.todoCompleted, ({ id }) =>
    tables.todos.update({ completed: true }).where({ id }),
  ),
})

const state = State.SQLite.makeState({ tables, materializers })

export const schema = makeSchema({ events, state })
export const storeTables = tables
export const storeEvents = events
```

### Layer composition

Compose store layers with your application services:


## `reference/store/effect/layer-composition.ts`

```ts filename="reference/store/effect/layer-composition.ts"
import { Effect, Layer } from 'effect'

import { TodoStore, TodoStoreLayer } from './make-store-context.ts'
import { storeEvents } from './schema.ts'

// ---cut---
// Define services that depend on the store
class TodoService extends Effect.Service<TodoService>()('TodoService', {
  effect: Effect.gen(function* () {
    const { store } = yield* TodoStore

    const createTodo = (id: string, text: string) =>
      Effect.sync(() => store.commit(storeEvents.todoCreated({ id, text })))

    const completeTodo = (id: string) => Effect.sync(() => store.commit(storeEvents.todoCompleted({ id })))

    return { createTodo, completeTodo } as const
  }),
  dependencies: [TodoStoreLayer],
}) {}

// Compose everything into a main layer
const MainLayer = Layer.mergeAll(TodoStoreLayer, TodoService.Default)

// Use in your application
const program = Effect.gen(function* () {
  const todoService = yield* TodoService
  yield* todoService.createTodo('1', 'Learn Effect')
  yield* todoService.completeTodo('1')
})

// Provide MainLayer when running (OtelTracer is also required)
void program.pipe(Effect.provide(MainLayer))
```

### `reference/store/effect/make-store-context.ts`

```ts filename="reference/store/effect/make-store-context.ts"
import { makeAdapter } from '@livestore/adapter-node'
import { Store } from '@livestore/livestore/effect'

import { schema } from './schema.ts'

// ---cut---
// Define a typed store context with your schema
export const TodoStore = Store.Tag(schema, 'todos')

// Create a layer to initialize the store
const adapter = makeAdapter({ storage: { type: 'fs' } })

export const TodoStoreLayer = TodoStore.layer({
  adapter,
  batchUpdates: (cb) => cb(), // For Node.js; use React's unstable_batchedUpdates in React apps
})
```

### `reference/store/effect/schema.ts`

```ts filename="reference/store/effect/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, State } from '@livestore/livestore'

const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text(),
      completed: State.SQLite.boolean({ default: false }),
    },
  }),
} as const

const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
  }),
  todoCompleted: Events.synced({
    name: 'v1.TodoCompleted',
    schema: Schema.Struct({ id: Schema.String }),
  }),
} as const

const materializers = State.SQLite.materializers(events, {
  [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
    tables.todos.insert({ id, text, completed: false }),
  ),
  [events.todoCompleted.name]: defineMaterializer(events.todoCompleted, ({ id }) =>
    tables.todos.update({ completed: true }).where({ id }),
  ),
})

const state = State.SQLite.makeState({ tables, materializers })

export const schema = makeSchema({ events, state })
export const storeTables = tables
export const storeEvents = events
```

### Multiple stores with Effect

Each store gets a unique context tag, allowing multiple stores in the same Effect context:


## `reference/store/effect/multiple-stores.ts`

```ts filename="reference/store/effect/multiple-stores.ts"
import { Effect, Layer } from 'effect'

import { makeAdapter } from '@livestore/adapter-node'
import { Store } from '@livestore/livestore/effect'

import { schema as mainSchema } from './schema.ts'

// For demonstration, we'll use the same schema for both stores
const settingsSchema = mainSchema

// ---cut---
// Define multiple typed store contexts
const MainStore = Store.Tag(mainSchema, 'main')
const SettingsStore = Store.Tag(settingsSchema, 'settings')

// Each store has its own layer
const adapter = makeAdapter({ storage: { type: 'fs' } })

const MainStoreLayer = MainStore.layer({ adapter, batchUpdates: (cb) => cb() })
const SettingsStoreLayer = SettingsStore.layer({ adapter, batchUpdates: (cb) => cb() })

// Compose layers together
const _AllStoresLayer = Layer.mergeAll(MainStoreLayer, SettingsStoreLayer)

// Both stores available in Effect code
const _program = Effect.gen(function* () {
  const { store: mainStore } = yield* MainStore
  const { store: settingsStore } = yield* SettingsStore

  // Each store is independently typed
  return { mainStore, settingsStore }
})
```

### `reference/store/effect/schema.ts`

```ts filename="reference/store/effect/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, State } from '@livestore/livestore'

const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text(),
      completed: State.SQLite.boolean({ default: false }),
    },
  }),
} as const

const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
  }),
  todoCompleted: Events.synced({
    name: 'v1.TodoCompleted',
    schema: Schema.Struct({ id: Schema.String }),
  }),
} as const

const materializers = State.SQLite.materializers(events, {
  [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
    tables.todos.insert({ id, text, completed: false }),
  ),
  [events.todoCompleted.name]: defineMaterializer(events.todoCompleted, ({ id }) =>
    tables.todos.update({ completed: true }).where({ id }),
  ),
})

const state = State.SQLite.makeState({ tables, materializers })

export const schema = makeSchema({ events, state })
export const storeTables = tables
export const storeEvents = events
```

For more Effect patterns including Effect Atom integration, see the [Effect patterns](/patterns/effect) page.

## Multiple stores

You can create and use multiple stores in the same app. This can be useful when breaking up your data model into smaller pieces.

## Sync Status

LiveStore provides APIs to monitor the synchronization status between the client session and the leader thread. This is useful for displaying sync indicators or performing health checks.

### SyncStatus type

```ts
type SyncStatus = {
  localHead: string      // e.g., "e5.2" or "e5.2r1"
  upstreamHead: string   // e.g., "e3"
  pendingCount: number   // Number of events pending sync
  isSynced: boolean      // true when pendingCount === 0
}
```

### `store.syncStatus()`

Returns the current sync status synchronously.

```ts
const status = store.syncStatus()
if (!status.isSynced) {
  console.log(`${status.pendingCount} events pending sync`)
}
```

### `store.subscribeSyncStatus(callback)`

Subscribes to sync status changes. The callback is invoked immediately with the current status and whenever the sync state changes.

```ts
const unsubscribe = store.subscribeSyncStatus((status) => {
  updateUI(status.isSynced ? 'Synced' : 'Syncing...')
})

// Later, stop listening
unsubscribe()
```

### `store.syncStatusStream()`

Returns an Effect Stream of sync status updates. For Effect-based workflows.

```ts
import { Effect, Stream } from 'effect'

store.syncStatusStream().pipe(
  Stream.tap((status) => Effect.log(`Sync: ${status.isSynced}`)),
  Stream.runDrain,
)
```

For React, see [`store.useSyncStatus()`](/framework-integrations/react-integration#storeusesyncstatus).

## Development/debugging helpers

A store instance also exposes a `_dev` property that contains some helpful methods for development. For convenience you can access a store on `globalThis`/`window` like via `__debugLiveStore.default._dev` (`default` is the store id):

```ts
// Download the SQLite database
__debugLiveStore.default._dev.downloadDb()

// Download the eventlog database
__debugLiveStore.default._dev.downloadEventlogDb()

// Reset the store
__debugLiveStore.default._dev.hardReset()

// See the current sync state
__debugLiveStore.default._dev.syncStates()
```