> ## Documentation Index
> Fetch the complete documentation index at: https://docs.collab-kit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Real-Time Stores

> Sync application state across users with schema-driven KV stores.

This guide shows you how to use CollabKit's Stores module to build a shared task board where all changes are synced in real time across connected clients.

## What You'll Build

* A shared task list with real-time sync
* Type-safe store operations with schema validation
* Live update rendering when other users make changes

## Prerequisites

* A CollabKit server running with a room and users created
* `@collab-kit/client` and `@collab-kit/utils` installed

## Step 1: Define Your Schema

Create a schema using `defineStores()`. This gives you full type safety and validation:

```typescript theme={null}
import { defineStores } from '@collab-kit/utils';

const stores = defineStores({
  tasks: {
    title: { type: 'string', required: true },
    completed: { type: 'boolean', default: false },
    assignee: { type: 'string' },
    priority: { type: 'number', default: 0 },
  },
});
```

### Schema Rules

* `required: true` -- field must be present when calling `set()` (unless it has a `default`)
* `default: value` -- applied automatically when the field is missing on `set()`
* Fields without `required: true` become optional in TypeScript types

## Step 2: Initialize the Client with Stores

```typescript theme={null}
import CollabKitClient from '@collab-kit/client';

const client = new CollabKitClient({
  serverUrl: 'https://api.collab-kit.com',
  authToken: '<jwt-token>',
  stores,
});

await client.join({ role: 'editor' });
```

<Note>
  Store writes require an editor session. Viewers can read stores and receive realtime store updates, but cannot call `set`, `update`, or `delete`.
</Note>

## Step 3: Create, Read, Update, Delete

```typescript theme={null}
// CREATE -- 'completed' defaults to false, 'priority' defaults to 0
const task = await client.stores.tasks.set({
  key: 'task-1',
  value: { title: 'Design mockups', assignee: 'user-001' },
});

// READ one
const fetched = await client.stores.tasks.get({ key: 'task-1' });
// { title: 'Design mockups', completed: false, assignee: 'user-001', priority: 0 }

// READ all
const allTasks = await client.stores.tasks.getAll();
// [{ key: 'task-1', value: { ... } }, ...]

// UPDATE (partial -- only changes specified fields)
await client.stores.tasks.update({
  key: 'task-1',
  value: { completed: true },
});

// DELETE
await client.stores.tasks.delete({ key: 'task-1' });
```

## Step 4: Listen for Real-Time Changes

Store events fire when **other clients** make changes:

```typescript theme={null}
// Listen for any change in the tasks store
client.stores.tasks.on('changed', ({ key, action, value }) => {
  console.log(`[${action}] ${key}:`, value);
  // action is 'set', 'update', or 'delete'
  renderTaskList();
});

// Listen for changes to a specific key
client.stores.tasks.on('task-1', (value) => {
  console.log('task-1 changed:', value);
  updateTaskUI('task-1', value);
});
```

## Step 5: Build the Task Board UI

```typescript theme={null}
const listEl = document.getElementById('task-list');
const formEl = document.getElementById('task-form');
const inputEl = document.getElementById('task-input') as HTMLInputElement;

// Render all tasks
async function renderTaskList() {
  const tasks = await client.stores.tasks.getAll();

  listEl.innerHTML = tasks.map(({ key, value }) => `
    <div class="task ${value.completed ? 'completed' : ''}" data-key="${key}">
      <input type="checkbox" ${value.completed ? 'checked' : ''}
        onchange="toggleTask('${key}', this.checked)" />
      <span>${value.title}</span>
      ${value.assignee ? `<small>Assigned to: ${value.assignee}</small>` : ''}
      <button onclick="deleteTask('${key}')">Delete</button>
    </div>
  `).join('');
}

// Add a new task
formEl.addEventListener('submit', async (e) => {
  e.preventDefault();
  const title = inputEl.value.trim();
  if (!title) return;

  const key = `task-${Date.now()}`;
  await client.stores.tasks.set({
    key,
    value: { title, assignee: client.userId },
  });

  inputEl.value = '';
  renderTaskList();
});

// Toggle completion
window.toggleTask = async (key: string, completed: boolean) => {
  await client.stores.tasks.update({ key, value: { completed } });
  renderTaskList();
};

// Delete a task
window.deleteTask = async (key: string) => {
  await client.stores.tasks.delete({ key });
  renderTaskList();
};

// Re-render when other clients make changes
client.stores.tasks.on('changed', () => renderTaskList());

// Initial render
renderTaskList();
```

## Multiple Stores

You can define multiple stores for different data types:

```typescript theme={null}
const stores = defineStores({
  tasks: {
    title: { type: 'string', required: true },
    completed: { type: 'boolean', default: false },
  },
  settings: {
    theme: { type: 'string', default: 'light' },
    sortBy: { type: 'string', default: 'created' },
  },
  labels: {
    name: { type: 'string', required: true },
    color: { type: 'string', required: true },
  },
});

// Each store is accessed independently
await client.stores.tasks.set({ key: 'task-1', value: { title: 'Ship it' } });
await client.stores.settings.set({ key: 'user-prefs', value: { theme: 'dark' } });
await client.stores.labels.set({ key: 'label-1', value: { name: 'Bug', color: '#ef4444' } });
```

## Syncing on Reconnect

When the client reconnects after a network interruption, call `sync()` to reload the latest state:

```typescript theme={null}
client.socket.on('reconnected', async () => {
  await client.stores.tasks.sync();
  renderTaskList();
});
```

## Next Steps

* Add [comments](/guides/comments-system) to individual tasks
* Use [presence](/guides/cursor-tracking) to show which task a user is viewing
* Set up [webhooks](/guides/webhooks-workflows) to notify external services when tasks change
