# Vue integration for LiveStore

The [vue-livestore](https://github.com/slashv/vue-livestore) package provides integration with Vue. It's currently in beta but aims to match feature parity with the React integration.

## API

### `<LiveStoreProvider>`

In order to use LiveStore with Vue, you need to wrap your application in a `<LiveStoreProvider>`.


## `reference/framework-integrations/vue/provider.vue`

```vue filename="reference/framework-integrations/vue/provider.vue"
<script setup lang="ts">
import { makeInMemoryAdapter } from '@livestore/adapter-web'

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

const adapter = makeInMemoryAdapter()
const storeId = 'demo-store'
const _options = { schema, adapter, storeId }
</script>

<template>
  <LiveStoreProvider :options="options">
    <template #loading>
      <div>Loading LiveStore...</div>
    </template>
    <slot />
  </LiveStoreProvider>
</template>
```

### `reference/framework-integrations/react/schema.ts`

```ts filename="reference/framework-integrations/react/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, SessionIdSymbol, State } from '@livestore/livestore'

export 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 }),
      createdAt: State.SQLite.datetime(),
    },
  }),
  uiState: State.SQLite.clientDocument({
    name: 'UiState',
    schema: Schema.Struct({
      newTodoText: Schema.String,
      filter: Schema.Literal('all', 'active', 'completed'),
    }),
    default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
  }),
} as const

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

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

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

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

### `reference/framework-integrations/vue/schema.ts`

```ts filename="reference/framework-integrations/vue/schema.ts"
export { events, schema, tables } from '../react/schema.ts'
```

### useStore


## `reference/framework-integrations/vue/use-store.ts`

```ts filename="reference/framework-integrations/vue/use-store.ts"
import { useStore } from 'vue-livestore'

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

export const createTodo = () => {
  const { store } = useStore()

  store.commit(events.todoCreated({ id: crypto.randomUUID(), text: 'Eat broccoli', createdAt: new Date() }))
}
```

### `reference/framework-integrations/react/schema.ts`

```ts filename="reference/framework-integrations/react/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, SessionIdSymbol, State } from '@livestore/livestore'

export 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 }),
      createdAt: State.SQLite.datetime(),
    },
  }),
  uiState: State.SQLite.clientDocument({
    name: 'UiState',
    schema: Schema.Struct({
      newTodoText: Schema.String,
      filter: Schema.Literal('all', 'active', 'completed'),
    }),
    default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
  }),
} as const

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

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

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

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

### `reference/framework-integrations/vue/schema.ts`

```ts filename="reference/framework-integrations/vue/schema.ts"
export { events, schema, tables } from '../react/schema.ts'
```

### useQuery


## `reference/framework-integrations/vue/use-query.vue`

```vue filename="reference/framework-integrations/vue/use-query.vue"
<script setup lang="ts">
import { useQuery } from 'vue-livestore'

import { queryDb } from '@livestore/livestore'

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

const visibleTodos$ = queryDb(() => tables.todos.where({ completed: false }), { label: 'visibleTodos' })

// biome-ignore lint/correctness/useHookAtTopLevel: Vue composables run at script setup level
const todos = useQuery(visibleTodos$)

void todos
</script>

<template>
  <div>
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>
```

### `reference/framework-integrations/react/schema.ts`

```ts filename="reference/framework-integrations/react/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, SessionIdSymbol, State } from '@livestore/livestore'

export 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 }),
      createdAt: State.SQLite.datetime(),
    },
  }),
  uiState: State.SQLite.clientDocument({
    name: 'UiState',
    schema: Schema.Struct({
      newTodoText: Schema.String,
      filter: Schema.Literal('all', 'active', 'completed'),
    }),
    default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
  }),
} as const

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

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

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

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

### `reference/framework-integrations/vue/schema.ts`

```ts filename="reference/framework-integrations/vue/schema.ts"
export { events, schema, tables } from '../react/schema.ts'
```

### useClientDocument

**[!] The interface for useClientDocument is experimental and might change**

Since it's more common in Vue to work with a single writable ref (as compared to state, setState in React) the useClientDocument composable for Vue tries to make that easier by directly returning a collection of refs.

The current implementation destructures all client state variables into the return object which allows directly binding to v-model or editing the .value reactivly.


## `reference/framework-integrations/vue/use-client-document.vue`

```vue filename="reference/framework-integrations/vue/use-client-document.vue"
<script setup lang="ts">
import { useClientDocument } from 'vue-livestore'

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

// biome-ignore lint/correctness/useHookAtTopLevel: Vue composables run at script setup level
const { newTodoText, filter } = useClientDocument(tables.uiState)

void newTodoText
void filter

void newTodoText
void filter
</script>

<template>
  <div>
    <input type="text" v-model="newTodoText" />

    <select v-model="filter">
      <option value="all">All</option>
      <option value="active">Active</option>
      <option value="completed">Completed</option>
    </select>
  </div>
</template>
```

### `reference/framework-integrations/react/schema.ts`

```ts filename="reference/framework-integrations/react/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, SessionIdSymbol, State } from '@livestore/livestore'

export 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 }),
      createdAt: State.SQLite.datetime(),
    },
  }),
  uiState: State.SQLite.clientDocument({
    name: 'UiState',
    schema: Schema.Struct({
      newTodoText: Schema.String,
      filter: Schema.Literal('all', 'active', 'completed'),
    }),
    default: { id: SessionIdSymbol, value: { newTodoText: '', filter: 'all' } },
  }),
} as const

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

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

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

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

### `reference/framework-integrations/vue/schema.ts`

```ts filename="reference/framework-integrations/vue/schema.ts"
export { events, schema, tables } from '../react/schema.ts'
```

## Usage with ...

### Vite

LiveStore and vue-livestore works with Vite out of the box.

### Nuxt.js

Works out of the box with Nuxt if SSR is disabled by just wrapping the main content in a LiveStoreProvider. Example repo upcoming.

## Technical notes

- Vue-livestore uses the provider component pattern similar to the React integration. In Vue the plugin pattern is more common but it isn't clear that that's the most suitable structure for LiveStore in Vue. We might switch to the plugin pattern if we later find that more suitable especially with regards to Nuxt support and supporting multiple stores.