# Web API

# React.ChannelErrors

Extends `unknown`.

Show error message to user on subscription errors in components deep in the tree.

```js
import { ChannelErrors } from '@logux/client/react'

export const App: FC = () => {
  return <>
    <SideMenu />
    <ChannelErrors
      NotFound={NotFoundPage}
      AccessDenied={AccessDeniedPage}
      Error={ServerErrorPage}
    >
      <Layout />
    </ChannelErrors>
  <>
}
```

## `React.ClientContext`

Context to send Logux Client or object space to components deep in the tree.

```js
import { ClientContext, ChannelErrors } from '@logux/client/react'
import { CrossTabClient } from '@logux/client'

let client = new CrossTabClient(…)

render(
 <ClientContext.Provider value={client}>
   <ChannelErrors NotFound={Page404} AccessDenied={Page403}>
     <App />
   </ChannelErrors>
 </ClientContext.Provider>,
 document.body
)
```

Type: `ReactContext`.

## `React.ErrorsContext`

Context to pass error handlers from `ChannelErrors`.

Type: `ReactContext`.

# `React.useAuth()`

Hook to return user's current authentication state and ID.

```js
import { useAuth } from '@logux/client/react'

export const UserPage = () => {
  let { isAuthenticated, userId } = useAuth()
  if (isAuthenticated) {
    return <User id={userId} />
  } else {
    return <Loader />
  }
}
```

Returns `StoreValue`.

# `React.useClient()`

Hook to return Logux client, which you set by `<ClientContext.Provider>`.

```js
import { useClient } from '@logux/client/react'

import { User } from '../stores/user'

export const NewUserForm = () => {
  let client = useClient()
  let onAdd = data => {
    User.create(client, data)
  }
}
```

Returns `Client`.

# `React.useFilter(Template, filter?, opts?)`

The way to [`createFilter`](#globals-createfilter) in React.

```js
import { useFilter } from '@logux/client/react'

import { User } from '../stores/user'

export const Users = ({ projectId }) => {
  let users = useFilter(User, { projectId })
  return <div>
    {users.list.map(user => <User user={user} />)}
    {users.isLoading && <Loader />}
  </div>
}
```

| Argument   | Type                                     | Description                  |
| ---------- | ---------------------------------------- | ---------------------------- |
| `Template` | `SyncMapTemplate \| SyncMapTemplateLike` | Store template.              |
| `filter` ? | `Filter`                                 | Key-value filter for stores. |
| `opts` ?   | `FilterOptions`                          | Filter options.              |

Returns `StoreValue`. Filter store to use with map.

# `React.useSync(Template, id)`

Create store by ID, subscribe and get store’s value.

```js
import { useSync } from '@logux/client/react'

import { User } from '../stores/user'

export const UserPage: FC = ({ id }) => {
  let user = useSync(User, id)
  if (user.isLoading) {
    return <Loader />
  } else {
    return <h1>{user.name}</h1>
  }
}
```

| Argument   | Type                                     | Description    |
| ---------- | ---------------------------------------- | -------------- |
| `Template` | `SyncMapTemplate \| SyncMapTemplateLike` | Store builder. |
| `id`       | `string`                                 | Store ID.      |

| Argument   | Type                                     | Description            |
| ---------- | ---------------------------------------- | ---------------------- |
| `Template` | `SyncMapTemplate \| SyncMapTemplateLike` | Store builder.         |
| `id`       | `string`                                 | Store ID.              |
| `args`     | `Args`                                   | Other store arguments. |

Returns `SyncMapValue`. Store value.

## `Vue.ChannelErrors`

Show error message to user on subscription errors in components deep in the tree.

```html
<template>
  <channel-errors v-slot="{ code, error }">
    <layout v-if="!error" />
    <error v-else-if="code === 500" />
    <error-not-found v-else-if="code === 404" />
    <error-access-denied v-else-if="code === 403" />
  </channel-errors>
</template>

<script>
import { ChannelErrors } from '@logux/client/vue'

export default {
  components: { ChannelErrors }
}
</script>
```

Type: `Component`.

## `Vue.ChannelErrorsSlotProps`

| Property | Type        |
| -------- | ----------- |
| `code`   | `Ref<Type>` |
| `error`  | `Ref<Type>` |

## `Vue.ClientKey`

Type: `InjectionKey`.

## `Vue.ErrorsKey`

Type: `InjectionKey`.

# `Vue.loguxPlugin(app, client)`

Plugin that injects Logux Client into all components within the application.

```js
import { createApp } from 'vue'
import { loguxPlugin } from '@logux/client/vue'
import { CrossTabClient } from '@logux/client'

let client = new CrossTabClient(…)
let app = createApp(…)

app.use(loguxPlugin, client)
```

| Argument | Type     |
| -------- | -------- |
| `app`    | `App`    |
| `client` | `Client` |

# `Vue.useAuth(client?)`

Returns user's current authentication state and ID.

```html
<template>
  <user v-if="isAuthenticated" :id="userId" />
  <sign-in v-else />
</template>

<script>
import { useAuth } from '@logux/client/vue'

export default () => {
  let { isAuthenticated, userId } = useAuth()
  return { isAuthenticated, userId }
}
</script>
```

| Argument   | Type     | Description            |
| ---------- | -------- | ---------------------- |
| `client` ? | `Client` | Logux Client instance. |

Returns `{ isAuthenticated: ComputedRef, userId: ComputedRef }`.

# `Vue.useClient()`

Returns the Logux Client instance.

```html
<script>
import { useClient } from '@logux/client/vue'

import { User } from '../stores/user'

let client = useClient()
let onAdd = data => {
  User.create(client, data)
}
</script>
```

Returns `Client`.

# `Vue.useFilter(Template, filter?, opts?)`

The way to [`createFilter`](#globals-createfilter) in Vue.

```html
<template>
  <loader v-if="users.isLoading" />
  <user v-else v-for="user in users" :user="user" />
</template>

<script>
import { useFilter } from '@logux/client/vue'

import { User } from '../stores/user'

export default {
  props: ['projectId'],
  setup (props) {
    let users = useFilter(User, { projectId: props.projectId })
    return { users }
  }
}
</script>
```

| Argument   | Type                                     | Description                  |
| ---------- | ---------------------------------------- | ---------------------------- |
| `Template` | `SyncMapTemplate \| SyncMapTemplateLike` | Store class.                 |
| `filter` ? | `any`                                    | Key-value filter for stores. |
| `opts` ?   | `any`                                    | Filter options.              |

Returns `Ref<Type>`. Filter store to use with map.

# `Vue.useSync(Template, id)`

Create store by ID, subscribe to store changes and get store’s value.

```html
<template>
  <loader v-if="user.isLoading" />
  <h1 v-else>{{ user.name }}</h1>
</template>

<script>
import { useSync } from '@logux/client/vue'

import { User } from '../stores/user'

export default {
  props: ['id'],
  setup (props) {
    let user = useSync(User, props.id)
    return { user }
  }
}
</script>
```

| Argument   | Type                                     | Description     |
| ---------- | ---------------------------------------- | --------------- |
| `Template` | `SyncMapTemplate \| SyncMapTemplateLike` | Store template. |
| `id`       | `any`                                    | Store ID.       |

| Argument   | Type                                     | Description            |
| ---------- | ---------------------------------------- | ---------------------- |
| `Template` | `SyncMapTemplate \| SyncMapTemplateLike` | Store template.        |
| `id`       | `any`                                    | Store ID.              |
| `args`     | `Args`                                   | Other store arguments. |

Returns `Ref<Type>`. Store value.

# Preact.ChannelErrors

Extends `unknown`.

Show error message to user on subscription errors in components deep in the tree.

```js
import { ChannelErrors } from '@logux/client/preact'

export const App: FC = () => {
  return <>
    <SideMenu />
    <ChannelErrors
      NotFound={NotFoundPage}
      AccessDenied={AccessDeniedPage}
      Error={ServerErrorPage}
    >
      <Layout />
    </ChannelErrors>
  <>
}
```

## `Preact.ChannelErrors#render()`

Returns `ComponentChild`.

## `Preact.ClientContext`

Context to send Logux Client or object space to components deep in the tree.

```js
import { ClientContext, ChannelErrors } from '@logux/client/preact'
import { CrossTabClient } from '@logux/client'

let client = new CrossTabClient(…)

render(
 <ClientContext.Provider value={client}>
   <ChannelErrors NotFound={Page404} AccessDenied={Page403}>
     <App />
   </ChannelErrors>
 </ClientContext.Provider>,
 document.body
)
```

Type: `PreactContext`.

## `Preact.ErrorsContext`

Context to pass error handlers from `ChannelErrors`.

Type: `PreactContext`.

# `Preact.useAuth()`

Hook to return user's current authentication state and ID.

```js
import { useAuth } from '@logux/client/preact'

export const UserPage = () => {
  let { isAuthenticated, userId } = useAuth()
  if (isAuthenticated) {
    return <User id={userId} />
  } else {
    return <Loader />
  }
}
```

Returns `StoreValue`.

# `Preact.useClient()`

Hook to return Logux client, which you set by `<ClientContext.Provider>`.

```js
import { useClient } from '@logux/client/preact'

import { User } from '../stores/user'

export const NewUserForm = () => {
  let client = useClient()
  let onAdd = data => {
    User.create(client, data)
  }
}
```

Returns `Client`.

# `Preact.useFilter(Template, filter?, opts?)`

The way to [`createFilter`](#globals-createfilter) in React.

```js
import { useFilter } from '@logux/client/preact'

import { User } from '../stores/user'

export const Users = ({ projectId }) => {
  let users = useFilter(User, { projectId })
  return <div>
    {users.list.map(user => <User user={user} />)}
    {users.isLoading && <Loader />}
  </div>
}
```

| Argument   | Type                                     | Description                  |
| ---------- | ---------------------------------------- | ---------------------------- |
| `Template` | `SyncMapTemplate \| SyncMapTemplateLike` | Store class.                 |
| `filter` ? | `Filter`                                 | Key-value filter for stores. |
| `opts` ?   | `FilterOptions`                          | Filter options.              |

Returns `StoreValue`. Filter store to use with map.

# `Preact.useSync(Template, id)`

Create store by ID, subscribe and get store’s value.

```js
import { useSync } from '@logux/client/preact'

import { User } from '../stores/user'

export const UserPage: FC = ({ id }) => {
  let user = useSync(User, id)
  if (user.isLoading) {
    return <Loader />
  } else {
    return <h1>{user.name}</h1>
  }
}
```

| Argument   | Type                                     | Description     |
| ---------- | ---------------------------------------- | --------------- |
| `Template` | `SyncMapTemplate \| SyncMapTemplateLike` | Store template. |
| `id`       | `string`                                 | Store ID.       |

| Argument   | Type                                     | Description            |
| ---------- | ---------------------------------------- | ---------------------- |
| `Template` | `SyncMapTemplate \| SyncMapTemplateLike` | Store template.        |
| `id`       | `string`                                 | Store ID.              |
| `args`     | `Args`                                   | Other store arguments. |

Returns `SyncMapValue`. Store value.

# Client

Base class for browser API to be extended in [`CrossTabClient`](#crosstabclient).

Because this class could have conflicts between different browser tab, you should use it only if you are really sure, that application will not be run in different tab (for instance, if you are developing a kiosk app).

```js
import { Client } from '@logux/client'

const userId = document.querySelector('meta[name=user]').content
const token = document.querySelector('meta[name=token]').content

const client = new Client({
  credentials: token,
  subprotocol: 1,
  server: 'wss://example.com:1337',
  userId: userId
})
client.start()
```

| Parameter | Type            | Description     |
| --------- | --------------- | --------------- |
| `opts`    | `ClientOptions` | Client options. |

## `Client#clientId`

Unique permanent client ID. Can be used to track this machine.

Type: `string`.

## `Client#connected`

Is leader tab connected to server.

Type: `boolean`.

## `Client#log`

Client events log.

```js
client.log.add(action)
```

Type: `ClientLog`.

## `Client#node`

Node instance to synchronize logs.

```js
if (client.node.state === 'synchronized')
```

Type: `ClientNode`.

## `Client#nodeId`

Unique Logux node ID.

```js
console.log('Client ID: ', client.nodeId)
```

Type: `string`.

## `Client#options`

Client options.

```js
console.log('Connecting to ' + client.options.server)
```

Type: `ClientOptions`.

## `Client#state`

Leader tab synchronization state. It can differs from `client.node.state` (because only the leader tab keeps connection).

```js
client.on('state', () => {
  if (client.state === 'disconnected' && client.state === 'sending') {
    showCloseWarning()
  }
})
```

Type: `ClientNode`.

## `Client#tabId`

Unique tab ID. Can be used to add an action to the specific tab.

Without `localStorage` (React Native) there are it is an empty string.

```js
client.log.add(action, { tab: client.tabId })
```

Type: `string`.

## `Client#changeUser(userId, token?)`

Disconnect from the server, update user, and connect again with new credentials.

```js
onAuth(async (userId, token) => {
  showLoader()
  client.changeUser(userId, token)
  await client.node.waitFor('synchronized')
  hideLoader()
})
```

You need manually chang user ID in all browser tabs.

| Argument  | Type     | Description               |
| --------- | -------- | ------------------------- |
| `userId`  | `string` | The new user ID.          |
| `token` ? | `string` | Credentials for new user. |

## `Client#clean()`

Clear stored data. Removes action log from `IndexedDB` if you used it.

Databases, subscribed to the `cleaning` event, are cleaned before the log, so the log can’t fill them back.

```js
signout.addEventListener('click', async () => {
  await client.clean()
  location.reload()
})
```

Returns `Promise`. Promise when all data will be removed.

## `Client#destroy()`

Disconnect and stop synchronization.

```js
shutdown.addEventListener('click', () => {
  client.destroy()
})
```

## `Client#on(event, listener)`

| Argument   | Type                       |
| ---------- | -------------------------- |
| `event`    | `"user"`                   |
| `listener` | `(userId: string) => void` |

| Argument   | Type                    | Description            |
| ---------- | ----------------------- | ---------------------- |
| `event`    | `"cleaning"`            | The event name.        |
| `listener` | `() => void \| Promise` | The listener function. |

| Argument   | Type         | Description            |
| ---------- | ------------ | ---------------------- |
| `event`    | `"state"`    | The event name.        |
| `listener` | `() => void` | The listener function. |

| Argument   | Type                           |
| ---------- | ------------------------------ |
| `event`    | `"add" \| "clean" \| "preadd"` |
| `listener` | `ClientActionListener`         |

Returns `Unsubscribe`.

## `Client#start(connect?)`

Connect to server and reconnect on any connection problem.

```js
client.start()
```

| Argument    | Type      | Description                   |
| ----------- | --------- | ----------------------------- |
| `connect` ? | `boolean` | Start connection immediately. |

## `Client#sync(action, meta?)`

Send action to the server (by setting `meta.sync` and adding to the log) and track server processing.

```js
showLoader()
client.sync(
  { type: 'CHANGE_NAME', name }
).then(() => {
  hideLoader()
}).catch(error => {
  hideLoader()
  showError(error.action.reason)
})
```

| Argument | Type                  | Description    |
| -------- | --------------------- | -------------- |
| `action` | `SyncAction`          | The action     |
| `meta` ? | `Partial<ClientMeta>` | Optional meta. |

Returns `Promise<ClientMeta>`. Promise for server processing.

## `Client#type(type, listener, opts?)`

Add listener for adding action with specific type. Works faster than `on('add', cb)` with `if`.

```js
client.type('rename', (action, meta) => {
  name = action.name
})
```

| Argument   | Type                                                    | Description    |
| ---------- | ------------------------------------------------------- | -------------- |
| `type`     | `TypeAction["type"]`                                    | Action’s type. |
| `listener` | `ClientActionListener`                                  |                |
| `opts` ?   | `{ event?: "add" \| "clean" \| "preadd", id?: string }` |                |

| Argument        | Type                                                    | Description              |
| --------------- | ------------------------------------------------------- | ------------------------ |
| `actionCreator` | `Creator`                                               | Action creator function. |
| `listener`      | `ClientActionListener`                                  |                          |
| `opts` ?        | `{ event?: "add" \| "clean" \| "preadd", id?: string }` |                          |

Returns `Unsubscribe`. Unbind listener from event.

## `Client#waitFor(state)`

Wait for specific state of the leader tab.

```js
await client.waitFor('synchronized')
hideLoader()
```

| Argument | Type         | Description |
| -------- | ------------ | ----------- |
| `state`  | `ClientNode` | State name  |

Returns `Promise`.

# CrossTabClient

Extends [Client](#client).

Low-level browser API for Logux.

Instead of [`Client`](#client), this class prevents conflicts between Logux instances in different tabs on single browser.

```js
import { CrossTabClient } from '@logux/client'

const userId = document.querySelector('meta[name=user]').content
const token = document.querySelector('meta[name=token]').content

const client = new CrossTabClient({
  subprotocol: 1,
  server: 'wss://example.com:1337',
  userId,
  token
})
client.start()
```

| Parameter | Type            | Description     |
| --------- | --------------- | --------------- |
| `opts`    | `ClientOptions` | Client options. |

## `CrossTabClient#clientId`

Unique permanent client ID. Can be used to track this machine.

Type: `string`.

## `CrossTabClient#connected`

Is leader tab connected to server.

Type: `boolean`.

## `CrossTabClient#isLocalStorage`

Cache for localStorage detection. Can be overridden to disable leader tab election in tests.

Type: `boolean`.

## `CrossTabClient#log`

Client events log.

```js
client.log.add(action)
```

Type: `ClientLog`.

## `CrossTabClient#node`

Node instance to synchronize logs.

```js
if (client.node.state === 'synchronized')
```

Type: `ClientNode`.

## `CrossTabClient#nodeId`

Unique Logux node ID.

```js
console.log('Client ID: ', client.nodeId)
```

Type: `string`.

## `CrossTabClient#options`

Client options.

```js
console.log('Connecting to ' + client.options.server)
```

Type: `ClientOptions`.

## `CrossTabClient#role`

Current tab role. Only `leader` tab connects to server. `followers` just listen to events from `leader`.

```js
client.on('role', () => {
  console.log('Tab role:', client.role)
})
```

Type: `"follower" | "leader"`.

## `CrossTabClient#state`

Leader tab synchronization state. It can differs from `client.node.state` (because only the leader tab keeps connection).

```js
client.on('state', () => {
  if (client.state === 'disconnected' && client.state === 'sending') {
    showCloseWarning()
  }
})
```

Type: `ClientNode`.

## `CrossTabClient#tabId`

Unique tab ID. Can be used to add an action to the specific tab.

Without `localStorage` (React Native) there are it is an empty string.

```js
client.log.add(action, { tab: client.tabId })
```

Type: `string`.

## `CrossTabClient#changeUser(userId, token?)`

Disconnect from the server, update user, and connect again with new credentials.

```js
onAuth(async (userId, token) => {
  showLoader()
  client.changeUser(userId, token)
  await client.node.waitFor('synchronized')
  hideLoader()
})
```

You need manually chang user ID in all browser tabs.

| Argument  | Type     | Description               |
| --------- | -------- | ------------------------- |
| `userId`  | `string` | The new user ID.          |
| `token` ? | `string` | Credentials for new user. |

## `CrossTabClient#clean()`

Clear stored data. Removes action log from `IndexedDB` if you used it.

Databases, subscribed to the `cleaning` event, are cleaned before the log, so the log can’t fill them back.

```js
signout.addEventListener('click', async () => {
  await client.clean()
  location.reload()
})
```

Returns `Promise`. Promise when all data will be removed.

## `CrossTabClient#destroy()`

Disconnect and stop synchronization.

```js
shutdown.addEventListener('click', () => {
  client.destroy()
})
```

## `CrossTabClient#forceConnect()`

Start web socket reconnection.

## `CrossTabClient#on(event, listener)`

| Argument   | Type                           |
| ---------- | ------------------------------ |
| `event`    | `"add" \| "clean" \| "preadd"` |
| `listener` | `ClientActionListener`         |

| Argument   | Type                | Description            |
| ---------- | ------------------- | ---------------------- |
| `event`    | `"role" \| "state"` | The event name.        |
| `listener` | `() => void`        | The listener function. |

| Argument   | Type                       | Description            |
| ---------- | -------------------------- | ---------------------- |
| `event`    | `"user"`                   | The event name.        |
| `listener` | `(userId: string) => void` | The listener function. |

| Argument   | Type                    |
| ---------- | ----------------------- |
| `event`    | `"cleaning"`            |
| `listener` | `() => void \| Promise` |

Returns `Unsubscribe`.

## `CrossTabClient#start(connect?)`

Connect to server and reconnect on any connection problem.

```js
client.start()
```

| Argument    | Type      | Description                   |
| ----------- | --------- | ----------------------------- |
| `connect` ? | `boolean` | Start connection immediately. |

## `CrossTabClient#sync(action, meta?)`

Send action to the server (by setting `meta.sync` and adding to the log) and track server processing.

```js
showLoader()
client.sync(
  { type: 'CHANGE_NAME', name }
).then(() => {
  hideLoader()
}).catch(error => {
  hideLoader()
  showError(error.action.reason)
})
```

| Argument | Type                  | Description    |
| -------- | --------------------- | -------------- |
| `action` | `SyncAction`          | The action     |
| `meta` ? | `Partial<ClientMeta>` | Optional meta. |

Returns `Promise<ClientMeta>`. Promise for server processing.

## `CrossTabClient#type(type, listener, opts?)`

Add listener for adding action with specific type. Works faster than `on('add', cb)` with `if`.

```js
client.type('rename', (action, meta) => {
  name = action.name
})
```

| Argument   | Type                                                    | Description    |
| ---------- | ------------------------------------------------------- | -------------- |
| `type`     | `TypeAction["type"]`                                    | Action’s type. |
| `listener` | `ClientActionListener`                                  |                |
| `opts` ?   | `{ event?: "add" \| "clean" \| "preadd", id?: string }` |                |

| Argument        | Type                                                    | Description              |
| --------------- | ------------------------------------------------------- | ------------------------ |
| `actionCreator` | `Creator`                                               | Action creator function. |
| `listener`      | `ClientActionListener`                                  |                          |
| `opts` ?        | `{ event?: "add" \| "clean" \| "preadd", id?: string }` |                          |

Returns `Unsubscribe`. Unbind listener from event.

## `CrossTabClient#waitFor(state)`

Wait for specific state of the leader tab.

```js
await client.waitFor('synchronized')
hideLoader()
```

| Argument | Type         | Description |
| -------- | ------------ | ----------- |
| `state`  | `ClientNode` | State name  |

Returns `Promise`.

# IndexedStore

Extends `unknown`.

`IndexedDB` store for Logux log.

```js
import { IndexedStore } from '@logux/client'
const client = new CrossTabClient({
  …,
  store: new IndexedStore()
})
```

| Parameter | Type     | Description                                                     |
| --------- | -------- | --------------------------------------------------------------- |
| `name` ?  | `string` | Database name to run multiple Logux instances on same web page. |

## `IndexedStore#name`

Database name.

Type: `string`.

# `attention(client)`

Highlight tabs on synchronization errors.

```js
import { attention } from '@logux/client'
attention(client)
```

| Argument | Type     | Description               |
| -------- | -------- | ------------------------- |
| `client` | `Client` | Observed Client instance. |

Returns `() => void`. Unbind listener.

# `badge(client, opts)`

Display Logux widget in browser.

```js
import { badge, badgeEn } from '@logux/client'
import { badgeStyles } from '@logux/client/badge/styles'

badge(client, {
 messages: badgeEn,
 styles: {
   ...badgeStyles,
   synchronized: { backgroundColor: 'green' }
 },
 position: 'top-left'
})
```

| Argument | Type           | Description               |
| -------- | -------------- | ------------------------- |
| `client` | `Client`       | Observed Client instance. |
| `opts`   | `BadgeOptions` | Widget settings.          |

Returns `() => void`. Unbind badge listener and remove widget from DOM.

# `buildNewSyncMap(client, Template, value)`

Send create action and build store instance.

```js
import { buildNewSyncMap } from '@logux/client'

let userStore = buildNewSyncMap(client, User, {
  id: nanoid(),
  login: 'test'
})
```

| Argument   | Type                     | Description                                                        |
| ---------- | ------------------------ | ------------------------------------------------------------------ |
| `client`   | `Client`                 | Logux Client instance.                                             |
| `Template` | `SyncMapTemplate`        | Store template from [`syncMapTemplate`](#globals-syncmaptemplate). |
| `value`    | `{ id: string } & Value` | Initial value.                                                     |

Returns `Promise<any>`. Promise with store instance.

# `changeSyncMap(store, diff)`

Change keys in the store’s value.

```js
import { changeSyncMap } from '@logux/client'

showLoader()
await changeSyncMap(userStore, { name: 'New name' })
hideLoader()
```

| Argument | Type                        | Description       |
| -------- | --------------------------- | ----------------- |
| `store`  | `any`                       | Store’s instance. |
| `diff`   | `Partial<Omit<Value,"id">>` | Store’s changes.  |

| Argument | Type              | Description       |
| -------- | ----------------- | ----------------- |
| `store`  | `any`             | Store’s instance. |
| `key`    | `ValueKey`        |                   |
| `value`  | `Value[ValueKey]` |                   |

Returns `Promise`. Promise until server validation for remote classes or saving action to the log of fully offline classes.

# `changeSyncMapById(client, Template, id, diff)`

Change store without store instance just by store ID.

```js
import { changeSyncMapById } from '@logux/client'

let userStore = changeSyncMapById(client, User, 'user:4hs2jd83mf', {
  name: 'New name'
})
```

| Argument   | Type              | Description                                                        |
| ---------- | ----------------- | ------------------------------------------------------------------ |
| `client`   | `Client`          | Logux Client instance.                                             |
| `Template` | `SyncMapTemplate` | Store template from [`syncMapTemplate`](#globals-syncmaptemplate). |
| `id`       | `string`          | Store’s ID.                                                        |
| `diff`     | `Partial<Value>`  | Store’s changes.                                                   |

| Argument   | Type              | Description                                                        |
| ---------- | ----------------- | ------------------------------------------------------------------ |
| `client`   | `Client`          | Logux Client instance.                                             |
| `Template` | `SyncMapTemplate` | Store template from [`syncMapTemplate`](#globals-syncmaptemplate). |
| `id`       | `string`          | Store’s ID.                                                        |
| `key`      | `ValueKey`        |                                                                    |
| `value`    | `Value[ValueKey]` |                                                                    |

Returns `Promise`. Promise until server validation for remote classes or saving action to the log of fully offline classes.

# `confirm(client)`

Show confirm popup, when user close tab with non-synchronized actions.

```js
import { confirm } from '@logux/client'
confirm(client)
```

| Argument | Type     | Description               |
| -------- | -------- | ------------------------- |
| `client` | `Client` | Observed Client instance. |

Returns `() => void`. Unbind listener.

# `createFilter(client, Template, filter?, opts?)`

Load list of `SyncMap` with simple key-value requirements.

It will look for stores in loaded cache, log (for offline maps) and will subscribe to list from server (for remote maps).

```js
import { createFilter } from '@logux/client'

import { User } from '../store'

let usersInProject = createFilter(client, User, { projectId })
await usersInProject.loading
console.log(usersInProject.get())
```

| Argument   | Type              | Description                                                        |
| ---------- | ----------------- | ------------------------------------------------------------------ |
| `client`   | `Client`          | Logux Client.                                                      |
| `Template` | `SyncMapTemplate` | Store template from [`syncMapTemplate`](#globals-syncmaptemplate). |
| `filter` ? | `Filter`          | Key-value to filter stores.                                        |
| `opts` ?   | `FilterOptions`   | Loading options.                                                   |

Returns `any`.

# `createSyncMap(client, Template, value)`

Send create action to the server or to the log.

Server will create a row in database on this action. [`FilterStore`](#filterstore) will update the list.

```js
import { createSyncMap } from '@logux/client'

showLoader()
await createSyncMap(client, User, {
  id: nanoid(),
  login: 'test'
})
hideLoader()
```

| Argument   | Type                     | Description                                                        |
| ---------- | ------------------------ | ------------------------------------------------------------------ |
| `client`   | `Client`                 | Logux Client instance.                                             |
| `Template` | `SyncMapTemplate`        | Store template from [`syncMapTemplate`](#globals-syncmaptemplate). |
| `value`    | `{ id: string } & Value` | Initial value.                                                     |

Returns `Promise`. Promise until server validation for remote classes or saving action to the log of fully offline classes.

# `deleteSyncMap(store)`

Delete store.

```js
import { deleteSyncMap } from '@logux/client'

showLoader()
await deleteSyncMap(User)
```

| Argument | Type  | Description       |
| -------- | ----- | ----------------- |
| `store`  | `any` | Store’s instance. |

Returns `Promise`. Promise until server validation for remote classes or saving action to the log of fully offline classes.

# `deleteSyncMapById(client, Template, id)`

Delete store without store instance just by store ID.

```js
import { deleteSyncMapById } from '@logux/client'

showLoader()
await deleteSyncMapById(client, User, 'user:4hs2jd83mf')
```

| Argument   | Type              | Description                                                        |
| ---------- | ----------------- | ------------------------------------------------------------------ |
| `client`   | `Client`          | Logux Client instance.                                             |
| `Template` | `SyncMapTemplate` | Store template from [`syncMapTemplate`](#globals-syncmaptemplate). |
| `id`       | `string`          | Store’s ID.                                                        |

Returns `Promise`. Promise until server validation for remote classes or saving action to the log of fully offline classes.

# `encryptActions(client, secret, opts?)`

Encrypt actions before sending them to server.

Actions will be converted to `{ type: '0', d: encrypt(action) }`

Client will be switched to binary protocol, which has a compact format for encrypted actions. Call it before `client.start()`.

```js
import { encryptActions } from '@logux/client'
encryptActions(client, localStorage.getItem('userPassword'), {
  ignore: ['server/public'] // action.type to not be encrypted
})
```

| Argument | Type                                     | Description                                      |
| -------- | ---------------------------------------- | ------------------------------------------------ |
| `client` | `Client`                                 | Observed Client instance.                        |
| `secret` | `string \| CryptoKey`                    | Password for encryption, or a CryptoKey AES key. |
| `opts` ? | `{ clean?: boolean, ignore?: string[] }` | Encryption options.                              |

# `favicon(client, links)`

Change favicon to show Logux synchronization status.

```js
import { favicon } from '@logux/client'
favicon(client, {
  normal: '/favicon.ico',
  offline: '/offline.ico',
  error: '/error.ico'
})
```

| Argument | Type           | Description               |
| -------- | -------------- | ------------------------- |
| `client` | `Client`       | Observed Client instance. |
| `links`  | `FaviconLinks` | Favicon links.            |

Returns `() => void`. Unbind listener.

# `log(client, messages?)`

Display Logux events in browser console.

```js
import { log } from '@logux/client'
log(client, { ignoreActions: ['user/add'] })
```

| Argument     | Type          | Description                     |
| ------------ | ------------- | ------------------------------- |
| `client`     | `Client`      | Observed Client instance.       |
| `messages` ? | `LogMessages` | Disable specific message types. |

Returns `() => void`. Unbind listener.

# `request(action, opts)`

Create temporary client instance, send an action, wait response action from the server and destroy client.

Useful for simple actions like signin or signup.

```js
import { request } from '@logux/client'

let action = { type: 'signin', login, password }

request(action, {
  server: 'wss://example.com',
  subprotocol: 10
}).then(response => {
  saveToken(response.token)
}).catch(error => {
  showError(error.action.reason)
})
```

| Argument | Type             | Description                                 |
| -------- | ---------------- | ------------------------------------------- |
| `action` | `AnyAction`      | Action which we need to send to the server. |
| `opts`   | `RequestOptions` |                                             |

Returns `Promise<SentAction>`. Action of server response.

# `syncMapTemplate(plural, opts?)`

CRDT LWW Map. It can use server validation or be fully offline.

The best option for classic case with server and many clients. Store will resolve client’s edit conflicts with last write wins strategy.

```ts
import { syncMapTemplate } from '@logux/client'

export const User = syncMapTemplate<{
  login: string,
  name?: string,
  isAdmin: boolean
}>('users')
```

| Argument | Type                                      | Description                                                                      |
| -------- | ----------------------------------------- | -------------------------------------------------------------------------------- |
| `plural` | `string`                                  | Plural store name. It will be used in action type and channel name.              |
| `opts` ? | `{ offline?: boolean, remote?: boolean }` | Options to disable server validation or keep actions in log for offline support. |

Returns `SyncMapTemplate`.

# TestClient

Extends [Client](#client).

Virtual client to test client-side code end store extnesions.

```js
import { TestClient } from '@logux/client'

it('connects and sends actions', async () => {
  let client = new TestClient()
  let user = new UserStore(client, '10')

  client.server.onChannel('users/10', [
    { type: 'users/name', userId: 10, value: 'New name' }
  ])
  await client.connect()
  await delay(10)

  expect(user.name).toEqual('New name')
})
```

| Parameter | Type                | Description    |
| --------- | ------------------- | -------------- |
| `userId`  | `string`            | User ID.       |
| `opts` ?  | `TestClientOptions` | Other options. |

## `TestClient#clientId`

Unique permanent client ID. Can be used to track this machine.

Type: `string`.

## `TestClient#connected`

Is leader tab connected to server.

Type: `boolean`.

## `TestClient#log`

Client events log.

```js
client.log.add(action)
```

Type: `TestLog`.

## `TestClient#node`

Node instance to synchronize logs.

```js
if (client.node.state === 'synchronized')
```

Type: `ClientNode`.

## `TestClient#nodeId`

Unique Logux node ID.

```js
console.log('Client ID: ', client.nodeId)
```

Type: `string`.

## `TestClient#options`

Client options.

```js
console.log('Connecting to ' + client.options.server)
```

Type: `ClientOptions`.

## `TestClient#pair`

Connection between client and server.

Type: `TestPair`.

## `TestClient#server`

Virtual server to test client.

```js
expect(client.server.log.actions()).toEqual([
  { type: 'logux/subscribe', channel: 'users/10' }
])
```

Type: `TestServer`.

## `TestClient#state`

Leader tab synchronization state. It can differs from `client.node.state` (because only the leader tab keeps connection).

```js
client.on('state', () => {
  if (client.state === 'disconnected' && client.state === 'sending') {
    showCloseWarning()
  }
})
```

Type: `ClientNode`.

## `TestClient#tabId`

Unique tab ID. Can be used to add an action to the specific tab.

Without `localStorage` (React Native) there are it is an empty string.

```js
client.log.add(action, { tab: client.tabId })
```

Type: `string`.

## `TestClient#changeUser(userId, token?)`

Disconnect from the server, update user, and connect again with new credentials.

```js
onAuth(async (userId, token) => {
  showLoader()
  client.changeUser(userId, token)
  await client.node.waitFor('synchronized')
  hideLoader()
})
```

You need manually chang user ID in all browser tabs.

| Argument  | Type     | Description               |
| --------- | -------- | ------------------------- |
| `userId`  | `string` | The new user ID.          |
| `token` ? | `string` | Credentials for new user. |

## `TestClient#clean()`

Clear stored data. Removes action log from `IndexedDB` if you used it.

Databases, subscribed to the `cleaning` event, are cleaned before the log, so the log can’t fill them back.

```js
signout.addEventListener('click', async () => {
  await client.clean()
  location.reload()
})
```

Returns `Promise`. Promise when all data will be removed.

## `TestClient#connect()`

Connect to virtual server.

```js
await client.connect()
```

Returns `Promise`. Promise until connection will be established.

## `TestClient#destroy()`

Disconnect and stop synchronization.

```js
shutdown.addEventListener('click', () => {
  client.destroy()
})
```

## `TestClient#disconnect()`

Disconnect from virtual server.

```js
client.disconnect()
```

## `TestClient#on(event, listener)`

| Argument   | Type                       |
| ---------- | -------------------------- |
| `event`    | `"user"`                   |
| `listener` | `(userId: string) => void` |

| Argument   | Type                    | Description            |
| ---------- | ----------------------- | ---------------------- |
| `event`    | `"cleaning"`            | The event name.        |
| `listener` | `() => void \| Promise` | The listener function. |

| Argument   | Type         | Description            |
| ---------- | ------------ | ---------------------- |
| `event`    | `"state"`    | The event name.        |
| `listener` | `() => void` | The listener function. |

| Argument   | Type                           |
| ---------- | ------------------------------ |
| `event`    | `"add" \| "clean" \| "preadd"` |
| `listener` | `ClientActionListener`         |

Returns `Unsubscribe`.

## `TestClient#sent(test)`

Collect actions sent by client during the `test` call.

```js
let answers = await client.sent(async () => {
  client.log.add({ type: 'local' })
})
expect(actions).toEqual([{ type: 'local' }])
```

| Argument | Type                    | Description                                           |
| -------- | ----------------------- | ----------------------------------------------------- |
| `test`   | `() => void \| Promise` | Function, where do you expect action will be received |

Returns `Promise<Action[]>`. Promise with all received actions

## `TestClient#start(connect?)`

Connect to server and reconnect on any connection problem.

```js
client.start()
```

| Argument    | Type      | Description                   |
| ----------- | --------- | ----------------------------- |
| `connect` ? | `boolean` | Start connection immediately. |

## `TestClient#subscribed(channel)`

Does client subscribed to specific channel.

```js
let user = new UserStore(client, '10')
await delay(10)
expect(client.subscribed('users/10')).toBe(true)
```

| Argument  | Type     | Description   |
| --------- | -------- | ------------- |
| `channel` | `string` | Channel name. |

Returns `boolean`. Does client has an active subscription.

## `TestClient#sync(action, meta?)`

Send action to the server (by setting `meta.sync` and adding to the log) and track server processing.

```js
showLoader()
client.sync(
  { type: 'CHANGE_NAME', name }
).then(() => {
  hideLoader()
}).catch(error => {
  hideLoader()
  showError(error.action.reason)
})
```

| Argument | Type                  | Description    |
| -------- | --------------------- | -------------- |
| `action` | `SyncAction`          | The action     |
| `meta` ? | `Partial<ClientMeta>` | Optional meta. |

Returns `Promise<ClientMeta>`. Promise for server processing.

## `TestClient#type(type, listener, opts?)`

Add listener for adding action with specific type. Works faster than `on('add', cb)` with `if`.

```js
client.type('rename', (action, meta) => {
  name = action.name
})
```

| Argument   | Type                                                    | Description    |
| ---------- | ------------------------------------------------------- | -------------- |
| `type`     | `TypeAction["type"]`                                    | Action’s type. |
| `listener` | `ClientActionListener`                                  |                |
| `opts` ?   | `{ event?: "add" \| "clean" \| "preadd", id?: string }` |                |

| Argument        | Type                                                    | Description              |
| --------------- | ------------------------------------------------------- | ------------------------ |
| `actionCreator` | `Creator`                                               | Action creator function. |
| `listener`      | `ClientActionListener`                                  |                          |
| `opts` ?        | `{ event?: "add" \| "clean" \| "preadd", id?: string }` |                          |

Returns `Unsubscribe`. Unbind listener from event.

## `TestClient#waitFor(state)`

Wait for specific state of the leader tab.

```js
await client.waitFor('synchronized')
hideLoader()
```

| Argument | Type         | Description |
| -------- | ------------ | ----------- |
| `state`  | `ClientNode` | State name  |

Returns `Promise`.

# TestLog

Extends [Log](#log).

Log to be used in tests. It already has memory store, node ID, and special test timer.

Use [`TestTime`](#testtime) to create test log.

```js
import { TestTime } from '@logux/core'

it('tests log', () => {
  const log = TestTime.getLog()
})

it('tests 2 logs', () => {
  const time = new TestTime()
  const log1 = time.nextLog()
  const log2 = time.nextLog()
})
```

## `TestLog#nodeId`

Unique node ID. It is used in action IDs.

Type: `string`.

## `TestLog#store`

Log store.

Type: `LogStore`.

## `TestLog#actions()`

Return all action (without metadata) inside log, sorted by created time.

This shortcut works only with [`MemoryStore`](#memorystore).

```js
expect(log.action).toEqual([
  { type: 'A' }
])
```

Returns `Action[]`.

## `TestLog#add(action, meta?)`

Add action to log.

It will set `id`, `time` (if they was missed) and `added` property to `meta` and call all listeners.

```js
removeButton.addEventListener('click', () => {
  log.add({ type: 'users:remove', user: id })
})
```

| Argument | Type               | Description                         |
| -------- | ------------------ | ----------------------------------- |
| `action` | `NewAction`        | The new action.                     |
| `meta` ? | `Partial<LogMeta>` | Open structure for action metadata. |

| Argument  | Type               |
| --------- | ------------------ |
| `entries` | `[AnyAction, ?][]` |

Returns `Promise<false | LogMeta>`. Promise with `meta` if action was added to log or `false` if action was already in log.

## `TestLog#addReason(reasons, criteria?)`

Add reason tags to metadata of actions, which are already in the log. Reasons, which action already has, will not be duplicated.

```js
// The action still owns these cells, so it should keep their reasons
log.addReason('last-value', { id: meta.id })
```

| Argument     | Type                 | Description                                   |
| ------------ | -------------------- | --------------------------------------------- |
| `reasons`    | `string \| string[]` | The reason name or names.                     |
| `criteria` ? | `Criteria`           | Criteria to select actions for reason adding. |

Returns `Promise`. Promise when adding will be finished.

## `TestLog#byId(id)`

Does log already has action with this ID.

```js
if (action.type === 'logux/undo') {
  const [undidAction, undidMeta] = await log.byId(action.id)
  log.changeMeta(meta.id, { reasons: undidMeta.reasons })
}
```

| Argument | Type     | Description |
| -------- | -------- | ----------- |
| `id`     | `string` | Action ID.  |

Returns `Promise<[null, null] | [Action, LogMeta]>`. Promise with array of action and metadata.

## `TestLog#changeMeta(id, diff)`

Change action metadata. You will remove action by setting `reasons: []`.

```js
await process(action)
log.changeMeta(action, { status: 'processed' })
```

| Argument | Type               | Description                                      |
| -------- | ------------------ | ------------------------------------------------ |
| `id`     | `string`           | Action ID.                                       |
| `diff`   | `Partial<LogMeta>` | Object with values to change in action metadata. |

Returns `Promise<boolean>`. Promise with `true` if metadata was changed or `false` on unknown ID.

## `TestLog#each(opts, callback)`

| Argument   | Type             | Description                                |
| ---------- | ---------------- | ------------------------------------------ |
| `opts`     | `GetOptions`     | Iterator options.                          |
| `callback` | `ActionIterator` | Function will be executed on every action. |

| Argument   | Type             | Description                                |
| ---------- | ---------------- | ------------------------------------------ |
| `callback` | `ActionIterator` | Function will be executed on every action. |

| Argument   | Type             | Description                                |
| ---------- | ---------------- | ------------------------------------------ |
| `callback` | `ActionIterator` | Function will be executed on every action. |

Returns `Promise`.

## `TestLog#entries()`

Return all entries (with metadata) inside log, sorted by created time.

This shortcut works only with [`MemoryStore`](#memorystore).

```js
expect(log.action).toEqual([
  [{ type: 'A' }, { id: '1 test1 0', time: 1, added: 1, reasons: ['t'] }]
])
```

Returns `[Action, ClientMeta][]`.

## `TestLog#generateId()`

Generate next unique action ID.

```js
const id = log.generateId()
```

Returns `string`. Unique ID for action.

## `TestLog#keepActions()`

Keep actions without `meta.reasons` in the log by setting `test` reason during adding to the log.

```js
log.keepActions()
log.add({ type: 'test' })
log.actions() //=> [{ type: 'test' }]
```

## `TestLog#now()`

Current time of this node. Log uses it for `meta.time` and action ID.

Redefine it to use a custom clock, for instance, in tests.

```js
log.now = () => fakeTime
```

Returns `number`. Milliseconds since UNIX epoch.

## `TestLog#on(event, listener)`

Subscribe for log events. It implements nanoevents API. Supported events:

- `preadd`: when somebody try to add action to log. It fires before ID check. The best place to add reason.
- `add`: when new action was added to log.
- `clean`: when action was cleaned from store.
- `batch`: when actions from a single `Log#add()` call were added.

Note, that `Log#type()` will work faster than `on` event with `if`.

```js
log.on('preadd', (action, meta) => {
  if (action.type === 'beep') {
    meta.reasons.push('test')
  }
})
```

| Argument   | Type               | Description            |
| ---------- | ------------------ | ---------------------- |
| `event`    | `"add" \| "clean"` | The event name.        |
| `listener` | `ReadonlyListener` | The listener function. |

| Argument   | Type             | Description            |
| ---------- | ---------------- | ---------------------- |
| `event`    | `"preadd"`       | The event name.        |
| `listener` | `PreaddListener` | The listener function. |

| Argument   | Type                                     | Description            |
| ---------- | ---------------------------------------- | ---------------------- |
| `event`    | `"batch"`                                | The event name.        |
| `listener` | `(entries: [Action, LogMeta][]) => void` | The listener function. |

Returns `Unsubscribe`. Unbind listener from event.

## `TestLog#removeReason(reasons, criteria?)`

Remove reason tags from actions’ metadata and remove actions without reasons from log.

```js
onSync(lastSent) {
  log.removeReason('unsynchronized', { maxAdded: lastSent })
}
```

| Argument     | Type                 | Description                                     |
| ------------ | -------------------- | ----------------------------------------------- |
| `reasons`    | `string \| string[]` | The reason name or names.                       |
| `criteria` ? | `Criteria`           | Criteria to select actions for reason removing. |

Returns `Promise`. Promise when cleaning will be finished.

## `TestLog#type(type, listener, opts?)`

Add listener for adding action with specific type. Works faster than `on('add', cb)` with `if`.

Setting `opts.id` will filter events ponly from actions with specific `action.id`.

```js
const unbind = log.type('beep', (action, meta) => {
  beep()
})
function disableBeeps () {
  unbind()
}
```

| Argument   | Type                                        | Description            |
| ---------- | ------------------------------------------- | ---------------------- |
| `type`     | `NewAction["type"]`                         | Action’s type.         |
| `listener` | `ReadonlyListener`                          | The listener function. |
| `opts` ?   | `{ event?: "add" \| "clean", id?: string }` |                        |

| Argument   | Type                               | Description            |
| ---------- | ---------------------------------- | ---------------------- |
| `type`     | `NewAction["type"]`                | Action’s type.         |
| `listener` | `PreaddListener`                   | The listener function. |
| `opts`     | `{ event: "preadd", id?: string }` |                        |

Returns `Unsubscribe`. Unbind listener from event.

# TestPair

Extends [LocalPair](#localpair).

Two paired loopback connections with events tracking to be used in Logux tests.

```js
import { TestPair } from '@logux/core'
it('tracks events', async () => {
  const pair = new TestPair()
  const client = new ClientNode(pair.right)
  await pair.left.connect()
  expect(pair.leftEvents).toEqual('connect')
  await pair.left.send(msg)
  expect(pair.leftSent).toEqual([msg])
})
```

| Parameter | Type     | Description                                           |
| --------- | -------- | ----------------------------------------------------- |
| `delay` ? | `number` | Delay for connection and send events. Default is `1`. |

## `TestPair#delay`

Delay for connection and send events to emulate real connection latency.

Type: `number`.

## `TestPair#left`

First connection. Will be connected to `right` one after `connect()`.

```js
new ClientNode('client, log1, pair.left)
```

Type: `LocalConnection`.

## `TestPair#leftEvents`

Emitted events from `left` connection.

```js
await pair.left.connect()
pair.leftEvents //=> [['connect']]
```

Type: `string[][]`.

## `TestPair#leftNode`

Node instance used in this test, connected with `left`.

```js
function createTest () {
  test = new TestPair()
  test.leftNode = ClientNode('client', log, test.left)
  return test
}
```

Type: `BaseNode`.

## `TestPair#leftSent`

Sent messages from `left` connection.

```js
await pair.left.send(msg)
pair.leftSent //=> [msg]
```

Type: `Message[]`.

## `TestPair#right`

Second connection. Will be connected to `right` one after `connect()`.

```js
new ServerNode('server, log2, pair.right)
```

Type: `LocalConnection`.

## `TestPair#rightEvents`

Emitted events from `right` connection.

```js
await pair.right.connect()
pair.rightEvents //=> [['connect']]
```

Type: `string[][]`.

## `TestPair#rightNode`

Node instance used in this test, connected with `right`.

```js
function createTest () {
  test = new TestPair()
  test.rightNode = ServerNode('client', log, test.right)
  return test
}
```

Type: `BaseNode`.

## `TestPair#rightSent`

Sent messages from `right` connection.

```js
await pair.right.send(msg)
pair.rightSent //=> [msg]
```

Type: `Message[]`.

## `TestPair#clear()`

Clear all connections events and messages to test only last events.

```js
await client.connection.connect()
pair.clear() // Remove all connecting messages
await client.log.add({ type: 'a' })
expect(pair.leftSent).toEqual([
  ['sync', …]
])
```

## `TestPair#wait(receiver?)`

Return Promise until next event.

```js
pair.left.send(['test'])
await pair.wait('left')
pair.leftSend //=> [['test']]
```

| Argument     | Type                | Description                       |
| ------------ | ------------------- | --------------------------------- |
| `receiver` ? | `"left" \| "right"` | Wait for specific receiver event. |

Returns `Promise<TestPair>`. Promise until next event.

# TestServer

Virtual server to test client.

```js
let client = new TestClient()
client.server //=> TestServer
```

## `TestServer#log`

All actions received from the client.

```js
expect(client.server.log.actions()).toEqual([
  { type: 'logux/subscribe', channel: 'users/10' }
])
```

Type: `TestLog`.

## `TestServer#freezeProcessing(test)`

Stop to response with `logux/processed` on all new action and send `logux/processed` for all received actions when `test` callback will be finished.

```js
await client.server.freezeProcessing(() => {
  user.rename('Another name')
  expect(user.nameIsSaving).toBe(true)
})
await delay(10)
expect(user.nameIsSaving).toBe(false)
```

| Argument | Type            | Description                                             |
| -------- | --------------- | ------------------------------------------------------- |
| `test`   | `() => Promise` | Function, where server will not send `logux/processed`. |

Returns `Promise`. Promise until `test` will be finished.

## `TestServer#onChannel(channel, response)`

Define server’s responses for specific channel.

Second call with the same channel name will override previous data.

```js
  client.server.onChannel('users/10', [
    { type: 'users/name', userId: 10, value: 'New name' }
  ])
  let user = new UserStore(client, '10')
  await delay(10)
  expect(user.name).toEqual('New name')
```

| Argument   | Type     | Description                           |
| ---------- | -------- | ------------------------------------- |
| `channel`  | `string` | The channel name.                     |
| `response` | `any`    | Actions to send back on subscription. |

## `TestServer#resend(type, resend)`

Set channels for client’s actions.

| Argument | Type                                                             | Description                    |
| -------- | ---------------------------------------------------------------- | ------------------------------ |
| `type`   | `ResentAction["type"]`                                           | Action type.                   |
| `resend` | `(action: ResentAction, meta: ClientMeta) => string \| string[]` | Callback returns channel name. |

## `TestServer#sendAll(action, meta?)`

Send action to all connected clients.

```js
client.server.sendAll(action)
```

| Argument | Type         | Description    |
| -------- | ------------ | -------------- |
| `action` | `SentAction` | Action.        |
| `meta` ? | `any`        | Action‘s meta. |

Returns `Promise`.

## `TestServer#undoAction(action, reason?, extra?)`

Response with `logux/undo` instead of `logux/process` on receiving specific action.

```js
client.server.undoAction(
  { type: 'rename', userId: '10', value: 'Bad name' }
)
user.rename('Good name')
user.rename('Bad name')
await delay(10)
expect(user.name).toEqual('Good name')
```

| Argument   | Type             | Description                                     |
| ---------- | ---------------- | ----------------------------------------------- |
| `action`   | `RevertedAction` | Action to be undone on receiving                |
| `reason` ? | `string`         | Optional code for reason. Default is `'error'`. |
| `extra` ?  | `object`         | Extra fields to `logux/undo` action.            |

## `TestServer#undoNext(reason?, extra?)`

Response with `logux/undo` instead of `logux/process` on next action from the client.

```js
client.server.undoNext()
user.rename('Another name')
await delay(10)
expect(user.name).toEqual('Old name')
```

| Argument   | Type     | Description                                     |
| ---------- | -------- | ----------------------------------------------- |
| `reason` ? | `string` | Optional code for reason. Default is `'error'`. |
| `extra` ?  | `object` | Extra fields to `logux/undo` action.            |

# TestTime

Creates special logs for test purposes.

Real logs use real time in actions ID, so log content will be different on every test execution.

To fix it Logux has special logs for tests with simple sequence timer. All logs from one test should share same time. This is why you should use log creator to share time between all logs in one test.

```js
import { TestTime } from '@logux/core'

it('tests log', () => {
  const log = TestTime.getLog()
})

it('tests 2 logs', () => {
  const time = new TestTime()
  const log1 = time.nextLog()
  const log2 = time.nextLog()
})
```

## `TestTime.getLog(opts?)`

Shortcut to create time and generate single log. Use it only if you need one log in test.

```js
it('tests log', () => {
  const log = TestTime.getLog()
})
```

| Argument | Type             | Description  |
| -------- | ---------------- | ------------ |
| `opts` ? | `TestLogOptions` | Log options. |

Returns `TestLog`.

## `TestTime#lastId`

Last letd number in log’s `nodeId`.

Type: `number`.

## `TestTime#nextLog(opts?)`

Return next test log in same time.

```js
it('tests 2 logs', () => {
  const time = new TestTime()
  const log1 = time.nextLog()
  const log2 = time.nextLog()
})
```

| Argument | Type             | Description  |
| -------- | ---------------- | ------------ |
| `opts` ? | `TestLogOptions` | Log options. |

Returns `TestLog`.

# `emptyInTest(Template)`

Disable loader for filter for this builder.

```js
import { emptyInTest, cleanStores } from '@logux/client'

beforeEach(() => {
  prepareForTest(client, User, { name: 'Test user 1' })
  prepareForTest(client, User, { name: 'Test user 2' })
})

afterEach(() => {
  cleanStores(User)
})
```

| Argument   | Type              | Description    |
| ---------- | ----------------- | -------------- |
| `Template` | `SyncMapTemplate` | Store builder. |

## `prepareForTest`

Create and load stores to builder’s cache to use them in tests or storybook.

```js
import { prepareForTest, cleanStores, TestClient } from '@logux/client'

import { User } from '../store'

let client = new TestClient('10')

beforeEach(() => {
  prepareForTest(client, User, { name: 'Test user 1' })
  prepareForTest(client, User, { name: 'Test user 2' })
})

afterEach(() => {
  cleanStores(User)
})
```

Type: `PrepareForTest`.

# BaseNode

Base methods for synchronization nodes. Client and server nodes are based on this module.

| Parameter    | Type          | Description                            |
| ------------ | ------------- | -------------------------------------- |
| `nodeId`     | `string`      | Unique current machine name.           |
| `log`        | `NodeLog`     | Logux log instance to be synchronized. |
| `connection` | `Connection`  | Connection to remote node.             |
| `options` ?  | `NodeOptions` | Synchronization options.               |

## `BaseNode#authenticated`

Did we finish remote node authentication.

Type: `boolean`.

## `BaseNode#connected`

Is synchronization in process.

```js
node.on('disconnect', () => {
  node.connected //=> false
})
```

Type: `boolean`.

## `BaseNode#connection`

Connection used to communicate to remote node.

Type: `Connection`.

## `BaseNode#initializing`

Promise for node data initial loadiging.

Type: `Promise`.

## `BaseNode#lastReceived`

Latest remote node’s log `added` time, which was successfully synchronized. It will be saves in log store.

Type: `number`.

## `BaseNode#lastSent`

Latest current log `added` time, which was successfully synchronized. It will be saves in log store.

Type: `number`.

## `BaseNode#localNodeId`

Unique current machine name.

```js
console.log(node.localNodeId + ' is started')
```

Type: `string`.

## `BaseNode#localProtocol`

Used Logux protocol.

```js
if (tool.node.localProtocol !== 1) {
  throw new Error('Unsupported Logux protocol')
}
```

Type: `number`.

## `BaseNode#log`

Log for synchronization.

Type: `NodeLog`.

## `BaseNode#minProtocol`

Minimum version of Logux protocol, which is supported.

```js
console.log(`You need Logux protocol ${node.minProtocol} or higher`)
```

Type: `number`.

## `BaseNode#options`

Synchronization options.

Type: `NodeOptions`.

## `BaseNode#remoteHeaders`

Headers set by remote node. By default, it is an empty object.

```js
let message = I18N_ERRORS[node.remoteHeaders.language || 'en']
node.log.add({ type: 'error', message })
```

Type: `Headers | EmptyHeaders`.

## `BaseNode#remoteNodeId`

Unique name of remote machine. It is undefined until nodes handshake.

```js
console.log('Connected to ' + node.remoteNodeId)
```

Type: `string | undefined`.

## `BaseNode#remoteProtocol`

Remote node Logux protocol. It is undefined until nodes handshake.

```js
if (node.remoteProtocol >= 5) {
  useNewAPI()
} else {
  useOldAPI()
}
```

Type: `number | undefined`.

## `BaseNode#remoteSubprotocol`

Remote node’s application subprotocol version.

It is undefined until nodes handshake. If remote node will not send on handshake its subprotocol, it will be set to `0`.

```js
if (node.remoteSubprotocol > 9) {
  useNewAPI()
} else {
  useOldAPI()
}
```

Type: `number | undefined`.

## `BaseNode#state`

Current synchronization state.

- `disconnected`: no connection.
- `connecting`: connection was started and we wait for node answer.
- `sending`: new actions was sent, waiting for answer.
- `synchronized`: all actions was synchronized and we keep connection.

```js
node.on('state', () => {
  if (node.state === 'sending') {
    console.log('Do not close browser')
  }
})
```

Type: `NodeState`.

## `BaseNode#timeFix`

Time difference between nodes.

Type: `number`.

## `BaseNode#catch(listener)`

Disable throwing a error on error message and create error listener.

```js
node.catch(error => {
  console.error(error)
})
```

| Argument   | Type                          | Description         |
| ---------- | ----------------------------- | ------------------- |
| `listener` | `(error: LoguxError) => void` | The error listener. |

Returns `Unsubscribe`. Unbind listener from event.

## `BaseNode#destroy()`

Shut down the connection and unsubscribe from log events.

```js
connection.on('disconnect', () => {
  server.destroy()
})
```

## `BaseNode#on(event, listener)`

| Argument   | Type                         |
| ---------- | ---------------------------- |
| `event`    | `"headers"`                  |
| `listener` | `(headers: Headers) => void` |

| Argument   | Type                       |
| ---------- | -------------------------- |
| `event`    | `"synced"`                 |
| `listener` | `(synced: number) => void` |

| Argument   | Type                          |
| ---------- | ----------------------------- |
| `event`    | `"clientError" \| "error"`    |
| `listener` | `(error: LoguxError) => void` |

| Argument   | Type                                           | Description            |
| ---------- | ---------------------------------------------- | ---------------------- |
| `event`    | `"connect" \| "debug" \| "headers" \| "state"` | Event name.            |
| `listener` | `() => void`                                   | The listener function. |

| Argument   | Type                                    |
| ---------- | --------------------------------------- |
| `event`    | `"debug"`                               |
| `listener` | `(type: "error", data: string) => void` |

Returns `Unsubscribe`.

## `BaseNode#setLocalHeaders(headers)`

Set headers for current node.

```js
if (navigator) {
  node.setLocalHeaders({ language: navigator.language })
}
node.connection.connect()
```

| Argument  | Type      | Description                                              |
| --------- | --------- | -------------------------------------------------------- |
| `headers` | `Headers` | The data object will be set as headers for current node. |

## `BaseNode#waitFor(state)`

Return Promise until sync will have specific state.

If current state is correct, method will return resolved Promise.

```js
await node.waitFor('synchronized')
console.log('Everything is synchronized')
```

| Argument | Type        | Description                               |
| -------- | ----------- | ----------------------------------------- |
| `state`  | `NodeState` | The expected synchronization state value. |

Returns `Promise`. Promise until specific state.

# Connection

Abstract interface for connection to synchronize logs over it. For example, WebSocket or Loopback.

## `Connection#connected`

Is connection is enabled.

Type: `boolean`.

## `Connection#destroy`

Disconnect and unbind all even listeners.

Type: `() => void`.

## `Connection#connect()`

Start connection. Connection should be in disconnected state from the beginning and start connection only on this method call.

This method could be called again if connection moved to disconnected state.

Returns `Promise`. Promise until connection will be established.

## `Connection#disconnect(reason?)`

Finish current connection.

| Argument   | Type                                | Description           |
| ---------- | ----------------------------------- | --------------------- |
| `reason` ? | `"destroy" \| "error" \| "timeout"` | Disconnection reason. |

## `Connection#on(event, listener)`

| Argument   | Type                       |
| ---------- | -------------------------- |
| `event`    | `"disconnect"`             |
| `listener` | `(reason: string) => void` |

| Argument   | Type                     |
| ---------- | ------------------------ |
| `event`    | `"error"`                |
| `listener` | `(error: Error) => void` |

| Argument   | Type                                        | Description     |
| ---------- | ------------------------------------------- | --------------- |
| `event`    | `"connect" \| "connecting" \| "disconnect"` | Event name.     |
| `listener` | `() => void`                                | Event listener. |

| Argument   | Type                     |
| ---------- | ------------------------ |
| `event`    | `"message"`              |
| `listener` | `(msg: Message) => void` |

Returns `Unsubscribe`.

## `Connection#send(message)`

Send message to connection.

| Argument  | Type      | Description             |
| --------- | --------- | ----------------------- |
| `message` | `Message` | The message to be sent. |

# Log

Stores actions with time marks. Log is main idea in Logux. In most end-user tools you will work with log and should know log API.

```js
import Log from '@logux/core'
const log = new Log({
  store: new MemoryStore(),
  nodeId: 'client:134'
})

log.on('add', beeper)
log.add({ type: 'beep' })
```

| Parameter | Type         | Description  |
| --------- | ------------ | ------------ |
| `opts`    | `LogOptions` | Log options. |

## `Log#nodeId`

Unique node ID. It is used in action IDs.

Type: `string`.

## `Log#store`

Log store.

Type: `Store`.

## `Log#add(action, meta?)`

Add action to log.

It will set `id`, `time` (if they was missed) and `added` property to `meta` and call all listeners.

```js
removeButton.addEventListener('click', () => {
  log.add({ type: 'users:remove', user: id })
})
```

| Argument | Type               | Description                         |
| -------- | ------------------ | ----------------------------------- |
| `action` | `NewAction`        | The new action.                     |
| `meta` ? | `Partial<LogMeta>` | Open structure for action metadata. |

| Argument  | Type               |
| --------- | ------------------ |
| `entries` | `[AnyAction, ?][]` |

Returns `Promise<false | LogMeta>`. Promise with `meta` if action was added to log or `false` if action was already in log.

## `Log#addReason(reasons, criteria?)`

Add reason tags to metadata of actions, which are already in the log. Reasons, which action already has, will not be duplicated.

```js
// The action still owns these cells, so it should keep their reasons
log.addReason('last-value', { id: meta.id })
```

| Argument     | Type                 | Description                                   |
| ------------ | -------------------- | --------------------------------------------- |
| `reasons`    | `string \| string[]` | The reason name or names.                     |
| `criteria` ? | `Criteria`           | Criteria to select actions for reason adding. |

Returns `Promise`. Promise when adding will be finished.

## `Log#byId(id)`

Does log already has action with this ID.

```js
if (action.type === 'logux/undo') {
  const [undidAction, undidMeta] = await log.byId(action.id)
  log.changeMeta(meta.id, { reasons: undidMeta.reasons })
}
```

| Argument | Type     | Description |
| -------- | -------- | ----------- |
| `id`     | `string` | Action ID.  |

Returns `Promise<[null, null] | [Action, LogMeta]>`. Promise with array of action and metadata.

## `Log#changeMeta(id, diff)`

Change action metadata. You will remove action by setting `reasons: []`.

```js
await process(action)
log.changeMeta(action, { status: 'processed' })
```

| Argument | Type               | Description                                      |
| -------- | ------------------ | ------------------------------------------------ |
| `id`     | `string`           | Action ID.                                       |
| `diff`   | `Partial<LogMeta>` | Object with values to change in action metadata. |

Returns `Promise<boolean>`. Promise with `true` if metadata was changed or `false` on unknown ID.

## `Log#each(opts, callback)`

| Argument   | Type             | Description                                |
| ---------- | ---------------- | ------------------------------------------ |
| `opts`     | `GetOptions`     | Iterator options.                          |
| `callback` | `ActionIterator` | Function will be executed on every action. |

| Argument   | Type             | Description                                |
| ---------- | ---------------- | ------------------------------------------ |
| `callback` | `ActionIterator` | Function will be executed on every action. |

| Argument   | Type             | Description                                |
| ---------- | ---------------- | ------------------------------------------ |
| `callback` | `ActionIterator` | Function will be executed on every action. |

Returns `Promise`.

## `Log#generateId()`

Generate next unique action ID.

```js
const id = log.generateId()
```

Returns `string`. Unique ID for action.

## `Log#now()`

Current time of this node. Log uses it for `meta.time` and action ID.

Redefine it to use a custom clock, for instance, in tests.

```js
log.now = () => fakeTime
```

Returns `number`. Milliseconds since UNIX epoch.

## `Log#on(event, listener)`

Subscribe for log events. It implements nanoevents API. Supported events:

- `preadd`: when somebody try to add action to log. It fires before ID check. The best place to add reason.
- `add`: when new action was added to log.
- `clean`: when action was cleaned from store.
- `batch`: when actions from a single `Log#add()` call were added.

Note, that `Log#type()` will work faster than `on` event with `if`.

```js
log.on('preadd', (action, meta) => {
  if (action.type === 'beep') {
    meta.reasons.push('test')
  }
})
```

| Argument   | Type               | Description            |
| ---------- | ------------------ | ---------------------- |
| `event`    | `"add" \| "clean"` | The event name.        |
| `listener` | `ReadonlyListener` | The listener function. |

| Argument   | Type             | Description            |
| ---------- | ---------------- | ---------------------- |
| `event`    | `"preadd"`       | The event name.        |
| `listener` | `PreaddListener` | The listener function. |

| Argument   | Type                                     | Description            |
| ---------- | ---------------------------------------- | ---------------------- |
| `event`    | `"batch"`                                | The event name.        |
| `listener` | `(entries: [Action, LogMeta][]) => void` | The listener function. |

Returns `Unsubscribe`. Unbind listener from event.

## `Log#removeReason(reasons, criteria?)`

Remove reason tags from actions’ metadata and remove actions without reasons from log.

```js
onSync(lastSent) {
  log.removeReason('unsynchronized', { maxAdded: lastSent })
}
```

| Argument     | Type                 | Description                                     |
| ------------ | -------------------- | ----------------------------------------------- |
| `reasons`    | `string \| string[]` | The reason name or names.                       |
| `criteria` ? | `Criteria`           | Criteria to select actions for reason removing. |

Returns `Promise`. Promise when cleaning will be finished.

## `Log#type(type, listener, opts?)`

Add listener for adding action with specific type. Works faster than `on('add', cb)` with `if`.

Setting `opts.id` will filter events ponly from actions with specific `action.id`.

```js
const unbind = log.type('beep', (action, meta) => {
  beep()
})
function disableBeeps () {
  unbind()
}
```

| Argument   | Type                                        | Description            |
| ---------- | ------------------------------------------- | ---------------------- |
| `type`     | `NewAction["type"]`                         | Action’s type.         |
| `listener` | `ReadonlyListener`                          | The listener function. |
| `opts` ?   | `{ event?: "add" \| "clean", id?: string }` |                        |

| Argument   | Type                               | Description            |
| ---------- | ---------------------------------- | ---------------------- |
| `type`     | `NewAction["type"]`                | Action’s type.         |
| `listener` | `PreaddListener`                   | The listener function. |
| `opts`     | `{ event: "preadd", id?: string }` |                        |

Returns `Unsubscribe`. Unbind listener from event.

# MemoryStore

Extends [LogStore](#logstore).

Simple memory-based log store.

It is good for tests, but not for server or client usage, because it store all data in memory and will lose log on exit.

```js
import { MemoryStore } from '@logux/core'

var log = new Log({
  nodeId: 'server',
  store: new MemoryStore()
})
```

## `MemoryStore#entries`

Actions in the store.

Type: `[Action, ClientMeta][]`.

## `MemoryStore#add(action, meta)`

Add action to store. Action always will have `type` property.

| Argument | Type         | Description        |
| -------- | ------------ | ------------------ |
| `action` | `AnyAction`  | The action to add. |
| `meta`   | `ClientMeta` | Action’s metadata. |

Returns `Promise<false | ClientMeta>`. Promise with `meta` for new action or `false` if action with same `meta.id` was already in store.

## `MemoryStore#addReason(reasons, criteria)`

Add reasons to metadata of actions, which are already in the store.

Reasons, which action already has, should not be duplicated.

| Argument   | Type       | Description                                   |
| ---------- | ---------- | --------------------------------------------- |
| `reasons`  | `string[]` | The reason names.                             |
| `criteria` | `Criteria` | Criteria to select actions for reason adding. |

Returns `Promise`. Promise when adding will be finished.

## `MemoryStore#byId(id)`

Return action by action ID.

| Argument | Type     | Description |
| -------- | -------- | ----------- |
| `id`     | `string` | Action ID.  |

Returns `Promise<[null, null] | [Action, ClientMeta]>`. Promise with array of action and metadata.

## `MemoryStore#changeMeta(id, diff)`

Change action metadata.

| Argument | Type                  | Description                                      |
| -------- | --------------------- | ------------------------------------------------ |
| `id`     | `string`              | Action ID.                                       |
| `diff`   | `Partial<ClientMeta>` | Object with values to change in action metadata. |

Returns `Promise<boolean>`. Promise with `true` if metadata was changed or `false` on unknown ID.

## `MemoryStore#clean()`

Remove all data from the store.

Returns `Promise`. Promise when cleaning will be finished.

## `MemoryStore#get(opts?)`

Return a Promise with first page. Page object has `entries` property with part of actions and `next` property with function to load next page. If it was a last page, `next` property should be empty.

This tricky API is used, because log could be very big. So we need pagination to keep them in memory.

| Argument | Type         | Description    |
| -------- | ------------ | -------------- |
| `opts` ? | `GetOptions` | Query options. |

Returns `Promise<LogPage>`. Promise with first page.

## `MemoryStore#getLastAdded()`

Return biggest `added` number in store. All actions in this log have less or same `added` time.

Returns `Promise<number>`. Promise with biggest `added` number.

## `MemoryStore#getLastSynced()`

Get `added` values for latest synchronized received/sent events.

Returns `Promise<LastSynced>`. Promise with `added` values

## `MemoryStore#remove(id)`

Remove action from store.

| Argument | Type     | Description |
| -------- | -------- | ----------- |
| `id`     | `string` | Action ID.  |

Returns `Promise<false | [Action, ClientMeta]>`. Promise with entry if action was in store.

## `MemoryStore#removeReason(reasons, criteria, callback)`

Remove reasons from action’s metadata and remove actions without reasons.

| Argument   | Type               | Description                                     |
| ---------- | ------------------ | ----------------------------------------------- |
| `reasons`  | `string[]`         | The reason names.                               |
| `criteria` | `Criteria`         | Criteria to select actions for reason removing. |
| `callback` | `ReadonlyListener` | Callback for every removed action.              |

Returns `Promise`. Promise when cleaning will be finished.

## `MemoryStore#setLastSynced(values)`

Set `added` value for latest synchronized received or/and sent events.

| Argument | Type                  | Description                                 |
| -------- | --------------------- | ------------------------------------------- |
| `values` | `Partial<LastSynced>` | Object with latest sent or received values. |

Returns `Promise`. Promise when values will be saved to store.

# Reconnect

Extends [Connection](#connection).

Wrapper for Connection for re-connecting it on every disconnect.

```js
import { ClientNode, Reconnect } from '@logux/core'
const recon = new Reconnect(connection)
new ClientNode(nodeId, log, recon, options)
```

| Parameter    | Type               | Description                          |
| ------------ | ------------------ | ------------------------------------ |
| `connection` | `Connection`       | The connection to be re-connectable. |
| `options` ?  | `ReconnectOptions` | Re-connection options.               |

## `Reconnect#attempts`

Fails attempts since the last connected state.

Type: `number`.

## `Reconnect#connected`

Is connection is enabled.

Type: `boolean`.

## `Reconnect#connecting`

Are we in the middle of connecting.

Type: `boolean`.

## `Reconnect#connection`

Wrapped connection.

Type: `Connection`.

## `Reconnect#destroy`

Unbind all listeners and disconnect. Use it if you will not need this class anymore.

Type: `() => void`.

## `Reconnect#options`

Re-connection options.

Type: `ReconnectOptions`.

## `Reconnect#reconnecting`

Should we re-connect connection on next connection break. Next `connect` call will set to `true`.

```js
function lastTry () {
  recon.reconnecting = false
}
```

Type: `boolean`.

## `Reconnect#connect()`

Start connection. Connection should be in disconnected state from the beginning and start connection only on this method call.

This method could be called again if connection moved to disconnected state.

Returns `Promise`. Promise until connection will be established.

## `Reconnect#disconnect(reason?)`

Finish current connection.

| Argument   | Type                                | Description           |
| ---------- | ----------------------------------- | --------------------- |
| `reason` ? | `"destroy" \| "error" \| "timeout"` | Disconnection reason. |

## `Reconnect#on(event, listener)`

| Argument   | Type                       |
| ---------- | -------------------------- |
| `event`    | `"disconnect"`             |
| `listener` | `(reason: string) => void` |

| Argument   | Type                     |
| ---------- | ------------------------ |
| `event`    | `"error"`                |
| `listener` | `(error: Error) => void` |

| Argument   | Type                                        | Description     |
| ---------- | ------------------------------------------- | --------------- |
| `event`    | `"connect" \| "connecting" \| "disconnect"` | Event name.     |
| `listener` | `() => void`                                | Event listener. |

| Argument   | Type                     |
| ---------- | ------------------------ |
| `event`    | `"message"`              |
| `listener` | `(msg: Message) => void` |

Returns `Unsubscribe`.

## `Reconnect#send(message)`

Send message to connection.

| Argument  | Type      | Description             |
| --------- | --------- | ----------------------- |
| `message` | `Message` | The message to be sent. |

## `defineAction`

Type: `DefineAction`.

# `isFirstOlder(firstMeta, secondMeta)`

Compare time, when log entries were created.

It uses `meta.time` and `meta.id` to detect entries order.

```js
import { isFirstOlder } from '@logux/core'
if (isFirstOlder(lastBeep, meta) {
  beep(action)
  lastBeep = meta
}
```

| Argument     | Type                                | Description              |
| ------------ | ----------------------------------- | ------------------------ |
| `firstMeta`  | `string \| ClientMeta \| undefined` | Some action’s metadata.  |
| `secondMeta` | `string \| ClientMeta \| undefined` | Other action’s metadata. |

Returns `boolean`.

# `parseId(id)`

Parse `meta.id` or Node ID into component: user ID, client ID, node ID.

```js
import { parseId } from '@logux/core'
const { userId, clientId } = parseId(meta.id)
```

| Argument | Type     | Description       |
| -------- | -------- | ----------------- |
| `id`     | `string` | Action or Node ID |

Returns `IDComponents`.

# `AbstractActionCreator(args)`

| Argument | Type    |
| -------- | ------- |
| `args`   | `any[]` |

Returns `CreatedAction`.

## `AbstractActionCreator#type`

Type: `string`.

## `AbstractCrdtTable`

| Property | Type                                                                                  |
| -------- | ------------------------------------------------------------------------------------- |
| `plural` | `string`                                                                              |
| `create` | `(fields: AbstractNewCrdtRow[] \| AbstractNewCrdtRow) => Promise<string \| string[]>` |
| `update` | `(id: string, diff: Partial<RowFields>) => Promise`                                   |

## `AbstractNewCrdtRow`

Type: `{ id?: string } & CreateFields`.

## `Action`

| Property | Type     | Description       |
| -------- | -------- | ----------------- |
| `type`   | `string` | Action type name. |

# `ActionCreator(args)`

| Argument | Type          |
| -------- | ------------- |
| `args`   | `CreatorArgs` |

Returns `CreatedAction`.

## `ActionCreator#match`

Type: `(action: Action) => action is CreatedAction`.

## `ActionCreator#type`

Type: `string`.

# `ActionFilter(action, meta)`

| Argument | Type         |
| -------- | ------------ |
| `action` | `Action`     |
| `meta`   | `ClientMeta` |

Returns `false | Promise<false | [Action, ClientMeta]> | [Action, ClientMeta]`.

# `ActionIterator(action, meta)`

| Argument | Type      |
| -------- | --------- |
| `action` | `Action`  |
| `meta`   | `LogMeta` |

Returns `void | boolean`.

# `ActionListener(action, meta)`

| Argument | Type           |
| -------- | -------------- |
| `action` | `ListenAction` |
| `meta`   | `any`          |

Returns `void | Promise`.

## `ActionPacker`

Packer of actions with binary parts to binary format to use it in custom packers in SQL-based log stores.

| Property | Type                                                |
| -------- | --------------------------------------------------- |
| `pack`   | `(action: FullAction) => PackedAction \| undefined` |
| `unpack` | `(action: PackedAction) => FullAction`              |

## `ActionPackerMap`

Checks that every key of the packers map is equal to `type` of the action, which packer packs.

```ts
function createStore<Packers extends ActionPackerMap<Packers>>(
  packers: Packers
): Store

createStore({ '0': zeroPacker })
```

Type: `{ [Type: keyof Packers]: Type ? ActionPacker : never }`.

## `AnyAction`

| Property | Type     |
| -------- | -------- |
| `type`   | `string` |

# `Authenticator(nodeId, token, headers)`

| Argument  | Type                |
| --------- | ------------------- |
| `nodeId`  | `string`            |
| `token`   | `string`            |
| `headers` | `object \| Headers` |

Returns `Promise<boolean>`.

## `AuthStore`

Auth store. Use [`createAuth`](#globals-createauth) to create it.

| Property  | Type      | Description                           |
| --------- | --------- | ------------------------------------- |
| `loading` | `Promise` | While store is loading initial state. |

## `BadgeMessages`

| Property        | Type     |
| --------------- | -------- |
| `denied`        | `string` |
| `disconnected`  | `string` |
| `error`         | `string` |
| `protocolError` | `string` |
| `sending`       | `string` |
| `syncError`     | `string` |
| `synchronized`  | `string` |
| `wait`          | `string` |

## `BadgeOptions`

| Property     | Type                                                                                                                                                    | Description                                     |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| `duration` ? | `number`                                                                                                                                                | Synchronized state duration. Default is `3000`. |
| `messages`   | `BadgeMessages`                                                                                                                                         | Widget text for different states.               |
| `position` ? | `"bottom-center" \| "bottom-left" \| "bottom-right" \| "middle-center" \| "middle-left" \| "middle-right" \| "top-center" \| "top-left" \| "top-right"` | Widget position. Default is `bottom-right`.     |
| `styles`     | `BadgeStyles`                                                                                                                                           | Inline styles for different states.             |

## `BadgeStyles`

| Property        | Type                                                                                                                  |
| --------------- | --------------------------------------------------------------------------------------------------------------------- |
| `base`          | `object`                                                                                                              |
| `connecting`    | `object`                                                                                                              |
| `disconnected`  | `object`                                                                                                              |
| `error`         | `object`                                                                                                              |
| `icon`          | `{ disconnected: string, error: string, protocolError: string, sending: string, synchronized: string, wait: string }` |
| `protocolError` | `object`                                                                                                              |
| `sending`       | `object`                                                                                                              |
| `synchronized`  | `object`                                                                                                              |
| `text`          | `object`                                                                                                              |
| `wait`          | `object`                                                                                                              |

## `ChannelDeniedError`

Type: `LoguxUndoError`.

## `ChannelError`

Type: `ChannelDeniedError | ChannelNotFoundError | ChannelServerError`.

## `ChannelNotFoundError`

Type: `LoguxUndoError`.

## `ChannelServerError`

Type: `LoguxUndoError`.

# `ClientActionListener(action, meta)`

| Argument | Type           |
| -------- | -------------- |
| `action` | `ListenAction` |
| `meta`   | `ClientMeta`   |

## `ClientMeta`

| Property         | Type      | Description                                                                 |
| ---------------- | --------- | --------------------------------------------------------------------------- |
| `noAutoReason` ? | `boolean` | Disable setting `timeTravel` reason.                                        |
| `sync` ?         | `boolean` | This action should be synchronized with other browser tabs and server.      |
| `tab` ?          | `string`  | Action should be visible only for browser tab with the same `client.tabId`. |

# ClientNode

Extends [BaseNode](#basenode).

Client node in synchronization pair.

Instead of server node, it initializes synchronization and sends connect message.

```js
import { ClientNode } from '@logux/core'
const connection = new BrowserConnection(url)
const node = new ClientNode(nodeId, log, connection)
```

| Parameter    | Type          | Description                            |
| ------------ | ------------- | -------------------------------------- |
| `nodeId`     | `string`      | Unique current machine name.           |
| `log`        | `NodeLog`     | Logux log instance to be synchronized. |
| `connection` | `Connection`  | Connection to remote node.             |
| `options` ?  | `NodeOptions` | Synchronization options.               |

## `ClientNode#authenticated`

Did we finish remote node authentication.

Type: `boolean`.

## `ClientNode#connected`

Is synchronization in process.

```js
node.on('disconnect', () => {
  node.connected //=> false
})
```

Type: `boolean`.

## `ClientNode#connection`

Connection used to communicate to remote node.

Type: `Connection`.

## `ClientNode#initializing`

Promise for node data initial loadiging.

Type: `Promise`.

## `ClientNode#lastReceived`

Latest remote node’s log `added` time, which was successfully synchronized. It will be saves in log store.

Type: `number`.

## `ClientNode#lastSent`

Latest current log `added` time, which was successfully synchronized. It will be saves in log store.

Type: `number`.

## `ClientNode#localNodeId`

Unique current machine name.

```js
console.log(node.localNodeId + ' is started')
```

Type: `string`.

## `ClientNode#localProtocol`

Used Logux protocol.

```js
if (tool.node.localProtocol !== 1) {
  throw new Error('Unsupported Logux protocol')
}
```

Type: `number`.

## `ClientNode#log`

Log for synchronization.

Type: `NodeLog`.

## `ClientNode#minProtocol`

Minimum version of Logux protocol, which is supported.

```js
console.log(`You need Logux protocol ${node.minProtocol} or higher`)
```

Type: `number`.

## `ClientNode#options`

Synchronization options.

Type: `NodeOptions`.

## `ClientNode#remoteHeaders`

Headers set by remote node. By default, it is an empty object.

```js
let message = I18N_ERRORS[node.remoteHeaders.language || 'en']
node.log.add({ type: 'error', message })
```

Type: `Headers | EmptyHeaders`.

## `ClientNode#remoteNodeId`

Unique name of remote machine. It is undefined until nodes handshake.

```js
console.log('Connected to ' + node.remoteNodeId)
```

Type: `string | undefined`.

## `ClientNode#remoteProtocol`

Remote node Logux protocol. It is undefined until nodes handshake.

```js
if (node.remoteProtocol >= 5) {
  useNewAPI()
} else {
  useOldAPI()
}
```

Type: `number | undefined`.

## `ClientNode#remoteSubprotocol`

Remote node’s application subprotocol version.

It is undefined until nodes handshake. If remote node will not send on handshake its subprotocol, it will be set to `0`.

```js
if (node.remoteSubprotocol > 9) {
  useNewAPI()
} else {
  useOldAPI()
}
```

Type: `number | undefined`.

## `ClientNode#state`

Current synchronization state.

- `disconnected`: no connection.
- `connecting`: connection was started and we wait for node answer.
- `sending`: new actions was sent, waiting for answer.
- `synchronized`: all actions was synchronized and we keep connection.

```js
node.on('state', () => {
  if (node.state === 'sending') {
    console.log('Do not close browser')
  }
})
```

Type: `NodeState`.

## `ClientNode#timeFix`

Time difference between nodes.

Type: `number`.

## `ClientNode#catch(listener)`

Disable throwing a error on error message and create error listener.

```js
node.catch(error => {
  console.error(error)
})
```

| Argument   | Type                          | Description         |
| ---------- | ----------------------------- | ------------------- |
| `listener` | `(error: LoguxError) => void` | The error listener. |

Returns `Unsubscribe`. Unbind listener from event.

## `ClientNode#destroy()`

Shut down the connection and unsubscribe from log events.

```js
connection.on('disconnect', () => {
  server.destroy()
})
```

## `ClientNode#on(event, listener)`

| Argument   | Type                         |
| ---------- | ---------------------------- |
| `event`    | `"headers"`                  |
| `listener` | `(headers: Headers) => void` |

| Argument   | Type                       |
| ---------- | -------------------------- |
| `event`    | `"synced"`                 |
| `listener` | `(synced: number) => void` |

| Argument   | Type                          |
| ---------- | ----------------------------- |
| `event`    | `"clientError" \| "error"`    |
| `listener` | `(error: LoguxError) => void` |

| Argument   | Type                                           | Description            |
| ---------- | ---------------------------------------------- | ---------------------- |
| `event`    | `"connect" \| "debug" \| "headers" \| "state"` | Event name.            |
| `listener` | `() => void`                                   | The listener function. |

| Argument   | Type                                    |
| ---------- | --------------------------------------- |
| `event`    | `"debug"`                               |
| `listener` | `(type: "error", data: string) => void` |

Returns `Unsubscribe`.

## `ClientNode#setLocalHeaders(headers)`

Set headers for current node.

```js
if (navigator) {
  node.setLocalHeaders({ language: navigator.language })
}
node.connection.connect()
```

| Argument  | Type      | Description                                              |
| --------- | --------- | -------------------------------------------------------- |
| `headers` | `Headers` | The data object will be set as headers for current node. |

## `ClientNode#waitFor(state)`

Return Promise until sync will have specific state.

If current state is correct, method will return resolved Promise.

```js
await node.waitFor('synchronized')
console.log('Everything is synchronized')
```

| Argument | Type        | Description                               |
| -------- | ----------- | ----------------------------------------- |
| `state`  | `NodeState` | The expected synchronization state value. |

Returns `Promise`. Promise until specific state.

## `ClientOptions`

| Property                   | Type      | Description                                                                                              |
| -------------------------- | --------- | -------------------------------------------------------------------------------------------------------- |
| `allowDangerousProtocol` ? | `boolean` | Do not show warning when using `ws://` in production.                                                    |
| `attempts` ?               | `number`  | Maximum reconnection attempts. Default is `Infinity`.                                                    |
| `maxDelay` ?               | `number`  | Maximum delay between reconnections. Default is `5000`.                                                  |
| `minDelay` ?               | `number`  | Minimum delay between reconnections. Default is `1000`.                                                  |
| `ping` ?                   | `number`  | Milliseconds since last message to test connection by sending ping. Default is `10000`.                  |
| `prefix` ?                 | `string`  | Prefix for `IndexedDB` database to run multiple Logux instances in the same browser. Default is `logux`. |
| `server`                   | `any`     | Server URL.                                                                                              |
| `store` ?                  | `any`     | Store to save log data. Default is `MemoryStore`.                                                        |
| `subprotocol`              | `number`  | Client subprotocol version.                                                                              |
| `time` ?                   | `any`     | Test time to test client.                                                                                |
| `timeout` ?                | `number`  | Timeout in milliseconds to break connection. Default is `70000`.                                         |
| `token` ?                  | `any`     | Client credentials for authentication.                                                                   |
| `userId`                   | `string`  | User ID.                                                                                                 |

## `Convertor`

| Property | Type                       |
| -------- | -------------------------- |
| `decode` | `(str: string) => Value`   |
| `encode` | `(value: Value) => string` |

## `CrdtActionOptions`

| Property    | Type     | Description                                                                                                                                                                                                                                         |
| ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `version` ? | `number` | Version of the callback’s logic. Adding or removing an action re-creates the database from the log automatically, but a change inside the callback is not visible to the library. Increase this number to re-create the database after such change. |

## `CrdtAppliedListener`

Listener of the `applied` event.

Type: `(tx: Database, action: Action, meta: ClientMeta | MetaTime, won: CrdtCell[], touched: CrdtCell[]) => Promise | void`.

## `CrdtCell`

Single field of a single row: `[table, id, field]`.

Type: `[table: string, id: string, field: string]`.

## `CrdtColumn`

Column definition created by [`string`](#globals-string), [`number`](#globals-number), [`bigint`](#globals-bigint), [`boolean`](#globals-boolean), [`oneOf`](#globals-oneof) and [`optional`](#globals-optional) builders.

`Type` is the JS type of the column value in rows. `RequiredOnCreate` marks whether [`CrdtTable#create`](#crdttable-create) requires the field (columns wrapped in `optional()` or having `default` don’t).

| Property    | Type                                                    | Description                             |
| ----------- | ------------------------------------------------------- | --------------------------------------- |
| `default` ? | `Type \| () => Type`                                    |                                         |
| `required`  | `RequiredOnCreate`                                      |                                         |
| `sql` ?     | `string \| { [key: string]: string }`                   |                                         |
| `type`      | `"BIGINT" \| "BOOLEAN" \| "DOUBLE PRECISION" \| "TEXT"` | SQL column type used in `CREATE TABLE`. |
| `values` ?  | `readonly string[]`                                     |                                         |

## `CrdtColumnOptions`

| Property    | Type                                    | Description                                                                                                                                                                                                                                             |
| ----------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `default` ? | `() => NoInfer \| NoInfer`              | Default value or function to get it. Column with default becomes optional in [`CrdtTable#create`](#crdttable-create). The default is resolved when the create action is added and is stored inside the action, so replaying the log is deterministic.   |
| `sql` ?     | `string \| { [key: Dialects]: string }` | Extra SQL appended to the column definition, like `'UNIQUE COLLATE NOCASE'`. Pass a string to use it for every database, or an object with [`CrdtDatabaseOptions#dialect`](#crdtdatabaseoptions-dialect) names as keys to set SQL per database dialect. |

## `CrdtColumnType`

Type: `Column ? Type : never`.

## `CrdtColumnValue`

JS types of column values. Only JSON types are supported, because all values are stored in Logux actions and passed to the database driver as-is. Store dates as a number of milliseconds in [`bigint`](#globals-bigint) columns.

Type: `SyncMapTypes`.

## `CrdtCorruption`

Reason why the local database can not be trusted anymore:

- `error`: the database threw the error and can not be prepared.
- `interrupted-migration`: the tab was closed during the schema migration, between the drop of the tables and the end of the replay.
- `lost-database`: the data is gone. The client did not find the database it was using before: the file was lost or replaced by an empty one.
- `timeout`: the database did not answer in [`CrdtDatabaseOptions#timeout`](#crdtdatabaseoptions-timeout).

Type: `"error" | "interrupted-migration" | "lost-database" | "timeout"`.

## `CrdtCreateFields`

Fields accepted by [`CrdtTable#create`](#crdttable-create). Columns wrapped in [`optional`](#globals-optional) or having `default` can be omitted.

Type: `{ [Column: keyof Schema]?: undefined ? Exclude<CrdtColumnType,undefined> | null : CrdtColumnType } & { [Column: keyof Schema]: CrdtColumnType }`.

## `CrdtDatabase`

| Property  | Type                                                                                                                                                                     | Description                                                                           |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| `ready`   | `Promise`                                                                                                                                                                | Promise resolved when the database was prepared and tables can be used.               |
| `status`  | `ReadableAtom`                                                                                                                                                           | Database preparing status:                                                            |
| `tables`  | `CrdtTables`                                                                                                                                                             | Schemas of all tables of [`CrdtDatabase#table`](#crdtdatabase-table) by their plural. |
| `action`  | `(creator: Creator, apply: (tx: Database, action: ReturnType<Creator>, meta: ClientMeta) => void \| Promise, opts?: CrdtActionOptions) => (args: Parameters) => Promise` |                                                                                       |
| `clean`   | `() => Promise`                                                                                                                                                          |                                                                                       |
| `destroy` | `() => void`                                                                                                                                                             |                                                                                       |
| `empty`   | `() => Promise`                                                                                                                                                          |                                                                                       |
| `on`      | `(event: "applied", listener: CrdtAppliedListener) => Unsubscribe`                                                                                                       |                                                                                       |
| `table`   | `(plural: string, schema: Schema, indexes?: CrdtIndex[]) => CrdtTable`                                                                                                   |                                                                                       |

## `CrdtDatabaseOptions`

| Property    | Type                                                          | Description                                                                                                                                                                                                                                                                                                                                                             |
| ----------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dialect` ? | `Dialect`                                                     | SQL dialect of the database: `'sqlite'` (default), `'pglite'` or any other name for your own dialect. The dialect selects per-dialect extra column SQL in [`CrdtColumnOptions#sql`](#crdtcolumnoptions-sql) and prohibits [`boolean`](#globals-boolean) columns in SQLite (values are passed to the database driver without conversion and SQLite has no boolean type). |
| `key` ?     | `string`                                                      | Storage key to store the schema version (also used as the prefix of the leader tab lock name). Change it when the database is used in a third-party widget to avoid conflicts with the website’s own Logux database. Default is `logux:db`.                                                                                                                             |
| `storage` ? | `PersistentStorage`                                           | Storage to keep the tables schema instead of `localStorage` (for instance, for React Native or tests).                                                                                                                                                                                                                                                                  |
| `sync` ?    | `boolean`                                                     | Should table actions be sent to the server. Default is `true`.                                                                                                                                                                                                                                                                                                          |
| `timeout` ? | `number`                                                      | Milliseconds to wait for the database to be prepared.                                                                                                                                                                                                                                                                                                                   |
| `repeat` ?  | `() => [Action, MetaTime][] \| Promise<[Action, MetaTime][]>` |                                                                                                                                                                                                                                                                                                                                                                         |

## `CrdtIndex`

Index definition for [`CrdtDatabase#table`](#crdtdatabase-table). It can be:

- a column name for a single-column index;
- an array of columns for a multi-column index;
- an object with `columns` and `unique`;
- an object with the whole `CREATE INDEX` statement in `sql` for partial indexes, expressions and dialect-specific features.

```ts
let user = crdt.table('user', schema, [
  'email',
  ['teamId', 'name'],
  { columns: ['email'], unique: true },
  {
    sql: `CREATE INDEX IF NOT EXISTS "user_active" ON "user" ("name")` +
      ` WHERE "role" = 'admin'`
  }
])
```

Type: `{ columns: CrdtIndexColumn[], unique?: boolean } | { sql: string } | CrdtIndexColumn | CrdtIndexColumn[]`.

## `CrdtIndexColumn`

Column in [`CrdtIndex`](#crdtindex): a column name from the table schema, `id`, or an `updatedAt_field` column.

Everything after the column name is passed to the database as is, so it can contain `DESC`, `COLLATE` or an operator class:

```ts
let post = crdt.table('post', schema, [
  'publishedAt DESC',
  'title COLLATE NOCASE'
])
```

Type: `` `${CrdtIndexName} ${string}` | CrdtIndexName ``.

## `CrdtParsedAction`

| Property | Type                               | Description                                                                                                    |
| -------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `plural` | `string`                           | Table name from the action type.                                                                               |
| `rows`   | `[id: string, fields: string[]][]` | Rows of the action with the names of the fields it writes to them. A `plural/deleted` action writes no fields. |
| `verb`   | `CrdtVerb`                         | Verb from the action type.                                                                                     |

## `CrdtParsedType`

| Property | Type       | Description                      |
| -------- | ---------- | -------------------------------- |
| `plural` | `string`   | Table name from the action type. |
| `verb`   | `CrdtVerb` | Verb from the action type.       |

## `CrdtRowFields`

Row fields (without `id` and fields meta) inferred from table schema. Optional columns take `null` to clear the value (`undefined` fields are not changed, like in JSON).

Type: `{ [Column: keyof Schema]?: Exclude<CrdtColumnType,undefined> | null } & { [Column: keyof Schema]: CrdtColumnType }`.

## `CrdtSqlParam`

Values for SQL template parameters of [`CrdtTable#select`](#crdttable-select). Parameters are passed to the database driver as-is, without any conversion. Booleans are allowed only in dialects with [`boolean`](#globals-boolean) columns (not in `'sqlite'`).

Type: `Dialect ? never : boolean | number | string`.

## `CrdtTable`

| Property | Type                                                                                                              | Description                                                                                                              |
| -------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `driver` | `Driver`                                                                                                          | Database driver for raw queries to the table, like in [`crdtTableToActions`](#globals-crdttabletoactions).               |
| `plural` | `string`                                                                                                          | Table name. It is used as SQL table name and as prefix of action types (`user/created`, `user/changed`, `user/deleted`). |
| `schema` | `Schema`                                                                                                          | Column definitions of the table.                                                                                         |
| `change` | `(tx: Database, id: string \| string[], fields: Partial<CrdtRowFields>, meta: ClientMeta) => Promise<CrdtCell[]>` |                                                                                                                          |
| `create` | `(rows: NewCrdtRow[]) => Promise<string[]>`                                                                       |                                                                                                                          |
| `delete` | `(id: string \| string[]) => Promise`                                                                             |                                                                                                                          |
| `select` | `(sql?: TemplateStringsArray, params: CrdtSqlParam[]) => SqlStore`                                                |                                                                                                                          |
| `update` | `(id: string \| string[], diff: Partial<CrdtRowFields>) => Promise`                                               |                                                                                                                          |

## `CrdtTableChangedAction`

Type: `{ fields: Partial<Fields>, id: string, type: string } | { fields: Partial<Fields>, ids: string[], type: string }`.

## `CrdtTableCreatedAction`

Type: `{ fields: Fields, id: string, type: string } | { records: Fields & { id: string }[], type: string }`.

## `CrdtTableDeletedAction`

Type: `{ id: string, type: string } | { ids: string[], type: string }`.

## `CrdtTableRow`

Table row returned by [`CrdtTable#select`](#crdttable-select). Rows contain data as the database driver returns it, without any conversion. Missing optional columns are `null`.

Every field has an extra `updatedAt_field` column with Logux Meta ID of the last action which changed it (`null` if the field was never set). They are used to resolve conflicts with per-field last write wins, and can be used in SQL, for instance, to sort by the last change:

```ts
let $recent = user.select`ORDER BY "updatedAt_name" DESC`
```

Type: `{ id: string } & { [Column: keyof Schema]: undefined ? Exclude<CrdtColumnType,undefined> | null : CrdtColumnType } & { [Column: keyof Schema]: null | string }`.

## `CrdtTables`

Schemas of the tables by their plural, like [`CrdtDatabase#tables`](#crdtdatabase-tables).

Type: `{ [key: string]: CrdtTableSchema }`.

## `CrdtTableSchema`

## `CrdtTasks`

| Property  | Type                                    |
| --------- | --------------------------------------- |
| `add`     | `(task: () => void \| Promise) => void` |
| `destroy` | `() => void`                            |
| `finish`  | `() => Promise`                         |

## `CrdtTasksOptions`

| Property    | Type                       | Description                  |
| ----------- | -------------------------- | ---------------------------- |
| `onError` ? | `(error: unknown) => void` | Called when the task throws. |

## `CrdtVerb`

Type: `"changed" | "created" | "deleted"`.

# `CreateClientStore(userIdStore, builder)`

Create stores to keep client instance and update it on user ID changes.

```js
import { createClientStore, Client, log } from '@logux/client'
import { persistentMap } from '@nanostores/persistent'

let sessionStore = persistentMap<{ userId: string }>('session:', {
  userId: 'anonymous'
})

export const clientStore = createClientStore(sessionStore, session => {
  let client new Client({
    subprotocol: SUBPROTOCOL,
    server: 'ws://example.com',
    userId: session.userId
  })
  log(client)
  return client
})
```

| Argument      | Type                        | Description                         |
| ------------- | --------------------------- | ----------------------------------- |
| `userIdStore` | `MapStore<UserId>`          | Store with object and `userId` key. |
| `builder`     | `(value: UserId) => Client` | Callback which return client        |

| Argument      | Type                                     |
| ------------- | ---------------------------------------- |
| `userIdStore` | `MapStore<UserId>`                       |
| `builder`     | `(value: UserId) => Client \| undefined` |

Returns `Atom<Client>`. Atom store with client

## `Criteria`

| Property        | Type         | Description                                                          |
| --------------- | ------------ | -------------------------------------------------------------------- |
| `exceptIndex` ? | `string`     | Do not change reasons for actions with this index in `meta.indexes`. |
| `id` ?          | `string`     | Change reasons only for action with `id`.                            |
| `ids` ?         | `string[]`   | Change reasons only for actions with these IDs.                      |
| `index` ?       | `string`     | Change reasons only for actions with this index in `meta.indexes`.   |
| `maxAdded` ?    | `number`     | Change reasons only for actions with lower `added`.                  |
| `minAdded` ?    | `number`     | Change reasons only for actions with bigger `added`.                 |
| `olderThan` ?   | `ClientMeta` | Change reasons only for actions older than specific action.          |
| `youngerThan` ? | `ClientMeta` | Change reasons only for actions younger than specific action.        |

## `Dialects`

Type: `"sqlite" | "pglite"`.

## `EmptyHeaders`

## `FaviconLinks`

| Property    | Type     | Description                                                              |
| ----------- | -------- | ------------------------------------------------------------------------ |
| `error` ?   | `string` | Error favicon link.                                                      |
| `normal` ?  | `string` | Default favicon link. By default, it will be taken from current favicon. |
| `offline` ? | `string` | Offline favicon link.                                                    |

## `Fields`

## `Filter`

Type: `{ [Key: keyof Value]?: Value[Key] }`.

## `FilterOptions`

| Property            | Type      |
| ------------------- | --------- |
| `listChangesOnly` ? | `boolean` |

## `FilterStore`

Type: `FilterStoreExt & MapStore<FilterValue>`.

## `FilterStoreExt`

| Property  | Type      | Description                                             |
| --------- | --------- | ------------------------------------------------------- |
| `loading` | `Promise` | While store is loading initial data from server or log. |

## `FilterValue`

Type: `{ isLoading: true } | LoadedFilterValue`.

## `GetOptions`

| Property   | Type                   | Description                                                         |
| ---------- | ---------------------- | ------------------------------------------------------------------- |
| `index` ?  | `string`               | Get entries with a custom index.                                    |
| `order` ?  | `"added" \| "created"` | Sort entries by created time or when they was added to current log. |
| `reason` ? | `string`               | Get only entries with this reason.                                  |

## `ID`

Action unique ID across all nodes.

```js
"OzcVoWD 380:R7BNGA:1"
```

Type: `string`.

## `IDComponents`

| Property   | Type                  |
| ---------- | --------------------- |
| `clientId` | `string`              |
| `nodeId`   | `string`              |
| `userId`   | `string \| undefined` |

## `LastSynced`

| Property   | Type     | Description                                 |
| ---------- | -------- | ------------------------------------------- |
| `received` | `number` | The `added` value of latest received event. |
| `sent`     | `number` | The `added` value of latest sent event.     |

## `LoadableStore`

Type: `{ loading: Promise<unknown> } & ReadableAtom`.

## `LoadedFilter`

Type: `FilterStoreExt & MapStore<LoadedFilterValue>`.

## `LoadedFilterValue`

| Property    | Type                       |
| ----------- | -------------------------- |
| `isEmpty`   | `boolean`                  |
| `isLoading` | `false`                    |
| `list`      | `LoadedSyncMapValue[]`     |
| `stores`    | `Map<string,SyncMapStore>` |

## `LoadedSyncMap`

Type: `MapStore<LoadedSyncMapValue> & SyncMapStoreExt`.

## `LoadedSyncMapValue`

Type: `{ id: string, isLoading: false } & Value`.

## `LoadedValue`

Type: `{ isLoading: false } & Type`.

# LocalConnection

Extends [Connection](#connection).

Abstract interface for connection to synchronize logs over it. For example, WebSocket or Loopback.

## `LocalConnection#connected`

Is connection is enabled.

Type: `boolean`.

## `LocalConnection#destroy`

Disconnect and unbind all even listeners.

Type: `() => void`.

## `LocalConnection#connect()`

Start connection. Connection should be in disconnected state from the beginning and start connection only on this method call.

This method could be called again if connection moved to disconnected state.

Returns `Promise`. Promise until connection will be established.

## `LocalConnection#disconnect(reason?)`

Finish current connection.

| Argument   | Type                                | Description           |
| ---------- | ----------------------------------- | --------------------- |
| `reason` ? | `"destroy" \| "error" \| "timeout"` | Disconnection reason. |

## `LocalConnection#on(event, listener)`

| Argument   | Type                       |
| ---------- | -------------------------- |
| `event`    | `"disconnect"`             |
| `listener` | `(reason: string) => void` |

| Argument   | Type                     |
| ---------- | ------------------------ |
| `event`    | `"error"`                |
| `listener` | `(error: Error) => void` |

| Argument   | Type                                        | Description     |
| ---------- | ------------------------------------------- | --------------- |
| `event`    | `"connect" \| "connecting" \| "disconnect"` | Event name.     |
| `listener` | `() => void`                                | Event listener. |

| Argument   | Type                     |
| ---------- | ------------------------ |
| `event`    | `"message"`              |
| `listener` | `(msg: Message) => void` |

Returns `Unsubscribe`.

## `LocalConnection#other()`

Returns `LocalConnection`.

## `LocalConnection#send(message)`

Send message to connection.

| Argument  | Type      | Description             |
| --------- | --------- | ----------------------- |
| `message` | `Message` | The message to be sent. |

# LocalPair

Two paired loopback connections.

```js
import { LocalPair, ClientNode, ServerNode } from '@logux/core'
const pair = new LocalPair()
const client = new ClientNode('client', log1, pair.left)
const server = new ServerNode('server', log2, pair.right)
```

| Parameter | Type     | Description                                           |
| --------- | -------- | ----------------------------------------------------- |
| `delay` ? | `number` | Delay for connection and send events. Default is `1`. |

## `LocalPair#delay`

Delay for connection and send events to emulate real connection latency.

Type: `number`.

## `LocalPair#left`

First connection. Will be connected to `right` one after `connect()`.

```js
new ClientNode('client, log1, pair.left)
```

Type: `LocalConnection`.

## `LocalPair#right`

Second connection. Will be connected to `right` one after `connect()`.

```js
new ServerNode('server, log2, pair.right)
```

Type: `LocalConnection`.

## `LogMessages`

| Property          | Type       | Description                                  |
| ----------------- | ---------- | -------------------------------------------- |
| `add` ?           | `boolean`  | Disable action added messages.               |
| `clean` ?         | `boolean`  | Disable action cleaned messages.             |
| `error` ?         | `boolean`  | Disable error messages.                      |
| `ignoreActions` ? | `string[]` | Disable action messages with specific types. |
| `role` ?          | `boolean`  | Disable tab role messages.                   |
| `state` ?         | `boolean`  | Disable connection state messages.           |
| `user` ?          | `boolean`  | Disable user ID changing.                    |

## `LogOptions`

| Property | Type     | Description                  |
| -------- | -------- | ---------------------------- |
| `nodeId` | `string` | Unique current machine name. |
| `store`  | `Store`  | Store for log.               |

## `LogPage`

| Property  | Type                     | Description      |
| --------- | ------------------------ | ---------------- |
| `entries` | `[Action, ClientMeta][]` | Pagination page. |
| `next` ?  | `() => Promise<LogPage>` |                  |

# LogStore

Every Store class should provide 8 standard methods.

## `LogStore#add(action, meta)`

Add action to store. Action always will have `type` property.

| Argument | Type         | Description        |
| -------- | ------------ | ------------------ |
| `action` | `AnyAction`  | The action to add. |
| `meta`   | `ClientMeta` | Action’s metadata. |

Returns `Promise<false | ClientMeta>`. Promise with `meta` for new action or `false` if action with same `meta.id` was already in store.

## `LogStore#addReason(reasons, criteria)`

Add reasons to metadata of actions, which are already in the store.

Reasons, which action already has, should not be duplicated.

| Argument   | Type       | Description                                   |
| ---------- | ---------- | --------------------------------------------- |
| `reasons`  | `string[]` | The reason names.                             |
| `criteria` | `Criteria` | Criteria to select actions for reason adding. |

Returns `Promise`. Promise when adding will be finished.

## `LogStore#byId(id)`

Return action by action ID.

| Argument | Type     | Description |
| -------- | -------- | ----------- |
| `id`     | `string` | Action ID.  |

Returns `Promise<[null, null] | [Action, ClientMeta]>`. Promise with array of action and metadata.

## `LogStore#changeMeta(id, diff)`

Change action metadata.

| Argument | Type                  | Description                                      |
| -------- | --------------------- | ------------------------------------------------ |
| `id`     | `string`              | Action ID.                                       |
| `diff`   | `Partial<ClientMeta>` | Object with values to change in action metadata. |

Returns `Promise<boolean>`. Promise with `true` if metadata was changed or `false` on unknown ID.

## `LogStore#clean()`

Remove all data from the store.

Returns `Promise`. Promise when cleaning will be finished.

## `LogStore#get(opts?)`

Return a Promise with first page. Page object has `entries` property with part of actions and `next` property with function to load next page. If it was a last page, `next` property should be empty.

This tricky API is used, because log could be very big. So we need pagination to keep them in memory.

| Argument | Type         | Description    |
| -------- | ------------ | -------------- |
| `opts` ? | `GetOptions` | Query options. |

Returns `Promise<LogPage>`. Promise with first page.

## `LogStore#getLastAdded()`

Return biggest `added` number in store. All actions in this log have less or same `added` time.

Returns `Promise<number>`. Promise with biggest `added` number.

## `LogStore#getLastSynced()`

Get `added` values for latest synchronized received/sent events.

Returns `Promise<LastSynced>`. Promise with `added` values

## `LogStore#remove(id)`

Remove action from store.

| Argument | Type     | Description |
| -------- | -------- | ----------- |
| `id`     | `string` | Action ID.  |

Returns `Promise<false | [Action, ClientMeta]>`. Promise with entry if action was in store.

## `LogStore#removeReason(reasons, criteria, callback)`

Remove reasons from action’s metadata and remove actions without reasons.

| Argument   | Type               | Description                                     |
| ---------- | ------------------ | ----------------------------------------------- |
| `reasons`  | `string[]`         | The reason names.                               |
| `criteria` | `Criteria`         | Criteria to select actions for reason removing. |
| `callback` | `ReadonlyListener` | Callback for every removed action.              |

Returns `Promise`. Promise when cleaning will be finished.

## `LogStore#setLastSynced(values)`

Set `added` value for latest synchronized received or/and sent events.

| Argument | Type                  | Description                                 |
| -------- | --------------------- | ------------------------------------------- |
| `values` | `Partial<LastSynced>` | Object with latest sent or received values. |

Returns `Promise`. Promise when values will be saved to store.

# LoguxError

Extends `Error`.

Logux error in logs synchronization.

```js
if (error.name === 'LoguxError') {
  console.log('Server throws: ' + error.description)
}
```

| Parameter    | Type                           | Description                          |
| ------------ | ------------------------------ | ------------------------------------ |
| `type`       | `ErrorType`                    | The error code.                      |
| `options` ?  | `LoguxErrorOptions[ErrorType]` | The error option.                    |
| `received` ? | `boolean`                      | Was error received from remote node. |

## `LoguxError.description(type, options?)`

Return a error description by it code.

| Argument    | Type                      | Description                               |
| ----------- | ------------------------- | ----------------------------------------- |
| `type`      | `Type`                    | The error code.                           |
| `options` ? | `LoguxErrorOptions[Type]` | The errors options depends on error code. |

Returns `string`.

## `LoguxError#description`

Human-readable error description.

```js
console.log('Server throws: ' + error.description)
```

Type: `string`.

## `LoguxError#message`

Full text of error to print in debug message.

Type: `string`.

## `LoguxError#name`

Always equal to `LoguxError`. The best way to check error class.

```js
if (error.name === 'LoguxError') {
```

Type: `"LoguxError"`.

## `LoguxError#options`

Error options depends on error type.

```js
if (error.type === 'timeout') {
  console.error('A timeout was reached (' + error.options + ' ms)')
}
```

Type: `LoguxErrorOptions[ErrorType]`.

## `LoguxError#received`

Was error received from remote client.

Type: `boolean`.

## `LoguxError#stack`

Calls which cause the error.

Type: `string`.

## `LoguxError#type`

The error code.

```js
if (error.type === 'timeout') {
  fixNetwork()
}
```

Type: `ErrorType`.

## `LoguxErrorOptions`

| Property            | Type       |
| ------------------- | ---------- |
| `bruteforce`        | `void`     |
| `timeout`           | `number`   |
| `unknown-message`   | `string`   |
| `wrong-credentials` | `void`     |
| `wrong-format`      | `string`   |
| `wrong-protocol`    | `Versions` |
| `wrong-subprotocol` | `Versions` |

# LoguxNotFoundError

Extends `Error`.

An error for `load()` callback to return `logux/undo` with 404.

```js
import { LoguxNotFoundError } from '@logux/actions'

server.channel('posts/:id', {
  load () {
    throw new LoguxNotFoundError()
  },
  …
})
```

## `LoguxNotFoundError#name`

Type: `"LoguxNotFoundError"`.

## `LoguxProcessedAction`

| Property | Type                |
| -------- | ------------------- |
| `id`     | `string`            |
| `type`   | `"logux/processed"` |

## `LoguxSubscribeAction`

| Property     | Type                           |
| ------------ | ------------------------------ |
| `channel`    | `string`                       |
| `creating` ? | `true`                         |
| `filter` ?   | `{ }`                          |
| `since` ?    | `{ id: string, time: number }` |
| `type`       | `"logux/subscribe"`            |

## `LoguxSubscribedAction`

| Property  | Type                 |
| --------- | -------------------- |
| `channel` | `string`             |
| `type`    | `"logux/subscribed"` |

## `LoguxUndoAction`

| Property | Type             |
| -------- | ---------------- |
| `action` | `RevertedAction` |
| `id`     | `string`         |
| `reason` | `Reason`         |
| `type`   | `"logux/undo"`   |

# LoguxUndoError

Extends `Error`.

Error on `logux/undo` action from the server.

```js
try {
  client.sync(action)
} catch (e) {
  if (e.name === 'LoguxUndoError') {
    console.log(e.action.action.type ' was undid')
  }
}
```

| Parameter | Type             |
| --------- | ---------------- |
| `action`  | `RevertedAction` |

## `LoguxUndoError#action`

Server `logux/undo` action. It has origin actions (which was undid) in `action.action`.

```js
console.log(error.action.action.type ' was undid')
```

Type: `RevertedAction`.

## `LoguxUndoError#name`

The better way to check error, than `instanceof`.

```js
if (error.name === 'LoguxUndoError') {
```

Type: `"LoguxUndoError"`.

## `LoguxUnsubscribeAction`

| Property   | Type                  |
| ---------- | --------------------- |
| `channel`  | `string`              |
| `filter` ? | `{ }`                 |
| `type`     | `"logux/unsubscribe"` |

## `Message`

Type: `["connect", number, string, number, ?] | ["connected", number, string, [number, number], ?] | ["debug", "error", string] | ["error", keyof LoguxErrorOptions, ?] | ["headers", object] | ["ping", number] | ["pong", number] | ["sync", number, ...AnyAction | SyncMeta[]] | ["synced", number]`.

## `Meta`

| Property        | Type       | Description                                                               |
| --------------- | ---------- | ------------------------------------------------------------------------- |
| `added`         | `number`   | Sequence number of action in current log. Log fills it.                   |
| `id`            | `string`   | Action unique ID. Log sets it automatically.                              |
| `indexes` ?     | `string[]` | Indexes for action quick extraction.                                      |
| `keepLast` ?    | `string`   | Set value to `reasons` and this reason from old action.                   |
| `reasons`       | `string[]` | Why action should be kept in log. Action without reasons will be removed. |
| `subprotocol` ? | `number`   | Application subprotocol version.                                          |
| `time`          | `number`   | Action created time in current node time. Milliseconds since UNIX epoch.  |

## `MetaTime`

Type: `Pick<ClientMeta,"time" | "id">`.

## `NewCrdtRow`

Row accepted by [`CrdtTable#create`](#crdttable-create): [`CrdtCreateFields`](#crdtcreatefields) with optional `id`, which will be generated if omitted.

Type: `{ id?: string } & CrdtCreateFields`.

## `NodeOptions`

| Property        | Type                       | Description                                                                                                                                                               |
| --------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auth` ?        | `Authenticator`            | Function to check client credentials.                                                                                                                                     |
| `fixTime` ?     | `boolean`                  | Detect difference between client and server and fix time in synchronized actions.                                                                                         |
| `onReceive` ?   | `ActionFilter`             | Function to filter or change actions coming from remote node’s before put it to current log.                                                                              |
| `onSend` ?      | `ActionFilter`             | Function to filter or change actions before sending to remote node’s.                                                                                                     |
| `ping` ?        | `number`                   | Milliseconds since last message to test connection by sending ping.                                                                                                       |
| `subprotocol` ? | `number`                   | Application subprotocol version.                                                                                                                                          |
| `syncBatch` ?   | `number`                   | Maximum actions in a single `sync` message. `100` by default. Node will split a bigger batch into a few messages, so the remote node will be able to apply them by parts. |
| `timeout` ?     | `number`                   | Timeout in milliseconds to wait answer before disconnect.                                                                                                                 |
| `token` ?       | `string \| TokenGenerator` | Client credentials. For example, access token.                                                                                                                            |

## `NodeState`

Type: `"connecting" | "disconnected" | "sending" | "synchronized"`.

## `OmitFromUnion`

`Omit` which is applied to each member of the union separately. It is necessary for actions with different shapes, like batch actions.

Type: `Type ? Omit<Type,Keys> : never`.

## `PackedAction`

| Property | Type            |
| -------- | --------------- |
| `action` | `ReducedAction` |
| `blob`   | `Uint8Array`    |

## `PersistentStorage`

Type: `{ [key: string]: string | undefined }`.

## `PreactErrorHandlers`

| Property         | Type  |
| ---------------- | ----- |
| `AccessDenied` ? | `any` |
| `Error` ?        | `any` |
| `NotFound` ?     | `any` |

# `PreaddListener(action, meta)`

| Argument | Type             |
| -------- | ---------------- |
| `action` | `ListenerAction` |
| `meta`   | `LogMeta`        |

# `PrepareForTest(client, Template, value)`

| Argument   | Type                                 |
| ---------- | ------------------------------------ |
| `client`   | `Client`                             |
| `Template` | `SyncMapTemplateLike`                |
| `value`    | `{ id?: string } & Omit<Value,"id">` |

| Argument   | Type                                 |
| ---------- | ------------------------------------ |
| `client`   | `Client`                             |
| `Template` | `SyncMapTemplate`                    |
| `value`    | `{ id?: string } & Omit<Value,"id">` |

Returns `MapStore<Value>`.

## `ReactErrorHandlers`

| Property         | Type  |
| ---------------- | ----- |
| `AccessDenied` ? | `any` |
| `Error` ?        | `any` |
| `NotFound` ?     | `any` |

# `ReadonlyListener(action, meta)`

| Argument | Type             |
| -------- | ---------------- |
| `action` | `ListenerAction` |
| `meta`   | `LogMeta`        |

## `ReconnectOptions`

| Property     | Type     | Description                          |
| ------------ | -------- | ------------------------------------ |
| `attempts` ? | `number` | Maximum reconnecting attempts.       |
| `maxDelay` ? | `number` | Maximum delay between re-connecting. |
| `minDelay` ? | `number` | Minimum delay between re-connecting. |

## `Reducer`

| Property  | Type                                                           | Description                                                                            |
| --------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `ready`   | `Promise`                                                      | Promise resolved when the data was prepared and all actions from the log were reduced. |
| `status`  | `ReadableAtom`                                                 |                                                                                        |
| `destroy` | `() => void`                                                   |                                                                                        |
| `type`    | `(type: TypeAction["type"], listener: ActionListener) => void` |                                                                                        |

## `ReducerInitCallbacks`

| Property      | Type                                                                                               | Description                                                                                                |
| ------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `storage` ?   | `PersistentStorage`                                                                                | Storage to keep the reducer’s version instead of `localStorage` (for instance, for React Native or tests). |
| `clean`       | `(oldVersion: number) => void \| [Action, MetaTime][] \| Promise \| Promise<[Action, MetaTime][]>` |                                                                                                            |
| `init` ?      | `() => void \| Promise`                                                                            |                                                                                                            |
| `migrating` ? | `(done: Promise) => void`                                                                          |                                                                                                            |
| `stop` ?      | `() => void`                                                                                       |                                                                                                            |

## `ReducerMigrationStatus`

Type: `"initializing" | "migrating" | "outdated" | "ready"`.

## `RequestOptions`

| Property                   | Type      | Description                                                                                              |
| -------------------------- | --------- | -------------------------------------------------------------------------------------------------------- |
| `allowDangerousProtocol` ? | `boolean` | Do not show warning when using `ws://` in production.                                                    |
| `attempts` ?               | `number`  | Maximum reconnection attempts. Default is `Infinity`.                                                    |
| `maxDelay` ?               | `number`  | Maximum delay between reconnections. Default is `5000`.                                                  |
| `minDelay` ?               | `number`  | Minimum delay between reconnections. Default is `1000`.                                                  |
| `ping` ?                   | `number`  | Milliseconds since last message to test connection by sending ping. Default is `10000`.                  |
| `prefix` ?                 | `string`  | Prefix for `IndexedDB` database to run multiple Logux instances in the same browser. Default is `logux`. |
| `server`                   | `any`     | Server URL.                                                                                              |
| `store` ?                  | `any`     | Store to save log data. Default is `MemoryStore`.                                                        |
| `subprotocol`              | `number`  | Client subprotocol version.                                                                              |
| `time` ?                   | `any`     | Test time to test client.                                                                                |
| `timeout` ?                | `number`  | Timeout in milliseconds to break connection. Default is `70000`.                                         |
| `token` ?                  | `any`     | Client credentials for authentication.                                                                   |
| `userId` ?                 | `string`  |                                                                                                          |

## `ShadowAction`

| Property | Type       |
| -------- | ---------- |
| `id`     | `string`   |
| `type`   | `"shadow"` |

# SqlLogStore

Extends `unknown`.

SQL store for Logux log on top of `@nanostores/sql` database.

It works with any driver of `@nanostores/sql`: SQLite in the browser by SQLocal, PGlite, Expo SQLite in React Native, or Node.js SQLite in tests.

```js
import { CrossTabClient } from '@logux/client'
import { SqlLogStore } from '@logux/client/db'
import { openDb } from '@nanostores/sql'
import { sqlocalDriver } from '@nanostores/sql/sqlocal'

const db = openDb(sqlocalDriver('app.sqlite'))
const client = new CrossTabClient({
  …,
  store: new SqlLogStore(db)
})
```

The store keeps actions in `logux_log`, `logux_reason`, `logux_index`, and `logux_extra` tables. They will be created on the first query.

Version of the tables format is kept in `logux_version` table. If the database was created by a newer version of the client, all methods will throw an error.

| Parameter | Type                 | Description                                 |
| --------- | -------------------- | ------------------------------------------- |
| `db`      | `Database`           | Database from `@nanostores/sql` `openDb()`. |
| `opts` ?  | `SqlLogStoreOptions` | Store options.                              |

## `SqlLogStore#onTransactionAdd(callback)`

Set the callback, which will be called inside the transaction writing the action to the log. It allows to apply the action to the tables of the same database atomically: the action and its result will be committed together, and `await` of `log.add()` will mean that the tables were already changed.

[`createCrdtDatabase`](#globals-createcrdtdatabase) sets it automatically, if the log and the CRDT tables are in the same database.

An error in the callback rolls back the whole transaction, so the action will not be added to the log.

| Argument   | Type                                                                                  | Description                                         |
| ---------- | ------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `callback` | `(tx: Database, action: AnyAction, meta: ClientMeta) => void \| Promise \| undefined` | Callback or `undefined` to remove the previous one. |

## `SqlLogStoreOptions`

| Property    | Type      | Description                                                                                             |
| ----------- | --------- | ------------------------------------------------------------------------------------------------------- |
| `packers` ? | `Packers` | Packers to keep the binary parts of the actions in a separate column instead of Base64 inside the JSON. |

# `StatusListener(current, details)`

| Argument  | Type                                                                             |
| --------- | -------------------------------------------------------------------------------- |
| `current` | `StatusValue`                                                                    |
| `details` | `{ action: LoguxUndoAction, meta: ClientMeta } \| { error: Error } \| undefined` |

## `StatusOptions`

| Property     | Type     | Description                                     |
| ------------ | -------- | ----------------------------------------------- |
| `duration` ? | `number` | Synchronized state duration. Default is `3000`. |

## `StatusValue`

Type: `"connecting" | "connectingAfterWait" | "denied" | "disconnected" | "error" | "protocolError" | "sending" | "sendingAfterWait" | "syncError" | "synchronized" | "synchronizedAfterWait" | "wait" | "wrongCredentials"`.

# `StorageActionListener(prevValue, action, meta)`

| Argument    | Type           |
| ----------- | -------------- |
| `prevValue` | `Value`        |
| `action`    | `ListenAction` |
| `meta`      | `any`          |

Returns `Value | Promise<Value>`.

## `StorageCallbacks`

| Property      | Type                                                          | Description                                                                                                              |
| ------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `storage` ?   | `PersistentStorage`                                           | Storage to keep the value and the reducer’s version instead of `localStorage` (for instance, for React Native or tests). |
| `migrating` ? | `(done: Promise) => void`                                     |                                                                                                                          |
| `repeat`      | `() => [Action, MetaTime][] \| Promise<[Action, MetaTime][]>` |                                                                                                                          |

## `StorageReducer`

| Property  | Type                                                                  | Description                                                                                                                                   |
| --------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `ready`   | `Promise`                                                             | Promise resolved when the value was loaded and all actions from the log were reduced. It is also resolved when the reducer became `outdated`. |
| `status`  | `ReadableAtom`                                                        |                                                                                                                                               |
| `value`   | `ReadableAtom`                                                        | The reduced value.                                                                                                                            |
| `destroy` | `() => void`                                                          |                                                                                                                                               |
| `type`    | `(type: TypeAction["type"], listener: StorageActionListener) => void` |                                                                                                                                               |

## `SyncMapChangeAction`

| Property | Type                        |
| -------- | --------------------------- |
| `fields` | `Partial<Omit<Value,"id">>` |
| `id`     | `string`                    |
| `type`   | `string`                    |

## `SyncMapChangedAction`

| Property | Type                        |
| -------- | --------------------------- |
| `fields` | `Partial<Omit<Value,"id">>` |
| `id`     | `string`                    |
| `type`   | `string`                    |

## `SyncMapCreateAction`

| Property | Type               |
| -------- | ------------------ |
| `fields` | `Omit<Value,"id">` |
| `id`     | `string`           |
| `type`   | `string`           |

## `SyncMapCreatedAction`

| Property | Type               |
| -------- | ------------------ |
| `fields` | `Omit<Value,"id">` |
| `id`     | `string`           |
| `type`   | `string`           |

## `SyncMapDeleteAction`

| Property | Type     |
| -------- | -------- |
| `id`     | `string` |
| `type`   | `string` |

## `SyncMapDeletedAction`

| Property | Type     |
| -------- | -------- |
| `id`     | `string` |
| `type`   | `string` |

## `SyncMapStore`

Type: `MapStore<SyncMapValue> & SyncMapStoreExt`.

## `SyncMapStoreExt`

| Property      | Type      | Description                                               |
| ------------- | --------- | --------------------------------------------------------- |
| `client`      | `Client`  | Logux Client instance.                                    |
| `createdAt` ? | `any`     | Meta from create action if the store was created locally. |
| `deleted` ?   | `true`    | Mark that store was deleted.                              |
| `loading`     | `Promise` | While store is loading initial data from server or log.   |
| `offline`     | `boolean` | Does store keep data in the log after store is destroyed. |
| `plural`      | `string`  | Name of map class.                                        |
| `remote`      | `boolean` | Does store use server to load and save data.              |

# `SyncMapTemplate(id, client, args)`

| Argument | Type                                               |
| -------- | -------------------------------------------------- |
| `id`     | `string`                                           |
| `client` | `Client`                                           |
| `args`   | `[] \| [Action, ClientMeta, boolean \| undefined]` |

Returns `any`.

## `SyncMapTemplate#cache`

Type: `{ }`.

## `SyncMapTemplate#offline`

Type: `boolean`.

## `SyncMapTemplate#plural`

Type: `string`.

## `SyncMapTemplate#remote`

Type: `boolean`.

# `SyncMapTemplateLike(id, client, args)`

| Argument | Type     |
| -------- | -------- |
| `id`     | `string` |
| `client` | `Client` |
| `args`   | `Args`   |

Returns `MapStore<Value>`.

## `SyncMapTypes`

Type: `boolean | null | number | string | undefined`.

## `SyncMapValue`

Type: `{ id: string, isLoading: true } | LoadedSyncMapValue`.

## `SyncMapValues`

## `SyncMeta`

| Property        | Type     |
| --------------- | -------- |
| `id`            | `string` |
| `subprotocol` ? | `number` |
| `time`          | `number` |

## `TabID`

Type: `string`.

## `TestClientOptions`

| Property        | Type         |
| --------------- | ------------ |
| `headers` ?     | `Headers`    |
| `server` ?      | `TestServer` |
| `subprotocol` ? | `number`     |

## `TestLogOptions`

| Property   | Type       | Description                                                       |
| ---------- | ---------- | ----------------------------------------------------------------- |
| `nodeId` ? | `string`   | Unique log name.                                                  |
| `store` ?  | `LogStore` | Store for log. Will use [`MemoryStore`](#memorystore) by default. |

# `TokenGenerator()`

Returns `string | Promise<string>`.

## `Versions`

| Property    | Type     |
| ----------- | -------- |
| `supported` | `number` |
| `used`      | `number` |

## `WithoutMeta`

Row without conflict resolution data of every field.

Type: `{ [Key: keyof Value]: Value[Key] }`.

# WsBinaryConnection

Extends [WsConnection](https://logux.org/web-api/#wsconnection).

Logux connection for WebSocket using binary protocol.

Automatically detects text-based peers and falls back to JSON encoding, so it can be used in ServerConnection to handle both binary and text clients.

```js
import { WsBinaryConnection } from '@logux/core'

const connection = new WsBinaryConnection('wss://logux.example.com/')
const node = new ClientNode(nodeId, log, connection, opts)
```

| Parameter | Type      | Description                             |
| --------- | --------- | --------------------------------------- |
| `url`     | `string`  | WebSocket server URL.                   |
| `Class` ? | `unknown` |                                         |
| `opts` ?  | `unknown` | Extra option for WebSocket constructor. |

## `WsBinaryConnection#connected`

Is connection is enabled.

Type: `boolean`.

## `WsBinaryConnection#destroy`

Disconnect and unbind all even listeners.

Type: `() => void`.

## `WsBinaryConnection#textMode`

Whether to use text JSON protocol instead of binary. Always `true` for WsConnection, can change in WsBinaryConnection.

Type: `boolean`.

## `WsBinaryConnection#ws`

WebSocket instance.

Type: `WS`.

## `WsBinaryConnection#connect()`

Start connection. Connection should be in disconnected state from the beginning and start connection only on this method call.

This method could be called again if connection moved to disconnected state.

Returns `Promise`. Promise until connection will be established.

## `WsBinaryConnection#disconnect(reason?)`

Finish current connection.

| Argument   | Type                                | Description           |
| ---------- | ----------------------------------- | --------------------- |
| `reason` ? | `"destroy" \| "error" \| "timeout"` | Disconnection reason. |

## `WsBinaryConnection#on(event, listener)`

| Argument   | Type                       |
| ---------- | -------------------------- |
| `event`    | `"disconnect"`             |
| `listener` | `(reason: string) => void` |

| Argument   | Type                     |
| ---------- | ------------------------ |
| `event`    | `"error"`                |
| `listener` | `(error: Error) => void` |

| Argument   | Type                                        | Description     |
| ---------- | ------------------------------------------- | --------------- |
| `event`    | `"connect" \| "connecting" \| "disconnect"` | Event name.     |
| `listener` | `() => void`                                | Event listener. |

| Argument   | Type                     |
| ---------- | ------------------------ |
| `event`    | `"message"`              |
| `listener` | `(msg: Message) => void` |

Returns `Unsubscribe`.

## `WsBinaryConnection#send(message)`

Send message to connection.

| Argument  | Type      | Description             |
| --------- | --------- | ----------------------- |
| `message` | `Message` | The message to be sent. |

# WsConnection

Extends [Connection](#connection).

Logux connection for WebSocket.

```js
import { WsConnection } from '@logux/core'

const connection = new WsConnection('wss://logux.example.com/')
const node = new ClientNode(nodeId, log, connection, opts)
```

| Parameter | Type      | Description                             |
| --------- | --------- | --------------------------------------- |
| `url`     | `string`  | WebSocket server URL.                   |
| `Class` ? | `unknown` |                                         |
| `opts` ?  | `unknown` | Extra option for WebSocket constructor. |

## `WsConnection#connected`

Is connection is enabled.

Type: `boolean`.

## `WsConnection#destroy`

Disconnect and unbind all even listeners.

Type: `() => void`.

## `WsConnection#textMode`

Whether to use text JSON protocol instead of binary. Always `true` for WsConnection, can change in WsBinaryConnection.

Type: `boolean`.

## `WsConnection#ws`

WebSocket instance.

Type: `WS`.

## `WsConnection#connect()`

Start connection. Connection should be in disconnected state from the beginning and start connection only on this method call.

This method could be called again if connection moved to disconnected state.

Returns `Promise`. Promise until connection will be established.

## `WsConnection#disconnect(reason?)`

Finish current connection.

| Argument   | Type                                | Description           |
| ---------- | ----------------------------------- | --------------------- |
| `reason` ? | `"destroy" \| "error" \| "timeout"` | Disconnection reason. |

## `WsConnection#on(event, listener)`

| Argument   | Type                       |
| ---------- | -------------------------- |
| `event`    | `"disconnect"`             |
| `listener` | `(reason: string) => void` |

| Argument   | Type                     |
| ---------- | ------------------------ |
| `event`    | `"error"`                |
| `listener` | `(error: Error) => void` |

| Argument   | Type                                        | Description     |
| ---------- | ------------------------------------------- | --------------- |
| `event`    | `"connect" \| "connecting" \| "disconnect"` | Event name.     |
| `listener` | `() => void`                                | Event listener. |

| Argument   | Type                     |
| ---------- | ------------------------ |
| `event`    | `"message"`              |
| `listener` | `(msg: Message) => void` |

Returns `Unsubscribe`.

## `WsConnection#send(message)`

Send message to connection.

| Argument  | Type      | Description             |
| --------- | --------- | ----------------------- |
| `message` | `Message` | The message to be sent. |

## `ZeroAction`

| Property     | Type         |
| ------------ | ------------ |
| `compressed` | `boolean`    |
| `d`          | `Uint8Array` |
| `iv`         | `Uint8Array` |
| `type`       | `"0"`        |

## `ZeroCleanAction`

Type: `{ type: "0/clean" } & { id: string } | { ids: string[] }`.

# `actionEvents(emitter, event, action, meta)`

| Argument  | Type                           |
| --------- | ------------------------------ |
| `emitter` | `Emitter`                      |
| `event`   | `"add" \| "clean" \| "preadd"` |
| `action`  | `Action`                       |
| `meta`    | `ClientMeta`                   |

## `badgeEn`

English translation for widget.

Type: `BadgeMessages`.

## `badgeRu`

Russian translation for widget.

Type: `BadgeMessages`.

## `badgeStyles`

Type: `BadgeStyles`.

# `bigint(opts?)`

`BIGINT` column with `number` value. Use it for timestamps as a number of milliseconds — the same format as dates in Logux actions:

```ts
import { bigint } from '@logux/client/db'

let schema = {
  createdAt: bigint({ default: () => Date.now() }),
  publishedAt: optional(bigint())
}
```

| Argument | Type                                          | Description                                    |
| -------- | --------------------------------------------- | ---------------------------------------------- |
| `opts` ? | `string \| Omit<CrdtColumnOptions,"default">` | Extra column definition SQL or column options. |

| Argument | Type                                                        | Description                                    |
| -------- | ----------------------------------------------------------- | ---------------------------------------------- |
| `opts`   | `{ default: () => NoInfer \| NoInfer } & CrdtColumnOptions` | Extra column definition SQL or column options. |

Returns `{ type: "BIGINT" } & CrdtColumn`.

# `boolean(opts?)`

`BOOLEAN` column for databases with native boolean support like PGlite. SQLite has no boolean type, so with `'sqlite'` dialect use [`number`](#globals-number) column with `1`/`0` instead (using this builder there is a type error and throws in [`CrdtDatabase#table`](#crdtdatabase-table)).

| Argument | Type                                          | Description                                    |
| -------- | --------------------------------------------- | ---------------------------------------------- |
| `opts` ? | `string \| Omit<CrdtColumnOptions,"default">` | Extra column definition SQL or column options. |

| Argument | Type                                                        | Description                                    |
| -------- | ----------------------------------------------------------- | ---------------------------------------------- |
| `opts`   | `{ default: boolean \| () => boolean } & CrdtColumnOptions` | Extra column definition SQL or column options. |

Returns `{ type: "BOOLEAN" } & CrdtColumn`.

# `crdtTableToActions(tables)`

Restore actions from the tables.

The oldest restored action of a row is `plural/created`, the rest are `plural/changed`. Rows changed by one batch action are restored as a single batch action.

```ts
let crdt = createCrdtDatabase(client, db, {
  repeat: () => crdtTableToActions([user, post])
})
```

| Argument | Type                                                 | Description                                                    |
| -------- | ---------------------------------------------------- | -------------------------------------------------------------- |
| `tables` | `Pick<CrdtTable,"driver" \| "plural" \| "schema">[]` | Tables of [`CrdtDatabase#table`](#crdtdatabase-table) to read. |

Returns `Promise<[Action, MetaTime][]>`. Actions with restored meta `id` and `time`, oldest first.

# `createAuth(client)`

Create store with user’s authentication state.

```js
import { createAuth } from '@logux/client'

let auth = createAuth(client)
await auth.loading
console.log(auth.get())
```

| Argument | Type     | Description   |
| -------- | -------- | ------------- |
| `client` | `Client` | Logux Client. |

Returns `AuthStore`.

## `createClientStore`

Type: `CreateClientStore`.

# `createCrdtDatabase(client, db, opts?)`

Create CRDT LWW Map tables on top of SQL database filled from Logux log.

Tables are filled by reducing actions from the log (like [`createReducer`](#globals-createreducer)). [`CrdtTable#create`](#crdttable-create), [`CrdtTable#update`](#crdttable-update) and [`CrdtTable#delete`](#crdttable-delete) add actions to the log, and the reducer applies them to the database. Their promises are resolved only after the tables were changed, so the next [`CrdtTable#select`](#crdttable-select) will already see the change. They are rejected if applying failed or if the database was stopped before it.

Until the action is applied, it is kept in the log by the `applying-to-db` reason. Any tab can take the `key:apply` lock, apply the actions in batches, and remove the reason. Actions of the tab, which was closed in the middle of the work, will be applied on the next start.

Actions are the same as in [`syncMapTemplate`](#globals-syncmaptemplate) (`user/created`, `user/changed`, `user/deleted` from `@logux/actions`), so tables are compatible with existing Logux servers and can be mixed with `syncMapTemplate` stores on other clients. Arrays in [`CrdtTable#create`](#crdttable-create), [`CrdtTable#update`](#crdttable-update) and [`CrdtTable#delete`](#crdttable-delete) produce batch actions (with `records` or `ids` instead of `id`), which are applied to the database in a single query.

Each table has an extra `id` column and an `updatedAt_field` column for every field with the Logux Meta ID of its last change, to resolve edit conflicts with per-field last write wins strategy.

The schema version — serialized schemas and indexes of all tables — is kept in the `logux_crdt` table of the database itself and is copied to `localStorage` (or to [`CrdtDatabaseOptions#storage`](#crdtdatabaseoptions-storage)) to tell other tabs about the change. On any schema change all tables (including tables removed from the schema) are dropped and refilled by replaying actions from the log and from the `repeat()` callback.

While there are actions waiting to be applied to the database, the tab asks the user to confirm closing. It only saves the user from waiting for the next start: it guarantees nothing, since the browser can close the tab without asking, and the actions are not lost anyway.

```ts
import { openDb, sqlocalDriver } from '@nanostores/sql'
import {
  bigint, createCrdtDatabase, number, oneOf, optional, string
} from '@logux/client/db'

let db = openDb(sqlocalDriver('app.sqlite'))
let crdt = createCrdtDatabase(client, db, {
  async repeat() {
    return await fetchActionsSnapshot()
  }
})

crdt.on('migrating', done => {
  showLoader('Migrating database', done)
})
crdt.on('stop', () => {
  updateAppWarning.show()
})

showLoader('Loading data', crdt.ready)

let user = crdt.table(
  'user',
  {
    age: optional(number()),
    createdAt: bigint({ default: () => Date.now() }),
    email: string('COLLATE NOCASE'),
    isAdmin: number({ default: 0 }),
    name: string(),
    theme: oneOf(['dark', 'light'], { default: 'dark' })
  },
  [{ columns: ['email'], unique: true }, ['isAdmin', 'name']]
)

let id = await user.create({ email: 'ann@example.com', name: 'Ann' })
await user.update(id, { age: 30 })
let $admins = user.select`WHERE "isAdmin" = ${1} ORDER BY "name"`
await user.delete(id)
```

| Argument | Type                  | Description                                     |
| -------- | --------------------- | ----------------------------------------------- |
| `client` | `Client`              | Logux client.                                   |
| `db`     | `Database`            | SQL database from `@nanostores/sql` `openDb()`. |
| `opts` ? | `CrdtDatabaseOptions` | Database options and the source of old actions. |

Returns `CrdtDatabase`.

# `createCrdtTasks(crdt, opts?)`

Queue for the log writes of the database bookkeeping.

The `applied` listener runs inside the applying transaction, so it can not await a log write: the store will put the write in its own transaction, which waits for the applying one to commit. The migration replay opens its transactions the same way.

The queue solves both: the tasks are not awaited by the applier, they are serialized between themselves, so they do not overwrite each other’s changes, and they start only after [`CrdtDatabase#ready`](#crdtdatabase-ready).

```js
import { createCrdtTasks } from '@logux/client/db'

let tasks = createCrdtTasks(crdt)

crdt.on('applied', (tx, action, meta, won) => {
  tasks.add(async () => {
    await client.log.removeReason(
      won.map(cell => cell.join('/')),
      { olderThan: meta }
    )
  })
})
```

| Argument | Type                         | Description                                                             |
| -------- | ---------------------------- | ----------------------------------------------------------------------- |
| `crdt`   | `Pick<CrdtDatabase,"ready">` | CRDT database from [`createCrdtDatabase`](#globals-createcrdtdatabase). |
| `opts` ? | `CrdtTasksOptions`           | Queue options.                                                          |

Returns `CrdtTasks`. Task queue.

# `createReducer(client, name, version, callbacks)`

Create long-term persistent Logux actions reducer. It can be used to store Logux data in WASM sqlite or localStorage.

Only one browser tab will reduce the log.

The reducer’s version is removed on [`Client#clean`](#client-clean), since the value is not re-created from the log on the next start. Reducers with their own storage should subscribe to the `cleaning` event to remove the data itself.

```ts
import { createReducer } from '@logux/client'

let db = sqlite.openDatabase('database.sqlite')
createReducer(client, 'db', 10, {
  async clean() {
    await db.close()
    await sqlite.removeFile('database.sqlite')
  },
  async init() {
    db.query('CREATE TABLE users …')
  },
  migrating(done) {
    showLoader('Migrating data', done)
  },
  stop() {
    db.close()
    updateAppWarning.show()
  }
})
  .type('users/create', async action => {
    await db.query(`INSERT INTO users …`)
  })
```

| Argument    | Type                   | Description                                                |
| ----------- | ---------------------- | ---------------------------------------------------------- |
| `client`    | `Client`               | Logux client.                                              |
| `name`      | `string`               | The name of the reducer to use in the storage version key. |
| `version`   | `number`               | The current version to call migrations on new version.     |
| `callbacks` | `ReducerInitCallbacks` | The data migrations callbacks.                             |

Returns `Reducer`.

# `createStorageReducer(client, name, version, initialValue, callbacks)`

Create a reducer that reduces actions into a single value stored in `localStorage` (or in [`StorageCallbacks#storage`](#storagecallbacks-storage)). The value is loaded on first run and kept in sync across tabs via `storage` events.

The value is removed on [`Client#clean`](#client-clean) together with the log.

```ts
import { createStorageReducer } from '@logux/client'

let counter = createStorageReducer(client, 'counter', 1, 0, {
  decode: s => parseInt(s, 10),
  encode: v => String(v),
  repeat() {
    return client.log.each(action => action.type === 'inc')
  }
})
counter.type<{ type: 'inc' }>('inc', prev => prev + 1)
```

| Argument       | Type               |
| -------------- | ------------------ |
| `client`       | `Client`           |
| `name`         | `string`           |
| `version`      | `number`           |
| `initialValue` | `NoInfer`          |
| `callbacks`    | `StorageCallbacks` |

| Argument       | Type                           |
| -------------- | ------------------------------ |
| `client`       | `Client`                       |
| `name`         | `string`                       |
| `version`      | `number`                       |
| `initialValue` | `Value`                        |
| `callbacks`    | `StorageCallbacks & Convertor` |

Returns `StorageReducer`.

# `defineChangedCrdtTable(table)`

| Argument | Type                |
| -------- | ------------------- |
| `table`  | `AbstractCrdtTable` |

Returns `ActionCreator`.

# `defineChangedSyncMap(plural)`

| Argument | Type     |
| -------- | -------- |
| `plural` | `string` |

Returns `ActionCreator`.

# `defineChangeSyncMap(plural)`

| Argument | Type     |
| -------- | -------- |
| `plural` | `string` |

Returns `ActionCreator`.

# `defineCrdtTableActions(table)`

Returns created/changed/deleted action creators for a CRDT table. Column types are extracted from the table and put into the action `fields` types.

```js
import { defineCrdtTableActions } from '@logux/actions'

const user = crdt.table('user', { name: string(), age: optional(number()) })

const [
  createdUserAction,
  changedUserAction,
  deletedUserAction
] = defineCrdtTableActions(user)
```

| Argument | Type                |
| -------- | ------------------- |
| `table`  | `AbstractCrdtTable` |

Returns `[ActionCreator, ActionCreator, ActionCreator]`.

# `defineCreatedCrdtTable(table)`

| Argument | Type                |
| -------- | ------------------- |
| `table`  | `AbstractCrdtTable` |

Returns `ActionCreator`.

# `defineCreatedSyncMap(plural)`

| Argument | Type     |
| -------- | -------- |
| `plural` | `string` |

Returns `ActionCreator`.

# `defineCreateSyncMap(plural)`

| Argument | Type     |
| -------- | -------- |
| `plural` | `string` |

Returns `ActionCreator`.

# `defineDeletedCrdtTable(table)`

| Argument | Type                |
| -------- | ------------------- |
| `table`  | `AbstractCrdtTable` |

Returns `ActionCreator`.

# `defineDeletedSyncMap(plural)`

| Argument | Type     |
| -------- | -------- |
| `plural` | `string` |

Returns `ActionCreator`.

# `defineDeleteSyncMap(plural)`

| Argument | Type     |
| -------- | -------- |
| `plural` | `string` |

Returns `ActionCreator`.

# `defineSyncMapActions(plural)`

Returns actions for CRDT Map.

```js
import { defineSyncMapActions } from '@logux/actions'

const [
  createUserAction,
  changeUserAction,
  deleteUserAction,
  createdUserAction,
  changedUserAction,
  deletedUserAction
] = defineSyncMapActions('users')
```

| Argument | Type     |
| -------- | -------- |
| `plural` | `string` |

Returns `[ActionCreator, ActionCreator, ActionCreator]`.

# `eachStoreCheck(test)`

Pass all common tests for Logux store to callback.

```js
import { eachStoreCheck } from '@logux/core'

eachStoreCheck((desc, creator) => {
  it(desc, creator(() => new CustomStore()))
})
```

| Argument | Type                                                                                | Description                                      |
| -------- | ----------------------------------------------------------------------------------- | ------------------------------------------------ |
| `test`   | `(name: string, testCreator: (storeCreator: () => LogStore) => () => void) => void` | Callback to create tests in your test framework. |

# `ensureLoaded(value)`

Change store’s value type to value with `isLoaded: false`.

If store is still loading, this function will trow an error.

Use it for tests written on TypeScript.

```js
import { ensureLoaded } from '@logux/client'

expect(ensureLoaded($currentUser)).toEqual({ id: 1, name: 'User' })
```

| Argument | Type           | Description    |
| -------- | -------------- | -------------- |
| `value`  | `SyncMapValue` | Store’s value. |

| Argument | Type          | Description    |
| -------- | ------------- | -------------- |
| `value`  | `FilterValue` | Store’s value. |

Returns `LoadedSyncMapValue`.

# `ensureLoadedStore(store)`

| Argument | Type    |
| -------- | ------- |
| `store`  | `Store` |

Returns `any`.

## `fake-indexeddb`

## `fake-indexeddb/lib/FDBKeyRange`

# `fromCompat(str)`

Decode number from `-0-9A-Z_a-z` alphabet.

```js
fromCompat('OzcVoWD') //=> 1786312345678
```

| Argument | Type     | Description     |
| -------- | -------- | --------------- |
| `str`    | `string` | Encoded number. |

Returns `number`. Decoded number.

# `getRandomSpaces()`

Returns `string`.

# `idToTime(id)`

Decode `meta.time` from action ID.

```js
idToTime('OzcVoWD client:1') //=> 1786312345678
```

| Argument | Type     | Description                 |
| -------- | -------- | --------------------------- |
| `id`     | `string` | Action ID or its time part. |

Returns `number`. Milliseconds since UNIX epoch.

# `isSameClient(id, clientId)`

Faster alternative for `parseId(meta.id).clientId === clientId` check.

It doesn’t create any object or string during the check.

```js
import { isSameClient } from '@logux/core'
if (isSameClient(meta.id, ctx.clientId)) {
  // Action was created by this client
}
```

| Argument   | Type     | Description               |
| ---------- | -------- | ------------------------- |
| `id`       | `string` | Action or Node ID         |
| `clientId` | `string` | Client ID to compare with |

Returns `boolean`.

# `loadValue(store)`

Return store’s value if store is loaded or wait until store will be loaded and return its value.

Returns `undefined` on 404.

```js
import { loadValue } from '@logux/client'

let user = loadValue($currentUser)
```

| Argument | Type    | Description    |
| -------- | ------- | -------------- |
| `store`  | `Store` | Store to load. |

| Argument | Type    | Description    |
| -------- | ------- | -------------- |
| `store`  | `Store` | Store to load. |

Returns `Promise<any>`.

## `loguxProcessed`

Returns `logux/processed` action.

Type: `ActionCreator`.

## `loguxSubscribe`

Returns `logux/subscribe` action.

Type: `ActionCreator`.

## `loguxSubscribed`

Returns `logux/subscribed` action.

Type: `ActionCreator`.

# `loguxUndo(fields)`

Returns `logux/undo` action.

| Argument | Type                                                     |
| -------- | -------------------------------------------------------- |
| `fields` | `{ action: RevertedAction, id: string, reason: Reason }` |

Returns `LoguxUndoAction`.

## `loguxUnsubscribe`

Returns `logux/unsubscribe` action.

Type: `ActionCreator`.

# `number(opts?)`

`DOUBLE PRECISION` column with `number` value.

| Argument | Type                                          | Description                                    |
| -------- | --------------------------------------------- | ---------------------------------------------- |
| `opts` ? | `string \| Omit<CrdtColumnOptions,"default">` | Extra column definition SQL or column options. |

| Argument | Type                                       | Description                                    |
| -------- | ------------------------------------------ | ---------------------------------------------- |
| `opts`   | `{ default: NoInfer } & CrdtColumnOptions` | Extra column definition SQL or column options. |

Returns `{ type: "DOUBLE PRECISION" } & CrdtColumn`.

# `oneOf(values, opts?)`

Enum column with union of string values. Stored as `TEXT` with `CHECK` constraint.

```ts
import { oneOf } from '@logux/client/db'

let schema = {
  role: oneOf(['admin', 'guest', 'user'], { default: 'user' }),
  theme: oneOf(['dark', 'light'])
}
```

| Argument | Type                                          | Description                                    |
| -------- | --------------------------------------------- | ---------------------------------------------- |
| `values` | `Values`                                      | Allowed string values.                         |
| `opts` ? | `string \| Omit<CrdtColumnOptions,"default">` | Extra column definition SQL or column options. |

| Argument | Type                                                                      | Description                                    |
| -------- | ------------------------------------------------------------------------- | ---------------------------------------------- |
| `values` | `Values`                                                                  | Allowed string values.                         |
| `opts`   | `{ default: () => Values[number] \| Values[number] } & CrdtColumnOptions` | Extra column definition SQL or column options. |

Returns `{ type: "TEXT" } & CrdtColumn`.

# `optional(column)`

Mark column as optional. The field can be omitted or set to `null` in [`CrdtTable#create`](#crdttable-create), can be set to `null` in [`CrdtTable#update`](#crdttable-update) to clear the value, and is `null` (SQL `NULL`) in rows when missing.

```ts
import { number, optional } from '@logux/client'

let schema = {
  age: optional(number())
}
```

| Argument | Type     | Description                |
| -------- | -------- | -------------------------- |
| `column` | `Column` | Column definition to wrap. |

Returns `{ type: Column["type"] } & CrdtColumn`.

# `parseCrdtAction(action, crdt)`

Read the rows and fields of a table action, whatever batch shape it uses (`records`, `ids` or a single `id`).

Policies on the `applied` event and on the log events need it to know, which rows the action is about, before it was applied.

```ts
import { parseCrdtAction } from '@logux/client/db'

client.on('preadd', (action, meta) => {
  let parsed = parseCrdtAction(action, crdt)
  if (!parsed) return
  for (let [id, fields] of parsed.rows) {
    for (let field of fields) {
      meta.reasons.push(`${parsed.plural}/${id}/${field}`)
    }
  }
})
```

The schemas can be written by hand, so actions can be parsed without the database (in tests or on the server):

```ts
parseCrdtAction(action, { tables: { feeds: feedsSchema } })
```

| Argument | Type                          | Description                                                                                                                                                               |
| -------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `action` | `Action`                      | Any action from the log.                                                                                                                                                  |
| `crdt`   | `Pick<CrdtDatabase,"tables">` | Database from [`createCrdtDatabase`](#globals-createcrdtdatabase) or any object with [`CrdtDatabase#tables`](#crdtdatabase-tables) to parse actions without the database. |

Returns `false | CrdtParsedAction`. Parsed action or `false` if it is not an action of these tables.

# `parseCrdtRows(action)`

Split a table action into rows, whatever batch shape it uses (`records`, `ids` or a single `id`). The single place, which knows the shapes: re-implementing them in the app will break on the next shape added here.

The fields are the action’s own values without any check, so the caller must filter them by the table schema: the fields of a `records` row also contain `id`, and a `plural/deleted` action has no fields at all. Use [`parseCrdtAction`](#globals-parsecrdtaction) to get the field names of the schema instead.

The action type is not checked, so call it only for actions of [`CrdtDatabase#table`](#crdtdatabase-table).

```ts
import { parseCrdtRows } from '@logux/client/db'

for (let [id, fields] of parseCrdtRows(action)) {
  await copyToBackup(id, fields)
}
```

| Argument | Type     | Description   |
| -------- | -------- | ------------- |
| `action` | `Action` | Table action. |

Returns `[id: string, fields: object][]`. Row IDs with the fields, which the action writes to them.

# `parseCrdtType(type, crdt)`

Read the table and the verb of the action type, if the action belongs to one of the tables.

Use it when the rows are not necessary, for instance, to find the actions of deleted rows in the log.

```ts
import { parseCrdtType } from '@logux/client/db'

client.log.on('clean', action => {
  let parsed = parseCrdtType(action.type, crdt)
  if (parsed?.verb === 'deleted') cleaned.add(parsed.plural)
})
```

| Argument | Type                          | Description                                                                                                                                                               |
| -------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`   | `string`                      | Type of any action from the log.                                                                                                                                          |
| `crdt`   | `Pick<CrdtDatabase,"tables">` | Database from [`createCrdtDatabase`](#globals-createcrdtdatabase) or any object with [`CrdtDatabase#tables`](#crdtdatabase-tables) to parse actions without the database. |

Returns `false | CrdtParsedType`. Table and verb or `false` if it is not an action of these tables.

# `replaceWithShadow(client, meta)`

Add `shadow` action to the log. It is useful for client to clean server from encrypted `zero` actions.

It replaces materialized action in the log, keeping its ID, reasons and indexes, but dropping the body.

By tracking `shadow` reasons you can detect when you can ask server to remove original action.

```js
import { shadow, zeroClean } from '@logux/actions'
import { replaceWithShadow } from '@logux/client'

client.log.type('logux/processed', async processed => {
  let [action, meta] = await client.log.byId(processed.id)
  if (action) await replaceWithShadow(client, meta)
})

client.log.on('clean', action => {
  if (shadow.match(action)) {
    client.log.add(zeroClean({ ids: [action.id] }), { sync: true })
  }
})
```

| Argument | Type         | Description                  |
| -------- | ------------ | ---------------------------- |
| `client` | `Client`     | Logux Client.                |
| `meta`   | `ClientMeta` | Meta of the original action. |

Returns `Promise<ClientMeta>`. Meta of the added shadow action.

## `shadow`

Returns `shadow` action. It is useful for client to clean server from encrypted [`zero`](#globals-zero) actions.

It replaces materialized action in the log, keeping its ID, reasons and indexes, but dropping the body.

By tracking `shadow` reasons you can detect when you can ask server to remove original action.

Type: `ActionCreator`.

# `sortedToMeta(sorted)`

Convert string created by `toSorted()` back to metadata.

```js
sortedToMeta('------Ec test ------Ec') //=> { id: 'Ec test', time: 1000 }
```

| Argument | Type     | Description                     |
| -------- | -------- | ------------------------------- |
| `sorted` | `string` | String created by `toSorted()`. |

Returns `MetaTime`. Action’s metadata with `id` and `time` keys.

# `status(client, callback, options?)`

Low-level function to show Logux synchronization status with your custom UI. It is used in [`badge`](#globals-badge) widget.

```js
import { status } from '@logux/client'
status(client, current => {
  updateUI(current)
})
```

| Argument    | Type             | Description               |
| ----------- | ---------------- | ------------------------- |
| `client`    | `Client`         | Observed Client instance. |
| `callback`  | `StatusListener` |                           |
| `options` ? | `StatusOptions`  |                           |

Returns `() => void`. Unbind listener.

# `string(opts?)`

`TEXT` column with `string` value.

```ts
import { string } from '@logux/client'

let schema = {
  email: string('COLLATE NOCASE'),
  name: string(),
  theme: string<'dark' | 'light'>({ default: 'dark' })
}
```

| Argument | Type                                          | Description                                    |
| -------- | --------------------------------------------- | ---------------------------------------------- |
| `opts` ? | `string \| Omit<CrdtColumnOptions,"default">` | Extra column definition SQL or column options. |

| Argument | Type                                       | Description                                    |
| -------- | ------------------------------------------ | ---------------------------------------------- |
| `opts`   | `{ default: NoInfer } & CrdtColumnOptions` | Extra column definition SQL or column options. |

Returns `{ type: "TEXT" } & CrdtColumn`.

# `toCompat(number)`

Encode number to `-0-9A-Z_a-z` alphabet.

Chars are in ASCII order, so strings of the same length have the same order as encoded numbers.

```js
toCompat(64) //=> "0-"
```

| Argument | Type     | Description       |
| -------- | -------- | ----------------- |
| `number` | `number` | Number to encode. |

Returns `string`. Encoded number.

# `toSorted(meta)`

Convert metadata to a string with the same order as `isFirstOlder()`.

Numbers are padded, so simple string sorting (for instance, in a database column) will return actions in the log order.

```js
db.insert({ action, sorted: toSorted(meta) })
// SELECT * FROM actions ORDER BY sorted
```

| Argument | Type       | Description        |
| -------- | ---------- | ------------------ |
| `meta`   | `MetaTime` | Action’s metadata. |

Returns `string`. String to sort actions.

# `track(client, id)`

Track for `logux/processed` or `logux/undo` answer from server for the cases when `Client#sync` can’t be used.

```js
client.type('pay', (action, meta) => {
  track(client, id).then(() => {
    console.log('paid')
  }).catch(() => {
    console.log('unpaid')
  })
})
```

| Argument | Type  | Description   |
| -------- | ----- | ------------- |
| `client` | `any` | Logux Client. |
| `id`     | `ID`  | Action ID.    |

Returns `Promise`. Promise when action was proccessed.

# `withMeta(row)`

Add empty conflict resolution data to a row built in tests, so it could be compared with rows from [`CrdtTable#select`](#crdttable-select).

```ts
expect(await loadList(user.select())).toEqual([
  withMeta<UserValue>({ id: 'U1', name: 'Ann' })
])
```

| Argument | Type          | Description                            |
| -------- | ------------- | -------------------------------------- |
| `row`    | `WithoutMeta` | Row without `updatedAt_field` columns. |

Returns `Value`. Row with `null` in `updatedAt_field` column of every field.

# `withoutMeta(rows)`

Remove conflict resolution data, which is local and should not be in the backup or in tests expectations.

```ts
expect(withoutMeta(await loadList(user.select()))).toEqual([
  { id: 'U1', name: 'Ann' }
])
```

| Argument | Type      | Description                                        |
| -------- | --------- | -------------------------------------------------- |
| `rows`   | `Value[]` | Rows from [`CrdtTable#select`](#crdttable-select). |

Returns `WithoutMeta[]`. Rows without `updatedAt_field` columns.

## `zero`

Returns `0` action.

Type: `ActionCreator`.

## `zeroClean`

Returns `0/clean` action.

Type: `ActionCreator`.

## `zeroPacker`

Packer to `0` action to binary format to use in SQL stores.

Type: `ActionPacker`.
