# Node API

# ChannelContext

Extends [Context](#context).

Subscription context.

```js
server.channel('user/:id', {
  access (ctx, action, meta) {
    return ctx.params.id === ctx.userId
  }
})
```

## `ChannelContext#clientId`

Unique persistence client ID.

```js
server.clientIds.get(node.clientId)
```

Type: `string`.

## `ChannelContext#data`

Open structure to save some data between different steps of processing.

```js
server.type('RENAME', {
  access (ctx, action, meta) {
    ctx.data.user = findUser(ctx.userId)
    return ctx.data.user.hasAccess(action.projectId)
  }
  process (ctx, action, meta) {
    return ctx.data.user.rename(action.projectId, action.name)
  }
})
```

Type: `Data`.

## `ChannelContext#headers`

Client’s headers.

```js
ctx.sendBack({
  type: 'error',
  message: I18n[ctx.headers.locale || 'en'].error
})
```

Type: `Headers`.

## `ChannelContext#isServer`

Was action created by Logux server.

```js
access: (ctx, action, meta) => ctx.isServer
```

Type: `boolean`.

## `ChannelContext#nodeId`

Unique node ID.

```js
server.nodeIds.get(node.nodeId)
```

Type: `string`.

## `ChannelContext#params`

Parsed variable parts of channel pattern.

```js
server.channel('user/:id', {
  access (ctx, action, meta) {
    action.channel //=> user/10
    ctx.params //=> { id: '10' }
  }
})
server.channel(/post/(\d+)/, {
  access (ctx, action, meta) {
    action.channel //=> post/10
    ctx.params //=> ['post/10', '10']
  }
})
```

Type: `ChannelParams`.

## `ChannelContext#server`

Logux server

Type: `Server`.

## `ChannelContext#subprotocol`

Action creator application subprotocol version.

Type: `number`.

## `ChannelContext#userId`

User ID taken node ID.

```js
async access (ctx, action, meta) {
  const user = await db.getUser(ctx.userId)
  return user.admin
}
```

Type: `string`.

## `ChannelContext#drain()`

Wait until the client will confirm all actions, which were sent to it.

Use it to send a long history without loading it all into the memory: the client’s speed will limit how fast you read the database.

```js
while (await ctx.drain()) {
  let page = await cursor.next(100)
  if (!page.length) break
  ctx.sendBack(page.map(i => i.action))
}
```

Returns `Promise<boolean>`. Promise with `false` if the client was disconnected.

## `ChannelContext#sendBack(action, meta?)`

Send action back to the client.

```js
ctx.sendBack({ type: 'login/success', token })
```

An array of actions will be sent in a single message. Use it to send a big history page by page instead of a message per action.

```js
ctx.sendBack(page.map(i => i.action))
```

Every action in the array can have own meta as `[action, meta]`.

Action will not be processed by server’s callbacks from `Server#type`.

| Argument | Type                                                              | Description                         |
| -------- | ----------------------------------------------------------------- | ----------------------------------- |
| `action` | `TypeAction \| TypeAction \| [TypeAction, Partial<ServerMeta>][]` | The action or the array of actions. |
| `meta` ? | `Partial<ServerMeta>`                                             | Action’s meta.                      |

Returns `Promise`. Promise until action was added to the server log.

# Context

Extends [ConnectContext](#connectcontext).

Action context.

```
```

## `Context#clientId`

Unique persistence client ID.

```js
server.clientIds.get(node.clientId)
```

Type: `string`.

## `Context#data`

Open structure to save some data between different steps of processing.

```js
server.type('RENAME', {
  access (ctx, action, meta) {
    ctx.data.user = findUser(ctx.userId)
    return ctx.data.user.hasAccess(action.projectId)
  }
  process (ctx, action, meta) {
    return ctx.data.user.rename(action.projectId, action.name)
  }
})
```

Type: `Data`.

## `Context#headers`

Client’s headers.

```js
ctx.sendBack({
  type: 'error',
  message: I18n[ctx.headers.locale || 'en'].error
})
```

Type: `Headers`.

## `Context#isServer`

Was action created by Logux server.

```js
access: (ctx, action, meta) => ctx.isServer
```

Type: `boolean`.

## `Context#nodeId`

Unique node ID.

```js
server.nodeIds.get(node.nodeId)
```

Type: `string`.

## `Context#server`

Logux server

Type: `Server`.

## `Context#subprotocol`

Action creator application subprotocol version.

Type: `number`.

## `Context#userId`

User ID taken node ID.

```js
async access (ctx, action, meta) {
  const user = await db.getUser(ctx.userId)
  return user.admin
}
```

Type: `string`.

## `Context#drain()`

Wait until the client will confirm all actions, which were sent to it.

Use it to send a long history without loading it all into the memory: the client’s speed will limit how fast you read the database.

```js
while (await ctx.drain()) {
  let page = await cursor.next(100)
  if (!page.length) break
  ctx.sendBack(page.map(i => i.action))
}
```

Returns `Promise<boolean>`. Promise with `false` if the client was disconnected.

## `Context#sendBack(action, meta?)`

Send action back to the client.

```js
ctx.sendBack({ type: 'login/success', token })
```

An array of actions will be sent in a single message. Use it to send a big history page by page instead of a message per action.

```js
ctx.sendBack(page.map(i => i.action))
```

Every action in the array can have own meta as `[action, meta]`.

Action will not be processed by server’s callbacks from `Server#type`.

| Argument | Type                                                              | Description                         |
| -------- | ----------------------------------------------------------------- | ----------------------------------- |
| `action` | `TypeAction \| TypeAction \| [TypeAction, Partial<ServerMeta>][]` | The action or the array of actions. |
| `meta` ? | `Partial<ServerMeta>`                                             | Action’s meta.                      |

Returns `Promise`. Promise until action was added to the server log.

# ResponseError

Extends `Error`.

Throwing this error in `accessAndProcess` or `accessAndLoad` will deny the action.

| Parameter    | Type     |
| ------------ | -------- |
| `statusCode` | `number` |
| `url`        | `string` |

## `ResponseError#name`

Type: `"ResponseError"`.

## `ResponseError#statusCode`

Type: `number`.

# Server

Extends [BaseServer](#baseserver).

End-user API to create Logux server.

```js
import { Server } from '@logux/server'

const env = process.env.NODE_ENV || 'development'
const envOptions = {}
if (env === 'production') {
  envOptions.cert = 'cert.pem'
  envOptions.key = 'key.pem'
}

const server = new Server(Object.assign({
  subprotocol: 1,
  minSubprotocol: 1,
  root: import.meta.dirname
}, envOptions))

server.listen()
```

| Parameter | Type            | Description     |
| --------- | --------------- | --------------- |
| `opts`    | `ServerOptions` | Server options. |

## `Server.loadOptions(process, defaults)`

Load options from command-line arguments and/or environment.

```js
const server = new Server(Server.loadOptions(process, {
  minSubprotocol: 1,
  subprotocol: 1,
  root: import.meta.dirname,
  port: 31337
}))
```

| Argument   | Type            | Description                                                                     |
| ---------- | --------------- | ------------------------------------------------------------------------------- |
| `process`  | `Process`       | Current process object.                                                         |
| `defaults` | `ServerOptions` | Default server options. Arguments and environment variables will override them. |

Returns `ServerOptions`. Parsed options object.

## `Server#clientIds`

Connected client by client ID.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `Map<string,ServerClient>`.

## `Server#connected`

Connected clients.

```js
for (let client of server.connected.values()) {
  console.log(client.remoteAddress)
}
```

Type: `Map<string,ServerClient>`.

## `Server#env`

Production or development mode.

```js
if (server.env === 'development') {
  logDebugData()
}
```

Type: `"development" | "production"`.

## `Server#log`

Server actions log.

```js
server.log.each(finder)
```

Type: `Log`.

## `Server#logger`

Console for custom log records. It uses `pino` API.

```js
server.on('connected', client => {
  server.logger.info(
    { domain: client.httpHeaders.domain },
    'Client domain'
  )
})
```

Type: `{ debug: LogFn, error: LogFn, fatal: LogFn, info: LogFn, warn: LogFn }`.

## `Server#nodeId`

Server unique ID.

```js
console.log('Error was raised on ' + server.nodeId)
```

Type: `string`.

## `Server#nodeIds`

Connected client by node ID.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `Map<string,ServerClient>`.

## `Server#options`

Server options.

```js
console.log('Server options', server.options.subprotocol)
```

Type: `ServerOptions`.

## `Server#subscribers`

Clients subscribed to some channel.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `{ }`.

## `Server#userIds`

Connected client by user ID.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `Map<string,ServerClient[]>`.

## `Server#addClient(connection)`

Add new client for server. You should call this method manually mostly for test purposes.

```js
server.addClient(test.right)
```

| Argument     | Type               | Description                 |
| ------------ | ------------------ | --------------------------- |
| `connection` | `ServerConnection` | Logux connection to client. |

Returns `number`. Client ID.

## `Server#auth(authenticator)`

Set authenticate function. It will receive client credentials and node ID. It should return a Promise with `true` or `false`.

```js
server.auth(async ({ userId, cookie }) => {
  const user = await findUserByToken(cookie.token)
  return !!user && userId === user.id
})
```

| Argument        | Type                  | Description                  |
| --------------- | --------------------- | ---------------------------- |
| `authenticator` | `ServerAuthenticator` | The authentication callback. |

## `Server#autoloadModules(files?)`

Load module creators and apply to the server. By default, it will load files from `modules/*`.

```js
await server.autoloadModules()
```

| Argument  | Type                 | Description               |
| --------- | -------------------- | ------------------------- |
| `files` ? | `string \| string[]` | Pattern for module files. |

Returns `Promise`.

## `Server#channel(pattern, callbacks, options?)`

Define the channel.

```js
server.channel('user/:id', {
  access (ctx, action, meta) {
    return ctx.params.id === ctx.userId
  }
  filter (ctx, action, meta) {
    return (otherCtx, otherAction, otherMeta) => {
      return !action.hidden
    }
  }
  async load (ctx, action, meta) {
    const user = await db.loadUser(ctx.params.id)
    ctx.sendBack({ type: 'USER_NAME', name: user.name })
  }
})
```

| Argument    | Type               | Description                           |
| ----------- | ------------------ | ------------------------------------- |
| `pattern`   | `string`           | Pattern for channel name.             |
| `callbacks` | `ChannelCallbacks` | Callback during subscription process. |
| `options` ? | `ChannelOptions`   | Additional options                    |

| Argument    | Type               | Description                           |
| ----------- | ------------------ | ------------------------------------- |
| `pattern`   | `RegExp`           | Regular expression for channel name.  |
| `callbacks` | `ChannelCallbacks` | Callback during subscription process. |
| `options` ? | `ChannelOptions`   | Additional options                    |

## `Server#debugError(error)`

Send runtime error stacktrace to all clients.

```js
process.on('uncaughtException', e => {
  server.debugError(e)
})
```

| Argument | Type    | Description             |
| -------- | ------- | ----------------------- |
| `error`  | `Error` | Runtime error instance. |

## `Server#destroy()`

Stop server and unbind all listeners.

```js
afterEach(() => {
  testServer.destroy()
})
```

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

## `Server#drain(clientId)`

Wait until the client will confirm all actions, which were sent to it.

Use it to send a long history without loading it all into the memory: the client’s speed will limit how fast you read the database.

```js
while (await server.drain(clientId)) {
  let page = await cursor.next(100)
  if (!page.length) break
  server.log.add(page.map(i => [i.action, { clients: [clientId] }]))
}
```

| Argument   | Type     | Description |
| ---------- | -------- | ----------- |
| `clientId` | `string` | Client ID.  |

Returns `Promise<boolean>`. Promise with `false` if the client was disconnected.

## `Server#handleClient(ws, req)`

Handle WebSocket connection explicitly

This is a low-level method allowing to integrate Logux server with an existing server

```js
fastify.get('/', { websocket: true }, (socket, req) => {
  loguxServer.handleClient(socket, req)
})
```

| Argument | Type              |
| -------- | ----------------- |
| `ws`     | `WebSocket`       |
| `req`    | `IncomingMessage` |

## `Server#http(method, url, listener)`

Add non-WebSocket HTTP request processor.

```js
server.http('GET', '/auth', (req, res) => {
  let token = signIn(req)
  if (token) {
    res.setHeader('Set-Cookie', `token=${token}; Secure; HttpOnly`)
    res.end()
  } else {
    res.statusCode = 400
    res.end('Wrong user or password')
  }
})
```

| Argument   | Type                                                             |
| ---------- | ---------------------------------------------------------------- |
| `method`   | `string`                                                         |
| `url`      | `string`                                                         |
| `listener` | `(req: IncomingMessage, res: ServerResponse) => void \| Promise` |

| Argument   | Type                                                                         |
| ---------- | ---------------------------------------------------------------------------- |
| `listener` | `(req: IncomingMessage, res: ServerResponse) => boolean \| Promise<boolean>` |

## `Server#listen()`

Start WebSocket server and listen for clients.

Returns `Promise`. When the server has been bound.

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

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

| Argument   | Type                                                       | Description            |
| ---------- | ---------------------------------------------------------- | ---------------------- |
| `event`    | `"subscribing"`                                            | The event name.        |
| `listener` | `(action: LoguxSubscribeAction, meta: ServerMeta) => void` | Subscription listener. |

| Argument   | Type                                                                      | Description          |
| ---------- | ------------------------------------------------------------------------- | -------------------- |
| `event`    | `"processed"`                                                             | The event name.      |
| `listener` | `(action: Action, meta: ServerMeta, latencyMilliseconds: number) => void` | Processing listener. |

| Argument   | Type                                         | Description      |
| ---------- | -------------------------------------------- | ---------------- |
| `event`    | `"add" \| "clean"`                           | The event name.  |
| `listener` | `(action: Action, meta: ServerMeta) => void` | Action listener. |

| Argument   | Type                             | Description      |
| ---------- | -------------------------------- | ---------------- |
| `event`    | `"connected" \| "disconnected"`  | The event name.  |
| `listener` | `(client: ServerClient) => void` | Client listener. |

| Argument   | Type                       | Description            |
| ---------- | -------------------------- | ---------------------- |
| `event`    | `"clientError" \| "fatal"` | The event name.        |
| `listener` | `(err: Error) => void`     | The listener function. |

| Argument   | Type                                                     | Description     |
| ---------- | -------------------------------------------------------- | --------------- |
| `event`    | `"error"`                                                | The event name. |
| `listener` | `(err: Error, action: Action, meta: ServerMeta) => void` | Error listener. |

| Argument   | Type                                                          | Description      |
| ---------- | ------------------------------------------------------------- | ---------------- |
| `event`    | `"authenticated" \| "unauthenticated"`                        | The event name.  |
| `listener` | `(client: ServerClient, latencyMilliseconds: number) => void` | Client listener. |

| Argument   | Type                                         | Description      |
| ---------- | -------------------------------------------- | ---------------- |
| `event`    | `"preadd"`                                   | The event name.  |
| `listener` | `(action: Action, meta: ServerMeta) => void` | Action listener. |

| Argument   | Type                                                                                    | Description            |
| ---------- | --------------------------------------------------------------------------------------- | ---------------------- |
| `event`    | `"subscribed"`                                                                          | The event name.        |
| `listener` | `(action: LoguxSubscribeAction, meta: ServerMeta, latencyMilliseconds: number) => void` | Subscription listener. |

| Argument   | Type                                                                               | Description            |
| ---------- | ---------------------------------------------------------------------------------- | ---------------------- |
| `event`    | `"unsubscribed"`                                                                   | The event name.        |
| `listener` | `(action: LoguxUnsubscribeAction, meta: ServerMeta, clientNodeId: string) => void` | Subscription listener. |

| Argument   | Type       | Description      |
| ---------- | ---------- | ---------------- |
| `event`    | `"report"` | The event name.  |
| `listener` | `Reporter` | Report listener. |

Returns `Unsubscribe`.

## `Server#otherChannel(callbacks)`

Set callbacks for unknown channel subscription.

```js
server.otherChannel({
  async access (ctx, action, meta) {
    const res = await phpBackend.checkChannel(ctx.params[0], ctx.userId)
    if (res.code === 404) {
      this.wrongChannel(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
})
```

| Argument    | Type               | Description                           |
| ----------- | ------------------ | ------------------------------------- |
| `callbacks` | `ChannelCallbacks` | Callback during subscription process. |

## `Server#otherType(callbacks)`

Define callbacks for actions, which type was not defined by any [`Server#type`](#server-type). Useful for proxy or some hacks.

Without this settings, server will call [`Server#unknownType`](#server-unknowntype) on unknown type.

```js
server.otherType(
  async access (ctx, action, meta) {
    const response = await phpBackend.checkByHTTP(action, meta)
    if (response.code === 404) {
      this.unknownType(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
  async process (ctx, action, meta) {
    return await phpBackend.sendHTTP(action, meta)
  }
})
```

| Argument    | Type              | Description                           |
| ----------- | ----------------- | ------------------------------------- |
| `callbacks` | `ActionCallbacks` | Callbacks for actions with this type. |

## `Server#process(action, meta?)`

Add new action to the server and return the Promise until it will be resend to clients and processed.

| Argument | Type                  | Description                       |
| -------- | --------------------- | --------------------------------- |
| `action` | `TypeAction`          | New action to resend and process. |
| `meta` ? | `Partial<ServerMeta>` | Action’s meta.                    |

Returns `Promise<ServerMeta>`. Promise until new action will be resend to clients and processed.

## `Server#sendAction(action, meta)`

Send action, received by other server, to all clients of current server. This method is for multi-server configuration only.

```js
server.on('add', (action, meta) => {
  if (meta.server === server.nodeId) {
    sendToOtherServers(action, meta)
  }
})
onReceivingFromOtherServer((action, meta) => {
  server.sendAction(action, meta)
})
```

| Argument | Type         | Description        |
| -------- | ------------ | ------------------ |
| `action` | `Action`     | New action.        |
| `meta`   | `ServerMeta` | Action’s metadata. |

Returns `void | Promise`.

## `Server#sendOnConnect(loader)`

Change a way how server loads actions history for the client.

```js
server.sendOnConnect(async (ctx, lastSynced) => {
  return db.loadActions({ user: ctx.userId, after: lastSynced })
})
```

Actions should be returned from the newest one to the oldest one. The list will be split into messages by the `syncBatch` option, but it is loaded into the memory as a whole. For a big history send it by pages with `Context#sendBack()` and `Context#drain()` instead.

Set `meta.added` to let the client ask only for newer actions after the reconnect: the biggest `added` will be sent as the sync position.

| Argument | Type            | Description                                    |
| -------- | --------------- | ---------------------------------------------- |
| `loader` | `ConnectLoader` | Callback which loads list of actions and meta. |

## `Server#subscribe(nodeId, channel)`

Send `logux/subscribed` if client was not already subscribed.

```js
server.subscribe(ctx.nodeId, `users/${loaded}`)
```

| Argument  | Type     | Description   |
| --------- | -------- | ------------- |
| `nodeId`  | `string` | Node ID.      |
| `channel` | `string` | Channel name. |

## `Server#type(actionCreator, callbacks, options?)`

| Argument        | Type              | Description                              |
| --------------- | ----------------- | ---------------------------------------- |
| `actionCreator` | `Creator`         | Action creator function.                 |
| `callbacks`     | `ActionCallbacks` | Callbacks for action created by creator. |
| `options` ?     | `TypeOptions`     | Additional options                       |

| Argument    | Type                           | Description                                                  |
| ----------- | ------------------------------ | ------------------------------------------------------------ |
| `name`      | `RegExp \| TypeAction["type"]` | The action’s type or action’s type matching rule as RegExp.. |
| `callbacks` | `ActionCallbacks`              | Callbacks for actions with this type.                        |
| `options` ? | `TypeOptions`                  | Additional options                                           |

## `Server#undo(action, meta, reason?, extra?)`

Undo action from client.

```js
if (couldNotFixConflict(action, meta)) {
  server.undo(action, meta)
}
```

| Argument   | Type         | Description                                     |
| ---------- | ------------ | ----------------------------------------------- |
| `action`   | `Action`     | The original action to undo.                    |
| `meta`     | `ServerMeta` | The action’s metadata.                          |
| `reason` ? | `string`     | Optional code for reason. Default is `'error'`. |
| `extra` ?  | `object`     | Extra fields to `logux/undo` action.            |

Returns `Promise`. When action was saved to the log.

## `Server#unknownType(action, meta)`

If you receive action with unknown type, this method will mark this action with `error` status and undo it on the clients.

If you didn’t set [`Server#otherType`](#server-othertype), Logux will call it automatically.

```js
server.otherType({
  access (ctx, action, meta) {
    if (action.type.startsWith('myapp/')) {
      return proxy.access(action, meta)
    } else {
      server.unknownType(action, meta)
    }
  }
})
```

| Argument | Type         | Description                   |
| -------- | ------------ | ----------------------------- |
| `action` | `Action`     | The action with unknown type. |
| `meta`   | `ServerMeta` | Action’s metadata.            |

## `Server#wrongChannel(action, meta)`

Report that client try to subscribe for unknown channel.

Logux call it automatically, if you will not set [`Server#otherChannel`](#server-otherchannel).

```js
server.otherChannel({
  async access (ctx, action, meta) {
    const res = phpBackend.checkChannel(params[0], ctx.userId)
    if (res.code === 404) {
      this.wrongChannel(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
})
```

| Argument | Type                   | Description           |
| -------- | ---------------------- | --------------------- |
| `action` | `LoguxSubscribeAction` | The subscribe action. |
| `meta`   | `ServerMeta`           | Action’s metadata.    |

# ServerClient

Logux client connected to server.

```js
const client = server.connected.get(0)
```

## `ServerClient#app`

Server, which received client.

Type: `BaseServer`.

## `ServerClient#clientId`

Unique persistence machine ID. It will be undefined before correct authentication.

Type: `string`.

## `ServerClient#connection`

The Logux wrapper to WebSocket connection.

```js
console.log(client.connection.ws.upgradeReq.headers)
```

Type: `ServerConnection`.

## `ServerClient#data`

Open structure to save data for the whole connection.

```js
server.auth(async ({ client, userId, token }) => {
  let session = await findSession(token)
  if (!session) return false
  client.data.sessionId = session.id
  return true
})
server.on('disconnected', client => {
  touchSession(client.data.sessionId)
})
```

Type: `ClientData`.

## `ServerClient#httpHeaders`

HTTP headers of WS connection.

```js
client.httpHeaders['User-Agent']
```

Type: `{ }`.

## `ServerClient#key`

Client number used as `app.connected` key.

```js
function stillConnected (client) {
  return app.connected.has(client.key)
}
```

Type: `string`.

## `ServerClient#node`

Node instance to synchronize logs.

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

Type: `ServerNode`.

## `ServerClient#nodeId`

Unique node ID. It will be undefined before correct authentication.

Type: `string`.

## `ServerClient#processing`

Does server process some action from client.

```js
console.log('Clients in processing:', clients.map(i => i.processing))
```

Type: `boolean`.

## `ServerClient#remoteAddress`

Client IP address.

```js
const clientCity = detectLocation(client.remoteAddress)
```

Type: `string`.

## `ServerClient#userId`

User ID. It will be filled from client’s node ID. It will be undefined before correct authentication.

Type: `string`.

## `ServerClient#destroy()`

Disconnect client.

## `ServerClient#drain()`

Wait until the client will confirm all sent actions.

Returns `Promise<boolean>`. Promise with `false` if the client was disconnected.

# `addSyncMap(server, plural, operations)`

Add callbacks for client’s `SyncMap`.

```js
import { addSyncMap, isFirstTimeOlder, ChangedAt } from '@logux/server'
import { LoguxNotFoundError } from '@logux/actions'

addSyncMap(server, 'tasks', {
  async access (ctx, id) {
    const task = await Task.find(id)
    return ctx.userId === task.authorId
  },

  async load (ctx, id, since) {
    const task = await Task.find(id)
    if (!task) throw new LoguxNotFoundError()
    return {
      id: task.id,
      text: ChangedAt(task.text, task.textChanged),
      finished: ChangedAt(task.finished, task.finishedChanged),
    }
  },

  async create (ctx, id, fields, time) {
    await Task.create({
      id,
      text: fields.text,
      finished: fields.finished,
      authorId: ctx.userId,
      textChanged: time,
      finishedChanged: time
    })
  },

  async change (ctx, id, fields, time) {
    const task = await Task.find(id)
    if ('text' in fields) {
      if (task.textChanged < time) {
        await task.update({
          text: fields.text,
          textChanged: time
        })
      }
    }
    if ('finished' in fields) {
      if (task.finishedChanged < time) {
        await task.update({
          finished: fields.finished,
          finishedChanged: time
        })
      }
    }
  }

  async delete (ctx, id) {
    await Task.delete(id)
  }
})
```

| Argument     | Type                | Description                                |
| ------------ | ------------------- | ------------------------------------------ |
| `server`     | `BaseServer`        | Server instance.                           |
| `plural`     | `string`            | Prefix for channel names and action types. |
| `operations` | `SyncMapOperations` | Callbacks.                                 |

# `addSyncMapFilter(server, plural, operations)`

Add callbacks for client’s `useFilter`.

```js
import { addSyncMapFilter, ChangedAt } from '@logux/server'

addSyncMapFilter(server, 'tasks', {
  access (ctx, filter) {
    return true
  },

  initial (ctx, filter, since) {
    let tasks = await Tasks.where({ ...filter, authorId: ctx.userId })
    // You can return only data changed after `since`
    return tasks.map(task => ({
      id: task.id,
      text: ChangedAt(task.text, task.textChanged),
      finished: ChangedAt(task.finished, task.finishedChanged),
    }))
  },

  actions (filterCtx, filter) {
    return (actionCtx, action, meta) => {
      return actionCtx.userId === filterCtx.userId
    }
  }
})
```

| Argument     | Type                      | Description                                |
| ------------ | ------------------------- | ------------------------------------------ |
| `server`     | `BaseServer`              | Server instance.                           |
| `plural`     | `string`                  | Prefix for channel names and action types. |
| `operations` | `SyncMapFilterOperations` | Callbacks.                                 |

# TestClient

Client to test server.

```js
import { TestServer } from '@logux/server'
import postsModule from './posts.js'
import authModule from './auth.js'

let destroyable
afterEach(() => {
  if (destroyable) destroyable.destroy()
})

function createServer () {
  destroyable = new TestServer()
  return destroyable
}

it('check auth', () => {
  let server = createServer()
  authModule(server)
  await server.connect('1', { token: 'good' })
   expect(() => {
     await server.connect('2', { token: 'bad' })
   }).rejects.toEqual({
     error: 'Wrong credentials'
   })
})

it('creates and loads posts', () => {
  let server = createServer()
  postsModule(server)
  let client1 = await server.connect('1')
  await client1.process({ type: 'posts/add', post })
  let client1 = await server.connect('2')
  expect(await client2.subscribe('posts')).toEqual([
    { type: 'posts/add', post }
  ])
})
```

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

## `TestClient#clientId`

Client’s ID.

```js
let client = new TestClient(server, '10')
client.clientId //=> '10:1'
```

Type: `string`.

## `TestClient#log`

Client’s log with extra methods to check actions inside.

```js
console.log(client.log.entries())
```

Type: `TestLog`.

## `TestClient#node`

Logux node.

Type: `ClientNode`.

## `TestClient#nodeId`

Client’s node ID.

```js
let client = new TestClient(server, '10')
client.nodeId //=> '10:1:1'
```

Type: `string`.

## `TestClient#pair`

Connection channel between client and server to track sent messages.

```js
console.log(client.pair.leftSent)
```

Type: `TestPair`.

## `TestClient#userId`

User ID.

```js
let client = new TestClient(server, '10')
client.userId //=> '10'
```

Type: `string`.

## `TestClient#collect(test)`

Collect actions added by server and other clients during the `test` call.

```js
let answers = await client.collect(async () => {
  client.log.add({ type: 'pay' })
  await delay(10)
})
expect(actions).toEqual([{ type: 'paid' }])
```

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

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

## `TestClient#connect(opts?)`

Connect to test server.

```js
let client = new TestClient(server, '10')
await client.connect()
```

| Argument | Type                |
| -------- | ------------------- |
| `opts` ? | `{ token: string }` |

Returns `Promise`. Promise until the authorization.

## `TestClient#disconnect()`

Disconnect from test server.

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

Returns `Promise`. Promise until connection close.

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

Send action to the sever and collect all response actions.

```js
await client.process({ type: 'posts/add', post })
let posts = await client.subscribe('posts')
expect(posts).toHaveLength(1)
```

| Argument | Type                  | Description             |
| -------- | --------------------- | ----------------------- |
| `action` | `TypeAction`          | New action.             |
| `meta` ? | `Partial<ServerMeta>` | Optional action’s meta. |

Returns `Promise<Action[]>`. Promise until `logux/processed` answer.

## `TestClient#received(test)`

Collect actions received from server during the `test` call.

```js
let answers = await client1.received(async () => {
  await client2.process({ type: 'resend' })
})
expect(actions).toEqual([{ type: 'local' }])
```

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

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

## `TestClient#subscribe(channel, filter?, since?)`

Subscribe to the channel and collect all actions during the subscription.

```js
let posts = await client.subscribe('posts')
expect(posts).toEqual([
  { type: 'posts/add', post }
])
```

| Argument   | Type                           | Description                               |
| ---------- | ------------------------------ | ----------------------------------------- |
| `channel`  | `any`                          | Channel name or `logux/subscribe` action. |
| `filter` ? | `object`                       | Optional filter for subscription.         |
| `since` ?  | `{ id: string, time: number }` | Optional time from last data.             |

Returns `Promise<Action[]>`. Promise with all actions from the server.

## `TestClient#unsubscribe(channel, filter?)`

Unsubscribe client from the channel.

```js
await client.unsubscribe('posts')
```

| Argument   | Type     | Description                               |
| ---------- | -------- | ----------------------------------------- |
| `channel`  | `any`    | Channel name or `logux/subscribe` action. |
| `filter` ? | `object` | Optional filter for subscription.         |

Returns `Promise<Action[]>`. Promise until server will remove client from subscribers.

# 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, ServerMeta][]`.

## `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

Extends [BaseServer](#baseserver).

Server to be used in test.

```js
import { TestServer } from '@logux/server'
import usersModule from './users.js'

let server
afterEach(() => {
  if (server) server.destroy()
})

it('connects to the server', () => {
  server = new TestServer()
  usersModule(server)
  let client = await server.connect('10')
})
```

| Parameter | Type                | Description                         |
| --------- | ------------------- | ----------------------------------- |
| `opts` ?  | `TestServerOptions` | The limit subset of server options. |

## `TestServer#clientIds`

Connected client by client ID.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `Map<string,ServerClient>`.

## `TestServer#connected`

Connected clients.

```js
for (let client of server.connected.values()) {
  console.log(client.remoteAddress)
}
```

Type: `Map<string,ServerClient>`.

## `TestServer#env`

Production or development mode.

```js
if (server.env === 'development') {
  logDebugData()
}
```

Type: `"development" | "production"`.

## `TestServer#fetch`

fetch() compatible API to test HTTP endpoints.

```js
server.http('GET', '/version', (req, res) => {
  res.end('1.0.0')
})
let res = await server.fetch()
expect(await res.text()).toEqual('1.0.0')
```

Type: `(input: URL | RequestInfo, init?: RequestInit) => Promise<Response>`.

## `TestServer#log`

Server actions log, with methods to check actions inside.

```js
server.log.actions() //=> […]
```

Type: `TestLog`.

## `TestServer#logger`

Console for custom log records. It uses `pino` API.

```js
server.on('connected', client => {
  server.logger.info(
    { domain: client.httpHeaders.domain },
    'Client domain'
  )
})
```

Type: `{ debug: LogFn, error: LogFn, fatal: LogFn, info: LogFn, warn: LogFn }`.

## `TestServer#nodeId`

Server unique ID.

```js
console.log('Error was raised on ' + server.nodeId)
```

Type: `string`.

## `TestServer#nodeIds`

Connected client by node ID.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `Map<string,ServerClient>`.

## `TestServer#options`

Server options.

```js
console.log('Server options', server.options.subprotocol)
```

Type: `BaseServerOptions`.

## `TestServer#subscribers`

Clients subscribed to some channel.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `{ }`.

## `TestServer#time`

Time replacement without variable parts like current timestamp.

Type: `TestTime`.

## `TestServer#userIds`

Connected client by user ID.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `Map<string,ServerClient[]>`.

## `TestServer#addClient(connection)`

Add new client for server. You should call this method manually mostly for test purposes.

```js
server.addClient(test.right)
```

| Argument     | Type               | Description                 |
| ------------ | ------------------ | --------------------------- |
| `connection` | `ServerConnection` | Logux connection to client. |

Returns `number`. Client ID.

## `TestServer#auth(authenticator)`

Set authenticate function. It will receive client credentials and node ID. It should return a Promise with `true` or `false`.

```js
server.auth(async ({ userId, cookie }) => {
  const user = await findUserByToken(cookie.token)
  return !!user && userId === user.id
})
```

| Argument        | Type                  | Description                  |
| --------------- | --------------------- | ---------------------------- |
| `authenticator` | `ServerAuthenticator` | The authentication callback. |

## `TestServer#channel(pattern, callbacks, options?)`

Define the channel.

```js
server.channel('user/:id', {
  access (ctx, action, meta) {
    return ctx.params.id === ctx.userId
  }
  filter (ctx, action, meta) {
    return (otherCtx, otherAction, otherMeta) => {
      return !action.hidden
    }
  }
  async load (ctx, action, meta) {
    const user = await db.loadUser(ctx.params.id)
    ctx.sendBack({ type: 'USER_NAME', name: user.name })
  }
})
```

| Argument    | Type               | Description                           |
| ----------- | ------------------ | ------------------------------------- |
| `pattern`   | `string`           | Pattern for channel name.             |
| `callbacks` | `ChannelCallbacks` | Callback during subscription process. |
| `options` ? | `ChannelOptions`   | Additional options                    |

| Argument    | Type               | Description                           |
| ----------- | ------------------ | ------------------------------------- |
| `pattern`   | `RegExp`           | Regular expression for channel name.  |
| `callbacks` | `ChannelCallbacks` | Callback during subscription process. |
| `options` ? | `ChannelOptions`   | Additional options                    |

## `TestServer#connect(userId, opts?)`

Create and connect client.

```js
server = new TestServer()
let client = await server.connect('10')
```

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

Returns `Promise<TestClient>`. Promise with new client.

## `TestServer#debugError(error)`

Send runtime error stacktrace to all clients.

```js
process.on('uncaughtException', e => {
  server.debugError(e)
})
```

| Argument | Type    | Description             |
| -------- | ------- | ----------------------- |
| `error`  | `Error` | Runtime error instance. |

## `TestServer#destroy()`

Stop server and unbind all listeners.

```js
afterEach(() => {
  testServer.destroy()
})
```

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

## `TestServer#drain(clientId)`

Wait until the client will confirm all actions, which were sent to it.

Use it to send a long history without loading it all into the memory: the client’s speed will limit how fast you read the database.

```js
while (await server.drain(clientId)) {
  let page = await cursor.next(100)
  if (!page.length) break
  server.log.add(page.map(i => [i.action, { clients: [clientId] }]))
}
```

| Argument   | Type     | Description |
| ---------- | -------- | ----------- |
| `clientId` | `string` | Client ID.  |

Returns `Promise<boolean>`. Promise with `false` if the client was disconnected.

## `TestServer#expectDenied(test)`

Call callback and throw an error if there was no `Action was denied` during callback.

```js
await server.expectDenied(async () => {
  client.subscribe('secrets')
})
```

| Argument | Type            | Description                                   |
| -------- | --------------- | --------------------------------------------- |
| `test`   | `() => unknown` | Callback with subscripting or action sending. |

Returns `Promise`.

## `TestServer#expectError(text, test)`

Call callback and throw an error if there was no error during server processing.

| Argument | Type               | Description                                   |
| -------- | ------------------ | --------------------------------------------- |
| `text`   | `string \| RegExp` | RegExp or string of error message.            |
| `test`   | `() => unknown`    | Callback with subscripting or action sending. |

Returns `Promise`.

## `TestServer#expectUndo(reason, test)`

Call callback and throw an error if there was no `logux/undo` in return with specific reason.

```js
await server.expectUndo('notFound', async () => {
  client.subscribe('projects/nothing')
})
```

| Argument | Type            | Description                                   |
| -------- | --------------- | --------------------------------------------- |
| `reason` | `string`        | The reason in undo action.                    |
| `test`   | `() => unknown` | Callback with subscripting or action sending. |

Returns `Promise`.

## `TestServer#expectWrongCredentials(userId, opts?)`

Try to connect client and throw an error is client didn’t received `Wrong Cregentials` message from the server.

```js
server = new TestServer()
await server.expectWrongCredentials('10')
```

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

Returns `Promise`. Promise until check.

## `TestServer#handleClient(ws, req)`

Handle WebSocket connection explicitly

This is a low-level method allowing to integrate Logux server with an existing server

```js
fastify.get('/', { websocket: true }, (socket, req) => {
  loguxServer.handleClient(socket, req)
})
```

| Argument | Type              |
| -------- | ----------------- |
| `ws`     | `WebSocket`       |
| `req`    | `IncomingMessage` |

## `TestServer#http(method, url, listener)`

Add non-WebSocket HTTP request processor.

```js
server.http('GET', '/auth', (req, res) => {
  let token = signIn(req)
  if (token) {
    res.setHeader('Set-Cookie', `token=${token}; Secure; HttpOnly`)
    res.end()
  } else {
    res.statusCode = 400
    res.end('Wrong user or password')
  }
})
```

| Argument   | Type                                                             |
| ---------- | ---------------------------------------------------------------- |
| `method`   | `string`                                                         |
| `url`      | `string`                                                         |
| `listener` | `(req: IncomingMessage, res: ServerResponse) => void \| Promise` |

| Argument   | Type                                                                         |
| ---------- | ---------------------------------------------------------------------------- |
| `listener` | `(req: IncomingMessage, res: ServerResponse) => boolean \| Promise<boolean>` |

## `TestServer#listen()`

Start WebSocket server and listen for clients.

Returns `Promise`. When the server has been bound.

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

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

| Argument   | Type                                                       | Description            |
| ---------- | ---------------------------------------------------------- | ---------------------- |
| `event`    | `"subscribing"`                                            | The event name.        |
| `listener` | `(action: LoguxSubscribeAction, meta: ServerMeta) => void` | Subscription listener. |

| Argument   | Type                                                                      | Description          |
| ---------- | ------------------------------------------------------------------------- | -------------------- |
| `event`    | `"processed"`                                                             | The event name.      |
| `listener` | `(action: Action, meta: ServerMeta, latencyMilliseconds: number) => void` | Processing listener. |

| Argument   | Type                                         | Description      |
| ---------- | -------------------------------------------- | ---------------- |
| `event`    | `"add" \| "clean"`                           | The event name.  |
| `listener` | `(action: Action, meta: ServerMeta) => void` | Action listener. |

| Argument   | Type                             | Description      |
| ---------- | -------------------------------- | ---------------- |
| `event`    | `"connected" \| "disconnected"`  | The event name.  |
| `listener` | `(client: ServerClient) => void` | Client listener. |

| Argument   | Type                       | Description            |
| ---------- | -------------------------- | ---------------------- |
| `event`    | `"clientError" \| "fatal"` | The event name.        |
| `listener` | `(err: Error) => void`     | The listener function. |

| Argument   | Type                                                     | Description     |
| ---------- | -------------------------------------------------------- | --------------- |
| `event`    | `"error"`                                                | The event name. |
| `listener` | `(err: Error, action: Action, meta: ServerMeta) => void` | Error listener. |

| Argument   | Type                                                          | Description      |
| ---------- | ------------------------------------------------------------- | ---------------- |
| `event`    | `"authenticated" \| "unauthenticated"`                        | The event name.  |
| `listener` | `(client: ServerClient, latencyMilliseconds: number) => void` | Client listener. |

| Argument   | Type                                         | Description      |
| ---------- | -------------------------------------------- | ---------------- |
| `event`    | `"preadd"`                                   | The event name.  |
| `listener` | `(action: Action, meta: ServerMeta) => void` | Action listener. |

| Argument   | Type                                                                                    | Description            |
| ---------- | --------------------------------------------------------------------------------------- | ---------------------- |
| `event`    | `"subscribed"`                                                                          | The event name.        |
| `listener` | `(action: LoguxSubscribeAction, meta: ServerMeta, latencyMilliseconds: number) => void` | Subscription listener. |

| Argument   | Type                                                                               | Description            |
| ---------- | ---------------------------------------------------------------------------------- | ---------------------- |
| `event`    | `"unsubscribed"`                                                                   | The event name.        |
| `listener` | `(action: LoguxUnsubscribeAction, meta: ServerMeta, clientNodeId: string) => void` | Subscription listener. |

| Argument   | Type       | Description      |
| ---------- | ---------- | ---------------- |
| `event`    | `"report"` | The event name.  |
| `listener` | `Reporter` | Report listener. |

Returns `Unsubscribe`.

## `TestServer#otherChannel(callbacks)`

Set callbacks for unknown channel subscription.

```js
server.otherChannel({
  async access (ctx, action, meta) {
    const res = await phpBackend.checkChannel(ctx.params[0], ctx.userId)
    if (res.code === 404) {
      this.wrongChannel(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
})
```

| Argument    | Type               | Description                           |
| ----------- | ------------------ | ------------------------------------- |
| `callbacks` | `ChannelCallbacks` | Callback during subscription process. |

## `TestServer#otherType(callbacks)`

Define callbacks for actions, which type was not defined by any [`Server#type`](#server-type). Useful for proxy or some hacks.

Without this settings, server will call [`Server#unknownType`](#server-unknowntype) on unknown type.

```js
server.otherType(
  async access (ctx, action, meta) {
    const response = await phpBackend.checkByHTTP(action, meta)
    if (response.code === 404) {
      this.unknownType(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
  async process (ctx, action, meta) {
    return await phpBackend.sendHTTP(action, meta)
  }
})
```

| Argument    | Type              | Description                           |
| ----------- | ----------------- | ------------------------------------- |
| `callbacks` | `ActionCallbacks` | Callbacks for actions with this type. |

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

Add new action to the server and return the Promise until it will be resend to clients and processed.

| Argument | Type                  | Description                       |
| -------- | --------------------- | --------------------------------- |
| `action` | `TypeAction`          | New action to resend and process. |
| `meta` ? | `Partial<ServerMeta>` | Action’s meta.                    |

Returns `Promise<ServerMeta>`. Promise until new action will be resend to clients and processed.

## `TestServer#sendAction(action, meta)`

Send action, received by other server, to all clients of current server. This method is for multi-server configuration only.

```js
server.on('add', (action, meta) => {
  if (meta.server === server.nodeId) {
    sendToOtherServers(action, meta)
  }
})
onReceivingFromOtherServer((action, meta) => {
  server.sendAction(action, meta)
})
```

| Argument | Type         | Description        |
| -------- | ------------ | ------------------ |
| `action` | `Action`     | New action.        |
| `meta`   | `ServerMeta` | Action’s metadata. |

Returns `void | Promise`.

## `TestServer#sendOnConnect(loader)`

Change a way how server loads actions history for the client.

```js
server.sendOnConnect(async (ctx, lastSynced) => {
  return db.loadActions({ user: ctx.userId, after: lastSynced })
})
```

Actions should be returned from the newest one to the oldest one. The list will be split into messages by the `syncBatch` option, but it is loaded into the memory as a whole. For a big history send it by pages with `Context#sendBack()` and `Context#drain()` instead.

Set `meta.added` to let the client ask only for newer actions after the reconnect: the biggest `added` will be sent as the sync position.

| Argument | Type            | Description                                    |
| -------- | --------------- | ---------------------------------------------- |
| `loader` | `ConnectLoader` | Callback which loads list of actions and meta. |

## `TestServer#subscribe(nodeId, channel)`

Send `logux/subscribed` if client was not already subscribed.

```js
server.subscribe(ctx.nodeId, `users/${loaded}`)
```

| Argument  | Type     | Description   |
| --------- | -------- | ------------- |
| `nodeId`  | `string` | Node ID.      |
| `channel` | `string` | Channel name. |

## `TestServer#type(actionCreator, callbacks, options?)`

| Argument        | Type              | Description                              |
| --------------- | ----------------- | ---------------------------------------- |
| `actionCreator` | `Creator`         | Action creator function.                 |
| `callbacks`     | `ActionCallbacks` | Callbacks for action created by creator. |
| `options` ?     | `TypeOptions`     | Additional options                       |

| Argument    | Type                           | Description                                                  |
| ----------- | ------------------------------ | ------------------------------------------------------------ |
| `name`      | `RegExp \| TypeAction["type"]` | The action’s type or action’s type matching rule as RegExp.. |
| `callbacks` | `ActionCallbacks`              | Callbacks for actions with this type.                        |
| `options` ? | `TypeOptions`                  | Additional options                                           |

## `TestServer#undo(action, meta, reason?, extra?)`

Undo action from client.

```js
if (couldNotFixConflict(action, meta)) {
  server.undo(action, meta)
}
```

| Argument   | Type         | Description                                     |
| ---------- | ------------ | ----------------------------------------------- |
| `action`   | `Action`     | The original action to undo.                    |
| `meta`     | `ServerMeta` | The action’s metadata.                          |
| `reason` ? | `string`     | Optional code for reason. Default is `'error'`. |
| `extra` ?  | `object`     | Extra fields to `logux/undo` action.            |

Returns `Promise`. When action was saved to the log.

## `TestServer#unknownType(action, meta)`

If you receive action with unknown type, this method will mark this action with `error` status and undo it on the clients.

If you didn’t set [`Server#otherType`](#server-othertype), Logux will call it automatically.

```js
server.otherType({
  access (ctx, action, meta) {
    if (action.type.startsWith('myapp/')) {
      return proxy.access(action, meta)
    } else {
      server.unknownType(action, meta)
    }
  }
})
```

| Argument | Type         | Description                   |
| -------- | ------------ | ----------------------------- |
| `action` | `Action`     | The action with unknown type. |
| `meta`   | `ServerMeta` | Action’s metadata.            |

## `TestServer#wrongChannel(action, meta)`

Report that client try to subscribe for unknown channel.

Logux call it automatically, if you will not set [`Server#otherChannel`](#server-otherchannel).

```js
server.otherChannel({
  async access (ctx, action, meta) {
    const res = phpBackend.checkChannel(params[0], ctx.userId)
    if (res.code === 404) {
      this.wrongChannel(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
})
```

| Argument | Type                   | Description           |
| -------- | ---------------------- | --------------------- |
| `action` | `LoguxSubscribeAction` | The subscribe action. |
| `meta`   | `ServerMeta`           | Action’s metadata.    |

# 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`.

# 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, ServerMeta][]`.

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

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

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

Returns `Promise<false | ServerMeta>`. 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, ServerMeta]>`. Promise with array of action and metadata.

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

Change action metadata.

| Argument | Type                  | Description                                      |
| -------- | --------------------- | ------------------------------------------------ |
| `id`     | `string`              | Action ID.                                       |
| `diff`   | `Partial<ServerMeta>` | 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, ServerMeta]>`. 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.

# ServerConnection

Extends [WsBinaryConnection](#wsbinaryconnection).

Logux connection for server WebSocket.

Automatically handles both binary and text protocol clients. When a text-based client connects, it falls back to JSON encoding.

```js
import { ServerConnection } from '@logux/core'
import { Server } from 'ws'

wss.on('connection', function connection(ws) {
  const connection = new ServerConnection(ws)
  const node = new ServerNode('server', log, connection, opts)
})
```

| Parameter | Type        | Description                   |
| --------- | ----------- | ----------------------------- |
| `ws`      | `WebSocket` | WebSocket connection instance |

## `ServerConnection#connected`

Is connection is enabled.

Type: `boolean`.

## `ServerConnection#destroy`

Disconnect and unbind all even listeners.

Type: `() => void`.

## `ServerConnection#textMode`

Whether to use text JSON protocol instead of binary. Always `true` for WsConnection, can change in WsBinaryConnection.

Type: `boolean`.

## `ServerConnection#ws`

WebSocket connection instance

Type: `WebSocket`.

## `ServerConnection#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.

## `ServerConnection#disconnect(reason?)`

Finish current connection.

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

## `ServerConnection#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`.

## `ServerConnection#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 \| ServerMeta \| undefined` | Some action’s metadata.  |
| `secondMeta` | `string \| ServerMeta \| 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. |

## `ActionCallbacks`

Type: `{ access: Authorizer, process?: Processor } | { accessAndProcess: Processor } & { finally?: ActionFinally, resend?: Resender }`.

# `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`   | `ServerMeta` |

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

# `ActionFinally(ctx, action, meta)`

| Argument | Type         | Description                                     |
| -------- | ------------ | ----------------------------------------------- |
| `ctx`    | `Context`    | Information about node, who create this action. |
| `action` | `TypeAction` | The action data.                                |
| `meta`   | `ServerMeta` | The action metadata.                            |

# `ActionIterator(action, meta)`

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

Returns `void | boolean`.

## `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 }`.

## `ActionReporter`

| Property | Type         |
| -------- | ------------ |
| `action` | `Action`     |
| `meta`   | `ServerMeta` |

## `ALLOWED_META`

List of meta keys permitted for clients.

```js
import { ALLOWED_META } from '@logux/server'
async function onSend (action, meta) {
  const filtered = { }
  for (const i in meta) {
    if (ALLOWED_META.includes(i)) {
      filtered[i] = meta[i]
    }
  }
  return [action, filtered]
}
```

Type: `string[]`.

## `AnyAction`

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

## `AuthenticationReporter`

| Property       | Type     |
| -------------- | -------- |
| `connectionId` | `string` |
| `nodeId`       | `string` |
| `subprotocol`  | `string` |

# `Authenticator(nodeId, token, headers)`

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

Returns `Promise<boolean>`.

## `AuthenticatorOptions`

| Property  | Type                        |
| --------- | --------------------------- |
| `client`  | `ServerClient`              |
| `cookie`  | `{ [key: string]: string }` |
| `headers` | `Headers`                   |
| `token`   | `string`                    |
| `userId`  | `string`                    |

# `Authorizer(ctx, action, meta)`

| Argument | Type         | Description                                     |
| -------- | ------------ | ----------------------------------------------- |
| `ctx`    | `Context`    | Information about node, who create this action. |
| `action` | `TypeAction` | The action data.                                |
| `meta`   | `ServerMeta` | The action metadata.                            |

Returns `boolean | Promise<boolean>`.

# BaseServer

Base server class to extend.

| Parameter | Type                | Description     |
| --------- | ------------------- | --------------- |
| `opts`    | `BaseServerOptions` | Server options. |

## `BaseServer#clientIds`

Connected client by client ID.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `Map<string,ServerClient>`.

## `BaseServer#connected`

Connected clients.

```js
for (let client of server.connected.values()) {
  console.log(client.remoteAddress)
}
```

Type: `Map<string,ServerClient>`.

## `BaseServer#env`

Production or development mode.

```js
if (server.env === 'development') {
  logDebugData()
}
```

Type: `"development" | "production"`.

## `BaseServer#log`

Server actions log.

```js
server.log.each(finder)
```

Type: `ServerLog`.

## `BaseServer#logger`

Console for custom log records. It uses `pino` API.

```js
server.on('connected', client => {
  server.logger.info(
    { domain: client.httpHeaders.domain },
    'Client domain'
  )
})
```

Type: `{ debug: LogFn, error: LogFn, fatal: LogFn, info: LogFn, warn: LogFn }`.

## `BaseServer#nodeId`

Server unique ID.

```js
console.log('Error was raised on ' + server.nodeId)
```

Type: `string`.

## `BaseServer#nodeIds`

Connected client by node ID.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `Map<string,ServerClient>`.

## `BaseServer#options`

Server options.

```js
console.log('Server options', server.options.subprotocol)
```

Type: `BaseServerOptions`.

## `BaseServer#subscribers`

Clients subscribed to some channel.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `{ }`.

## `BaseServer#userIds`

Connected client by user ID.

Do not rely on this data, when you have multiple Logux servers. Each server will have a different list.

Type: `Map<string,ServerClient[]>`.

## `BaseServer#addClient(connection)`

Add new client for server. You should call this method manually mostly for test purposes.

```js
server.addClient(test.right)
```

| Argument     | Type               | Description                 |
| ------------ | ------------------ | --------------------------- |
| `connection` | `ServerConnection` | Logux connection to client. |

Returns `number`. Client ID.

## `BaseServer#auth(authenticator)`

Set authenticate function. It will receive client credentials and node ID. It should return a Promise with `true` or `false`.

```js
server.auth(async ({ userId, cookie }) => {
  const user = await findUserByToken(cookie.token)
  return !!user && userId === user.id
})
```

| Argument        | Type                  | Description                  |
| --------------- | --------------------- | ---------------------------- |
| `authenticator` | `ServerAuthenticator` | The authentication callback. |

## `BaseServer#channel(pattern, callbacks, options?)`

Define the channel.

```js
server.channel('user/:id', {
  access (ctx, action, meta) {
    return ctx.params.id === ctx.userId
  }
  filter (ctx, action, meta) {
    return (otherCtx, otherAction, otherMeta) => {
      return !action.hidden
    }
  }
  async load (ctx, action, meta) {
    const user = await db.loadUser(ctx.params.id)
    ctx.sendBack({ type: 'USER_NAME', name: user.name })
  }
})
```

| Argument    | Type               | Description                           |
| ----------- | ------------------ | ------------------------------------- |
| `pattern`   | `string`           | Pattern for channel name.             |
| `callbacks` | `ChannelCallbacks` | Callback during subscription process. |
| `options` ? | `ChannelOptions`   | Additional options                    |

| Argument    | Type               | Description                           |
| ----------- | ------------------ | ------------------------------------- |
| `pattern`   | `RegExp`           | Regular expression for channel name.  |
| `callbacks` | `ChannelCallbacks` | Callback during subscription process. |
| `options` ? | `ChannelOptions`   | Additional options                    |

## `BaseServer#debugError(error)`

Send runtime error stacktrace to all clients.

```js
process.on('uncaughtException', e => {
  server.debugError(e)
})
```

| Argument | Type    | Description             |
| -------- | ------- | ----------------------- |
| `error`  | `Error` | Runtime error instance. |

## `BaseServer#destroy()`

Stop server and unbind all listeners.

```js
afterEach(() => {
  testServer.destroy()
})
```

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

## `BaseServer#drain(clientId)`

Wait until the client will confirm all actions, which were sent to it.

Use it to send a long history without loading it all into the memory: the client’s speed will limit how fast you read the database.

```js
while (await server.drain(clientId)) {
  let page = await cursor.next(100)
  if (!page.length) break
  server.log.add(page.map(i => [i.action, { clients: [clientId] }]))
}
```

| Argument   | Type     | Description |
| ---------- | -------- | ----------- |
| `clientId` | `string` | Client ID.  |

Returns `Promise<boolean>`. Promise with `false` if the client was disconnected.

## `BaseServer#handleClient(ws, req)`

Handle WebSocket connection explicitly

This is a low-level method allowing to integrate Logux server with an existing server

```js
fastify.get('/', { websocket: true }, (socket, req) => {
  loguxServer.handleClient(socket, req)
})
```

| Argument | Type              |
| -------- | ----------------- |
| `ws`     | `WebSocket`       |
| `req`    | `IncomingMessage` |

## `BaseServer#http(method, url, listener)`

Add non-WebSocket HTTP request processor.

```js
server.http('GET', '/auth', (req, res) => {
  let token = signIn(req)
  if (token) {
    res.setHeader('Set-Cookie', `token=${token}; Secure; HttpOnly`)
    res.end()
  } else {
    res.statusCode = 400
    res.end('Wrong user or password')
  }
})
```

| Argument   | Type                                                             |
| ---------- | ---------------------------------------------------------------- |
| `method`   | `string`                                                         |
| `url`      | `string`                                                         |
| `listener` | `(req: IncomingMessage, res: ServerResponse) => void \| Promise` |

| Argument   | Type                                                                         |
| ---------- | ---------------------------------------------------------------------------- |
| `listener` | `(req: IncomingMessage, res: ServerResponse) => boolean \| Promise<boolean>` |

## `BaseServer#listen()`

Start WebSocket server and listen for clients.

Returns `Promise`. When the server has been bound.

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

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

| Argument   | Type                                                       | Description            |
| ---------- | ---------------------------------------------------------- | ---------------------- |
| `event`    | `"subscribing"`                                            | The event name.        |
| `listener` | `(action: LoguxSubscribeAction, meta: ServerMeta) => void` | Subscription listener. |

| Argument   | Type                                                                      | Description          |
| ---------- | ------------------------------------------------------------------------- | -------------------- |
| `event`    | `"processed"`                                                             | The event name.      |
| `listener` | `(action: Action, meta: ServerMeta, latencyMilliseconds: number) => void` | Processing listener. |

| Argument   | Type                                         | Description      |
| ---------- | -------------------------------------------- | ---------------- |
| `event`    | `"add" \| "clean"`                           | The event name.  |
| `listener` | `(action: Action, meta: ServerMeta) => void` | Action listener. |

| Argument   | Type                             | Description      |
| ---------- | -------------------------------- | ---------------- |
| `event`    | `"connected" \| "disconnected"`  | The event name.  |
| `listener` | `(client: ServerClient) => void` | Client listener. |

| Argument   | Type                       | Description            |
| ---------- | -------------------------- | ---------------------- |
| `event`    | `"clientError" \| "fatal"` | The event name.        |
| `listener` | `(err: Error) => void`     | The listener function. |

| Argument   | Type                                                     | Description     |
| ---------- | -------------------------------------------------------- | --------------- |
| `event`    | `"error"`                                                | The event name. |
| `listener` | `(err: Error, action: Action, meta: ServerMeta) => void` | Error listener. |

| Argument   | Type                                                          | Description      |
| ---------- | ------------------------------------------------------------- | ---------------- |
| `event`    | `"authenticated" \| "unauthenticated"`                        | The event name.  |
| `listener` | `(client: ServerClient, latencyMilliseconds: number) => void` | Client listener. |

| Argument   | Type                                         | Description      |
| ---------- | -------------------------------------------- | ---------------- |
| `event`    | `"preadd"`                                   | The event name.  |
| `listener` | `(action: Action, meta: ServerMeta) => void` | Action listener. |

| Argument   | Type                                                                                    | Description            |
| ---------- | --------------------------------------------------------------------------------------- | ---------------------- |
| `event`    | `"subscribed"`                                                                          | The event name.        |
| `listener` | `(action: LoguxSubscribeAction, meta: ServerMeta, latencyMilliseconds: number) => void` | Subscription listener. |

| Argument   | Type                                                                               | Description            |
| ---------- | ---------------------------------------------------------------------------------- | ---------------------- |
| `event`    | `"unsubscribed"`                                                                   | The event name.        |
| `listener` | `(action: LoguxUnsubscribeAction, meta: ServerMeta, clientNodeId: string) => void` | Subscription listener. |

| Argument   | Type       | Description      |
| ---------- | ---------- | ---------------- |
| `event`    | `"report"` | The event name.  |
| `listener` | `Reporter` | Report listener. |

Returns `Unsubscribe`.

## `BaseServer#otherChannel(callbacks)`

Set callbacks for unknown channel subscription.

```js
server.otherChannel({
  async access (ctx, action, meta) {
    const res = await phpBackend.checkChannel(ctx.params[0], ctx.userId)
    if (res.code === 404) {
      this.wrongChannel(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
})
```

| Argument    | Type               | Description                           |
| ----------- | ------------------ | ------------------------------------- |
| `callbacks` | `ChannelCallbacks` | Callback during subscription process. |

## `BaseServer#otherType(callbacks)`

Define callbacks for actions, which type was not defined by any [`Server#type`](#server-type). Useful for proxy or some hacks.

Without this settings, server will call [`Server#unknownType`](#server-unknowntype) on unknown type.

```js
server.otherType(
  async access (ctx, action, meta) {
    const response = await phpBackend.checkByHTTP(action, meta)
    if (response.code === 404) {
      this.unknownType(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
  async process (ctx, action, meta) {
    return await phpBackend.sendHTTP(action, meta)
  }
})
```

| Argument    | Type              | Description                           |
| ----------- | ----------------- | ------------------------------------- |
| `callbacks` | `ActionCallbacks` | Callbacks for actions with this type. |

## `BaseServer#process(action, meta?)`

Add new action to the server and return the Promise until it will be resend to clients and processed.

| Argument | Type                  | Description                       |
| -------- | --------------------- | --------------------------------- |
| `action` | `TypeAction`          | New action to resend and process. |
| `meta` ? | `Partial<ServerMeta>` | Action’s meta.                    |

Returns `Promise<ServerMeta>`. Promise until new action will be resend to clients and processed.

## `BaseServer#sendAction(action, meta)`

Send action, received by other server, to all clients of current server. This method is for multi-server configuration only.

```js
server.on('add', (action, meta) => {
  if (meta.server === server.nodeId) {
    sendToOtherServers(action, meta)
  }
})
onReceivingFromOtherServer((action, meta) => {
  server.sendAction(action, meta)
})
```

| Argument | Type         | Description        |
| -------- | ------------ | ------------------ |
| `action` | `Action`     | New action.        |
| `meta`   | `ServerMeta` | Action’s metadata. |

Returns `void | Promise`.

## `BaseServer#sendOnConnect(loader)`

Change a way how server loads actions history for the client.

```js
server.sendOnConnect(async (ctx, lastSynced) => {
  return db.loadActions({ user: ctx.userId, after: lastSynced })
})
```

Actions should be returned from the newest one to the oldest one. The list will be split into messages by the `syncBatch` option, but it is loaded into the memory as a whole. For a big history send it by pages with `Context#sendBack()` and `Context#drain()` instead.

Set `meta.added` to let the client ask only for newer actions after the reconnect: the biggest `added` will be sent as the sync position.

| Argument | Type            | Description                                    |
| -------- | --------------- | ---------------------------------------------- |
| `loader` | `ConnectLoader` | Callback which loads list of actions and meta. |

## `BaseServer#subscribe(nodeId, channel)`

Send `logux/subscribed` if client was not already subscribed.

```js
server.subscribe(ctx.nodeId, `users/${loaded}`)
```

| Argument  | Type     | Description   |
| --------- | -------- | ------------- |
| `nodeId`  | `string` | Node ID.      |
| `channel` | `string` | Channel name. |

## `BaseServer#type(actionCreator, callbacks, options?)`

| Argument        | Type              | Description                              |
| --------------- | ----------------- | ---------------------------------------- |
| `actionCreator` | `Creator`         | Action creator function.                 |
| `callbacks`     | `ActionCallbacks` | Callbacks for action created by creator. |
| `options` ?     | `TypeOptions`     | Additional options                       |

| Argument    | Type                           | Description                                                  |
| ----------- | ------------------------------ | ------------------------------------------------------------ |
| `name`      | `RegExp \| TypeAction["type"]` | The action’s type or action’s type matching rule as RegExp.. |
| `callbacks` | `ActionCallbacks`              | Callbacks for actions with this type.                        |
| `options` ? | `TypeOptions`                  | Additional options                                           |

## `BaseServer#undo(action, meta, reason?, extra?)`

Undo action from client.

```js
if (couldNotFixConflict(action, meta)) {
  server.undo(action, meta)
}
```

| Argument   | Type         | Description                                     |
| ---------- | ------------ | ----------------------------------------------- |
| `action`   | `Action`     | The original action to undo.                    |
| `meta`     | `ServerMeta` | The action’s metadata.                          |
| `reason` ? | `string`     | Optional code for reason. Default is `'error'`. |
| `extra` ?  | `object`     | Extra fields to `logux/undo` action.            |

Returns `Promise`. When action was saved to the log.

## `BaseServer#unknownType(action, meta)`

If you receive action with unknown type, this method will mark this action with `error` status and undo it on the clients.

If you didn’t set [`Server#otherType`](#server-othertype), Logux will call it automatically.

```js
server.otherType({
  access (ctx, action, meta) {
    if (action.type.startsWith('myapp/')) {
      return proxy.access(action, meta)
    } else {
      server.unknownType(action, meta)
    }
  }
})
```

| Argument | Type         | Description                   |
| -------- | ------------ | ----------------------------- |
| `action` | `Action`     | The action with unknown type. |
| `meta`   | `ServerMeta` | Action’s metadata.            |

## `BaseServer#wrongChannel(action, meta)`

Report that client try to subscribe for unknown channel.

Logux call it automatically, if you will not set [`Server#otherChannel`](#server-otherchannel).

```js
server.otherChannel({
  async access (ctx, action, meta) {
    const res = phpBackend.checkChannel(params[0], ctx.userId)
    if (res.code === 404) {
      this.wrongChannel(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
})
```

| Argument | Type                   | Description           |
| -------- | ---------------------- | --------------------- |
| `action` | `LoguxSubscribeAction` | The subscribe action. |
| `meta`   | `ServerMeta`           | Action’s metadata.    |

## `BaseServerOptions`

| Property              | Type                            | Description                                                                                                                                               |
| --------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cert` ?              | `string`                        | SSL certificate or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.             |
| `cleanFromLog` ?      | `RegExp`                        | Regular expression which should be cleaned from error message and stack.                                                                                  |
| `disableHttpServer` ? | `boolean`                       | Disable health check endpoint, [`Server#http`](#server-http).                                                                                             |
| `env` ?               | `"development" \| "production"` | Development or production server mode. By default, it will be taken from `NODE_ENV` environment variable. On empty `NODE_ENV` it will be `'development'`. |
| `fileUrl` ?           | `string`                        | URL of main JS file in the root dir for the cases where you can’t use `import.meta.dirname`.                                                              |
| `host` ?              | `string`                        | IP-address to bind server. Default is `127.0.0.1`.                                                                                                        |
| `id` ?                | `string`                        | Custom random ID to be used in node ID.                                                                                                                   |
| `key` ?               | `string \| { pem: string }`     | SSL key or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.                     |
| `minSubprotocol` ?    | `number`                        | The version requirements for client subprotocol version.                                                                                                  |
| `Node` ?              | `ServerNodeConstructor`         | Replace class for ServerNode.                                                                                                                             |
| `pid` ?               | `number`                        | Process ID, to display in logs.                                                                                                                           |
| `ping` ?              | `number`                        | Milliseconds since last message to test connection by sending ping. Default is `20000`.                                                                   |
| `port` ?              | `string \| number`              | Port to bind server. It will create HTTP server manually to connect WebSocket server to it. Default is `31337`.                                           |
| `redis` ?             | `string`                        | URL to Redis for Logux Server Pro scaling.                                                                                                                |
| `root` ?              | `string`                        | Application root to load files and show errors. Default is `process.cwd()`.                                                                               |
| `server` ?            | `any`                           | HTTP server to serve Logux’s WebSocket and HTTP requests.                                                                                                 |
| `store` ?             | `any`                           | Store to save log. Will be {@link @logux/core:MemoryStore}, by default.                                                                                   |
| `subprotocol` ?       | `number`                        | Server current application subprotocol version.                                                                                                           |
| `syncBatch` ?         | `number`                        | How many actions could be sent in a single message. `100` by default.                                                                                     |
| `time` ?              | `any`                           | Test time to test server.                                                                                                                                 |
| `timeout` ?           | `number`                        | Timeout in milliseconds to disconnect connection. Default is `70000`.                                                                                     |

# `ChangedAt(value, time)`

Add last changed time to value to use in conflict resolution.

If you do not know the time, use [`NoConflictResolution`](#noconflictresolution).

| Argument | Type     | Description        |
| -------- | -------- | ------------------ |
| `value`  | `Value`  | The value.         |
| `time`   | `number` | UNIX milliseconds. |

Returns `WithTime`. Wrapper.

# `ChannelAuthorizer(ctx, action, meta)`

| Argument | Type              | Description                                     |
| -------- | ----------------- | ----------------------------------------------- |
| `ctx`    | `ChannelContext`  | Information about node, who create this action. |
| `action` | `SubscribeAction` | The action data.                                |
| `meta`   | `ServerMeta`      | The action metadata.                            |

Returns `boolean | Promise<boolean>`.

## `ChannelCallbacks`

Type: `{ access: ChannelAuthorizer, load?: ChannelLoader } | { accessAndLoad: ChannelLoader } & { filter?: FilterCreator, finally?: ChannelFinally, unsubscribe?: ChannelUnsubscribe }`.

# `ChannelFilter(ctx, action, meta)`

| Argument | Type         | Description                                     |
| -------- | ------------ | ----------------------------------------------- |
| `ctx`    | `Context`    | Information about node, who create this action. |
| `action` | `Action`     | The action data.                                |
| `meta`   | `ServerMeta` | The action metadata.                            |

Returns `boolean | Promise<boolean>`.

# `ChannelFinally(ctx, action, meta)`

| Argument | Type              | Description                                     |
| -------- | ----------------- | ----------------------------------------------- |
| `ctx`    | `ChannelContext`  | Information about node, who create this action. |
| `action` | `SubscribeAction` | The action data.                                |
| `meta`   | `ServerMeta`      | The action metadata.                            |

# `ChannelLoader(ctx, action, meta)`

| Argument | Type              | Description                                     |
| -------- | ----------------- | ----------------------------------------------- |
| `ctx`    | `ChannelContext`  | Information about node, who create this action. |
| `action` | `SubscribeAction` | The action data.                                |
| `meta`   | `ServerMeta`      | The action metadata.                            |

Returns `any`.

## `ChannelOptions`

| Property  | Type     | Description                                                                                                |
| --------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `queue` ? | `string` | Name of the queue that will be used to process channels with the specified name pattern. Default is 'main' |

# `ChannelUnsubscribe(ctx, action, meta)`

| Argument | Type                     | Description                                     |
| -------- | ------------------------ | ----------------------------------------------- |
| `ctx`    | `ChannelContext`         | Information about node, who create this action. |
| `action` | `LoguxUnsubscribeAction` | The action data.                                |
| `meta`   | `ServerMeta`             | The action metadata.                            |

## `CleanReporter`

| Property   | Type |
| ---------- | ---- |
| `actionId` | `ID` |

# ConnectContext

| Parameter | Type           |
| --------- | -------------- |
| `server`  | `Server`       |
| `client`  | `ServerClient` |

## `ConnectContext#clientId`

Unique persistence client ID.

```js
server.clientIds.get(node.clientId)
```

Type: `string`.

## `ConnectContext#headers`

Client’s headers.

```js
ctx.sendBack({
  type: 'error',
  message: I18n[ctx.headers.locale || 'en'].error
})
```

Type: `Headers`.

## `ConnectContext#nodeId`

Unique node ID.

```js
server.nodeIds.get(node.nodeId)
```

Type: `string`.

## `ConnectContext#server`

Logux server

Type: `Server`.

## `ConnectContext#subprotocol`

Action creator application subprotocol version.

Type: `number`.

## `ConnectContext#userId`

User ID taken node ID.

```js
async access (ctx, action, meta) {
  const user = await db.getUser(ctx.userId)
  return user.admin
}
```

Type: `string`.

## `ConnectContext#drain()`

Wait until the client will confirm all actions, which were sent to it.

Use it to send a long history without loading it all into the memory: the client’s speed will limit how fast you read the database.

```js
while (await ctx.drain()) {
  let page = await cursor.next(100)
  if (!page.length) break
  ctx.sendBack(page.map(i => i.action))
}
```

Returns `Promise<boolean>`. Promise with `false` if the client was disconnected.

## `ConnectContext#sendBack(action, meta?)`

Send action back to the client.

```js
ctx.sendBack({ type: 'login/success', token })
```

An array of actions will be sent in a single message. Use it to send a big history page by page instead of a message per action.

```js
ctx.sendBack(page.map(i => i.action))
```

Every action in the array can have own meta as `[action, meta]`.

Action will not be processed by server’s callbacks from `Server#type`.

| Argument | Type                                                              | Description                         |
| -------- | ----------------------------------------------------------------- | ----------------------------------- |
| `action` | `TypeAction \| TypeAction \| [TypeAction, Partial<ServerMeta>][]` | The action or the array of actions. |
| `meta` ? | `Partial<ServerMeta>`                                             | Action’s meta.                      |

Returns `Promise`. Promise until action was added to the server log.

# `ConnectLoader(ctx, lastSynced)`

| Argument     | Type             |
| ------------ | ---------------- |
| `ctx`        | `ConnectContext` |
| `lastSynced` | `number`         |

Returns `[Action, ServerMeta][] | Promise<[Action, Partial<Pick<ServerMeta,"added" | "subprotocol">> & Pick<ServerMeta,"id" | "time">][]>`.

## `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 }`.

## `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` ?   | `ServerMeta` | Change reasons only for actions older than specific action.          |
| `youngerThan` ? | `ServerMeta` | Change reasons only for actions younger than specific action.        |

## `EmptyHeaders`

## `Fields`

# `FilterCreator(ctx, action, meta)`

| Argument | Type              | Description                                     |
| -------- | ----------------- | ----------------------------------------------- |
| `ctx`    | `ChannelContext`  | Information about node, who create this action. |
| `action` | `SubscribeAction` | The action data.                                |
| `meta`   | `ServerMeta`      | The action metadata.                            |

Returns `void | ChannelFilter | Promise<ChannelFilter>`.

## `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.     |

# 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`.

# `LogFn(objs)`

| Argument | Type        |
| -------- | ----------- |
| `objs`   | `unknown[]` |

## `Logger`

| Property | Type                                         |
| -------- | -------------------------------------------- |
| `debug`  | `(details: object, message: string) => void` |
| `error`  | `(details: object, message: string) => void` |
| `fatal`  | `(details: object, message: string) => void` |
| `info`   | `(details: object, message: string) => void` |
| `warn`   | `(details: object, message: string) => void` |

## `LoggerOptions`

| Property   | Type                | Description                               |
| ---------- | ------------------- | ----------------------------------------- |
| `color` ?  | `boolean`           | Use color for human output.               |
| `stream` ? | `LogStream`         | Stream to be used by logger to write log. |
| `type` ?   | `"human" \| "json"` | Logger message format.                    |

## `LogOptions`

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

## `LogPage`

| Property  | Type                     | Description      |
| --------- | ------------------------ | ---------------- |
| `entries` | `[Action, ServerMeta][]` | 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`   | `ServerMeta` | Action’s metadata. |

Returns `Promise<false | ServerMeta>`. 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, ServerMeta]>`. Promise with array of action and metadata.

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

Change action metadata.

| Argument | Type                  | Description                                      |
| -------- | --------------------- | ------------------------------------------------ |
| `id`     | `string`              | Action ID.                                       |
| `diff`   | `Partial<ServerMeta>` | 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, ServerMeta]>`. 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.

## `LogStream`

| Property      | Type                    |
| ------------- | ----------------------- |
| `flushSync` ? | `() => void`            |
| `write`       | `(str: string) => void` |

# LoguxActionError

Extends `Error`.

| Parameter   | Type     |
| ----------- | -------- |
| `message` ? | `string` |

| Parameter   | Type           |
| ----------- | -------------- |
| `message` ? | `string`       |
| `options` ? | `ErrorOptions` |

## `LoguxActionError#action`

Type: `Action`.

# 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"`   |

## `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<ServerMeta,"time" | "id">`.

# `NoConflictResolution(value)`

Mark that the value has no last changed date and conflict resolution can’t be applied.

| Argument | Type    | Description |
| -------- | ------- | ----------- |
| `value`  | `Value` | The value.  |

Returns `WithTime`. Wrapper.

## `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`    |

## `PostgresDriver`

Database driver, which the store can use without any wrapper: `pg`’s `Pool` or `Client`, PGlite, or `postgres`.

The parameters are `never[]`, so that the driver’s own stricter types for them still match this type.

Type: `{ begin?: (body: (tx: PostgresDriver) => Promise<unknown>) => Promise<unknown>, unsafe: (sql: string, params: never[]) => Promise<unknown> } | { connect?: () => Promise<unknown>, query: (sql: string, params: never[]) => Promise<unknown>, transaction?: (body: (tx: PostgresDriver) => Promise<unknown>) => Promise<unknown> }`.

# `PostgresQuery(sql, params)`

| Argument | Type        |
| -------- | ----------- |
| `sql`    | `string`    |
| `params` | `unknown[]` |

Returns `Promise<PostgresRows>`.

## `PostgresQuery#transaction`

Run the callback on a single connection inside a transaction.

Without it `init()` can not lock the migrations, so two servers starting at the same moment can apply them twice.

Type: `(body: (query: PostgresQuery) => Promise) => Promise`.

## `PostgresRows`

Rows of the query. `pg` and PGlite keep them in the result object, `postgres` returns them as an array.

Type: `{ rows: { [key: string]: unknown }[] } | { [key: string]: unknown }[]`.

# PostgresStore

Log store, which keeps actions in PostgreSQL.

It takes the database of `pg`, `postgres`, or PGlite. For any other driver, pass the function to send the query, see [`PostgresQuery`](#postgresquery).

```js
import { PostgresStore, Server } from '@logux/server'
import { Pool } from 'pg'

const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const store = new PostgresStore(pool)
await store.init()

const server = new Server(Server.loadOptions(process, {
  minSubprotocol: 1,
  subprotocol: 1,
  root: import.meta.dirname,
  store
}))
```

| Parameter | Type                              |
| --------- | --------------------------------- |
| `db`      | `PostgresQuery \| PostgresDriver` |
| `opts` ?  | `PostgresStoreOptions`            |

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

| Argument | Type         |
| -------- | ------------ |
| `action` | `AnyAction`  |
| `meta`   | `ServerMeta` |

Returns `Promise<any>`.

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

| Argument   | Type       |
| ---------- | ---------- |
| `reasons`  | `string[]` |
| `criteria` | `Criteria` |

Returns `Promise`.

## `PostgresStore#byId(id)`

| Argument | Type     |
| -------- | -------- |
| `id`     | `string` |

Returns `Promise<[Action, ServerMeta] | [null, null]>`.

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

| Argument | Type         |
| -------- | ------------ |
| `id`     | `string`     |
| `diff`   | `ServerMeta` |

Returns `Promise<boolean>`.

## `PostgresStore#clean()`

Returns `Promise`.

## `PostgresStore#get(opts?)`

| Argument | Type  |
| -------- | ----- |
| `opts` ? | `any` |

Returns `Promise<LogPage>`.

## `PostgresStore#getLastAdded()`

Returns `Promise<number>`.

## `PostgresStore#getLastSynced()`

Returns `Promise<LastSynced>`.

## `PostgresStore#init()`

Bring the log tables to the latest version, creating them if they were not created by the application’s migration.

Returns `Promise`.

## `PostgresStore#remove(id)`

| Argument | Type     |
| -------- | -------- |
| `id`     | `string` |

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

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

| Argument   | Type                                         |
| ---------- | -------------------------------------------- |
| `reasons`  | `string[]`                                   |
| `criteria` | `Criteria`                                   |
| `callback` | `(action: Action, meta: ServerMeta) => void` |

Returns `Promise`.

## `PostgresStore#setLastSynced(values)`

| Argument | Type         |
| -------- | ------------ |
| `values` | `LastSynced` |

Returns `Promise`.

## `PostgresStoreOptions`

| Property     | Type      | Description                                                                                                                                  |
| ------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `packers` ?  | `Packers` | Packers to keep the binary parts of the action in the `blob` column instead of Base64 inside JSON. The key must be the `type` of the action. |
| `pageSize` ? | `number`  | How many entries to load by a single query in `get()`.                                                                                       |

# `PreaddListener(action, meta)`

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

# `Processor(ctx, action, meta)`

| Argument | Type         | Description                                     |
| -------- | ------------ | ----------------------------------------------- |
| `ctx`    | `Context`    | Information about node, who create this action. |
| `action` | `TypeAction` | The action data.                                |
| `meta`   | `ServerMeta` | The action metadata.                            |

Returns `void | Promise`.

# `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. |

# `Reporter(event, payload)`

| Argument  | Type                        |
| --------- | --------------------------- |
| `event`   | `Event`                     |
| `payload` | `ReportersArguments[Event]` |

## `ReportersArguments`

| Property          | Type                                                                                                                                                                                                                         |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `add`             | `ActionReporter`                                                                                                                                                                                                             |
| `addClean`        | `ActionReporter`                                                                                                                                                                                                             |
| `authenticated`   | `AuthenticationReporter`                                                                                                                                                                                                     |
| `clean`           | `CleanReporter`                                                                                                                                                                                                              |
| `clientError`     | `{ connectionId?: string, err: Error, nodeId?: string }`                                                                                                                                                                     |
| `connect`         | `{ connectionId: string, ipAddress: string }`                                                                                                                                                                                |
| `denied`          | `CleanReporter`                                                                                                                                                                                                              |
| `destroy`         | `void`                                                                                                                                                                                                                       |
| `disconnect`      | `{ connectionId?: string, nodeId?: string }`                                                                                                                                                                                 |
| `error`           | `{ actionId?: any, connectionId?: string, err: Error, fatal?: true, nodeId?: string }`                                                                                                                                       |
| `listen`          | `{ cert: boolean, environment: "development" \| "production", host: string, loguxServer: string, minSubprotocol: number, nodeId: string, notes: object, port: string, redis: string, server: boolean, subprotocol: number }` |
| `processed`       | `{ actionId: ID, latency: number }`                                                                                                                                                                                          |
| `subscribed`      | `SubscriptionReporter`                                                                                                                                                                                                       |
| `unauthenticated` | `AuthenticationReporter`                                                                                                                                                                                                     |
| `unknownType`     | `{ actionId: ID, type: string }`                                                                                                                                                                                             |
| `unsubscribed`    | `SubscriptionReporter`                                                                                                                                                                                                       |
| `useless`         | `ActionReporter`                                                                                                                                                                                                             |
| `wrongChannel`    | `SubscriptionReporter`                                                                                                                                                                                                       |
| `zombie`          | `{ nodeId: string }`                                                                                                                                                                                                         |

## `Resend`

Type: `{ channel?: string, channels?: string[], client?: string, clients?: string[], excludeClients?: string[], node?: string, nodes?: string[], user?: string, users?: string[] } | string | string[]`.

# `Resender(ctx, action, meta)`

| Argument | Type         | Description                                     |
| -------- | ------------ | ----------------------------------------------- |
| `ctx`    | `Context`    | Information about node, who create this action. |
| `action` | `TypeAction` | The action data.                                |
| `meta`   | `ServerMeta` | The action metadata.                            |

Returns `Promise<Resend> | Resend`.

## `SendBackActions`

Type: `Action | [Action, Partial<ServerMeta>][] | Action | void`.

# `ServerAuthenticator(user)`

| Argument | Type                   |
| -------- | ---------------------- |
| `user`   | `AuthenticatorOptions` |

Returns `boolean | Promise<boolean>`.

## `ServerMeta`

| Property           | Type                                  | Description                                                      |
| ------------------ | ------------------------------------- | ---------------------------------------------------------------- |
| `channel` ?        | `string`                              | All nodes subscribed to channel will receive the action.         |
| `channels` ?       | `string[]`                            | All nodes subscribed to listed channels will receive the action. |
| `client` ?         | `string`                              | All nodes with listed client ID will receive the action.         |
| `clients` ?        | `string[]`                            | All nodes with listed client IDs will receive the action.        |
| `excludeClients` ? | `string[]`                            | Client IDs, which will not receive the action.                   |
| `node` ?           | `string`                              | Node with listed node ID will receive the action.                |
| `nodes` ?          | `string[]`                            | All nodes with listed node IDs will receive the action.          |
| `server`           | `string`                              | Node ID of the server received the action.                       |
| `status` ?         | `"error" \| "processed" \| "waiting"` | Action processing status                                         |
| `user` ?           | `string`                              | All nodes with listed user ID will receive the action.           |
| `users` ?          | `string[]`                            | All nodes with listed user IDs will receive the action.          |

# ServerNode

Extends [BaseNode](#basenode).

Server node in synchronization pair.

Instead of client node, it doesn’t initialize synchronization and destroy itself on disconnect.

```js
import { ServerNode } from '@logux/core'
startServer(ws => {
  const connection = new ServerConnection(ws)
  const node = new ServerNode('server' + id, 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.               |

## `ServerNode#authenticated`

Did we finish remote node authentication.

Type: `boolean`.

## `ServerNode#connected`

Is synchronization in process.

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

Type: `boolean`.

## `ServerNode#connection`

Connection used to communicate to remote node.

Type: `Connection`.

## `ServerNode#initializing`

Promise for node data initial loadiging.

Type: `Promise`.

## `ServerNode#lastReceived`

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

Type: `number`.

## `ServerNode#lastSent`

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

Type: `number`.

## `ServerNode#localNodeId`

Unique current machine name.

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

Type: `string`.

## `ServerNode#localProtocol`

Used Logux protocol.

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

Type: `number`.

## `ServerNode#log`

Log for synchronization.

Type: `NodeLog`.

## `ServerNode#minProtocol`

Minimum version of Logux protocol, which is supported.

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

Type: `number`.

## `ServerNode#options`

Synchronization options.

Type: `NodeOptions`.

## `ServerNode#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`.

## `ServerNode#remoteNodeId`

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

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

Type: `string | undefined`.

## `ServerNode#remoteProtocol`

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

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

Type: `number | undefined`.

## `ServerNode#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`.

## `ServerNode#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`.

## `ServerNode#timeFix`

Time difference between nodes.

Type: `number`.

## `ServerNode#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.

## `ServerNode#destroy()`

Shut down the connection and unsubscribe from log events.

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

## `ServerNode#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`.

## `ServerNode#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. |

## `ServerNode#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.

## `ServerNodeConstructor`

Type: `(args: unknown[]) => ServerNode`.

## `ServerOptions`

| Property              | Type                            | Description                                                                                                                                               |
| --------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cert` ?              | `string`                        | SSL certificate or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.             |
| `cleanFromLog` ?      | `RegExp`                        | Regular expression which should be cleaned from error message and stack.                                                                                  |
| `disableHttpServer` ? | `boolean`                       | Disable health check endpoint, [`Server#http`](#server-http).                                                                                             |
| `env` ?               | `"development" \| "production"` | Development or production server mode. By default, it will be taken from `NODE_ENV` environment variable. On empty `NODE_ENV` it will be `'development'`. |
| `fileUrl` ?           | `string`                        | URL of main JS file in the root dir for the cases where you can’t use `import.meta.dirname`.                                                              |
| `host` ?              | `string`                        | IP-address to bind server. Default is `127.0.0.1`.                                                                                                        |
| `id` ?                | `string`                        | Custom random ID to be used in node ID.                                                                                                                   |
| `key` ?               | `string \| { pem: string }`     | SSL key or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.                     |
| `logger` ?            | `Logger \| LoggerOptions`       | Logger with custom settings.                                                                                                                              |
| `minSubprotocol` ?    | `number`                        | The version requirements for client subprotocol version.                                                                                                  |
| `Node` ?              | `ServerNodeConstructor`         | Replace class for ServerNode.                                                                                                                             |
| `pid` ?               | `number`                        | Process ID, to display in logs.                                                                                                                           |
| `ping` ?              | `number`                        | Milliseconds since last message to test connection by sending ping. Default is `20000`.                                                                   |
| `port` ?              | `string \| number`              | Port to bind server. It will create HTTP server manually to connect WebSocket server to it. Default is `31337`.                                           |
| `redis` ?             | `string`                        | URL to Redis for Logux Server Pro scaling.                                                                                                                |
| `root` ?              | `string`                        | Application root to load files and show errors. Default is `process.cwd()`.                                                                               |
| `server` ?            | `any`                           | HTTP server to serve Logux’s WebSocket and HTTP requests.                                                                                                 |
| `store` ?             | `any`                           | Store to save log. Will be {@link @logux/core:MemoryStore}, by default.                                                                                   |
| `subprotocol` ?       | `number`                        | Server current application subprotocol version.                                                                                                           |
| `syncBatch` ?         | `number`                        | How many actions could be sent in a single message. `100` by default.                                                                                     |
| `time` ?              | `any`                           | Test time to test server.                                                                                                                                 |
| `timeout` ?           | `number`                        | Timeout in milliseconds to disconnect connection. Default is `70000`.                                                                                     |

## `ShadowAction`

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

## `SubscriptionReporter`

| Property   | Type     |
| ---------- | -------- |
| `actionId` | `ID`     |
| `channel`  | `string` |

# `SyncMapActionFilter(ctx, action, meta)`

| Argument | Type         |
| -------- | ------------ |
| `ctx`    | `Context`    |
| `action` | `any`        |
| `meta`   | `ServerMeta` |

Returns `boolean | Promise<boolean>`.

## `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`           |

## `SyncMapData`

Type: `{ [Key: keyof Value]: WithoutTime | WithTime } & { id: string }`.

## `SyncMapDeleteAction`

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

## `SyncMapDeletedAction`

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

## `SyncMapFilterOperations`

| Property    | Type                                                                                                                                                                         |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access` ?  | `(ctx: Context, filter: Partial<Value> \| undefined, action: LoguxSubscribeAction, meta: ServerMeta) => boolean \| Promise<boolean>`                                         |
| `actions` ? | `(ctx: Context, filter: Partial<Value> \| undefined, action: LoguxSubscribeAction, meta: ServerMeta) => void \| Promise<SyncMapActionFilter> \| SyncMapActionFilter`         |
| `initial`   | `(ctx: Context, filter: Partial<Value> \| undefined, since: number \| undefined, action: LoguxSubscribeAction, meta: ServerMeta) => SyncMapData[] \| Promise<SyncMapData[]>` |

## `SyncMapOperations`

| Property   | Type                                                                                                                                                              |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access`   | `(ctx: Context, id: string, action: any, meta: ServerMeta) => boolean \| Promise<boolean>`                                                                        |
| `change` ? | `(ctx: Context, id: string, fields: Partial<Value>, time: number, action: SyncMapChangeAction, meta: ServerMeta) => void \| boolean \| Promise<void \| boolean>`  |
| `create` ? | `(ctx: Context, id: string, fields: Value, time: number, action: SyncMapCreateAction, meta: ServerMeta) => void \| boolean \| Promise<void \| boolean>`           |
| `delete` ? | `(ctx: Context, id: string, action: SyncMapDeleteAction, meta: ServerMeta) => void \| boolean \| Promise<void \| boolean>`                                        |
| `load` ?   | `(ctx: Context, id: string, since: number \| undefined, action: LoguxSubscribeAction, meta: ServerMeta) => false \| Promise<false \| SyncMapData> \| SyncMapData` |

## `SyncMapTypes`

Type: `boolean | null | number | string | undefined`.

## `SyncMapValues`

## `SyncMeta`

| Property        | Type     |
| --------------- | -------- |
| `id`            | `string` |
| `subprotocol` ? | `number` |
| `time`          | `number` |

## `TestClientOptions`

| Property        | Type     |
| --------------- | -------- |
| `cookie` ?      | `object` |
| `headers` ?     | `object` |
| `httpHeaders` ? | `{ }`    |
| `subprotocol` ? | `number` |
| `token` ?       | `string` |

## `TestLogOptions`

| Property   | Type       | Description                                                       |
| ---------- | ---------- | ----------------------------------------------------------------- |
| `nodeId` ? | `string`   | Unique log name.                                                  |
| `store` ?  | `LogStore` | Store for log. Will use [`MemoryStore`](#memorystore) by default. |

## `TestServerOptions`

| Property              | Type                            | Description                                                                                                                                               |
| --------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auth` ?              | `false`                         | Disable built-in auth.                                                                                                                                    |
| `cert` ?              | `string`                        | SSL certificate or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.             |
| `cleanFromLog` ?      | `RegExp`                        | Regular expression which should be cleaned from error message and stack.                                                                                  |
| `disableHttpServer` ? | `boolean`                       | Disable health check endpoint, [`Server#http`](#server-http).                                                                                             |
| `env` ?               | `"development" \| "production"` | Development or production server mode. By default, it will be taken from `NODE_ENV` environment variable. On empty `NODE_ENV` it will be `'development'`. |
| `fileUrl` ?           | `string`                        | URL of main JS file in the root dir for the cases where you can’t use `import.meta.dirname`.                                                              |
| `host` ?              | `string`                        | IP-address to bind server. Default is `127.0.0.1`.                                                                                                        |
| `id` ?                | `string`                        | Custom random ID to be used in node ID.                                                                                                                   |
| `key` ?               | `string \| { pem: string }`     | SSL key or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.                     |
| `logger` ?            | `Logger \| LoggerOptions`       | Logger with custom settings.                                                                                                                              |
| `minSubprotocol` ?    | `number`                        |                                                                                                                                                           |
| `Node` ?              | `ServerNodeConstructor`         | Replace class for ServerNode.                                                                                                                             |
| `pid` ?               | `number`                        | Process ID, to display in logs.                                                                                                                           |
| `ping` ?              | `number`                        | Milliseconds since last message to test connection by sending ping. Default is `20000`.                                                                   |
| `port` ?              | `string \| number`              | Port to bind server. It will create HTTP server manually to connect WebSocket server to it. Default is `31337`.                                           |
| `redis` ?             | `string`                        | URL to Redis for Logux Server Pro scaling.                                                                                                                |
| `root` ?              | `string`                        | Application root to load files and show errors. Default is `process.cwd()`.                                                                               |
| `server` ?            | `any`                           | HTTP server to serve Logux’s WebSocket and HTTP requests.                                                                                                 |
| `store` ?             | `any`                           | Store to save log. Will be {@link @logux/core:MemoryStore}, by default.                                                                                   |
| `subprotocol` ?       | `number`                        |                                                                                                                                                           |
| `syncBatch` ?         | `number`                        | How many actions could be sent in a single message. `100` by default.                                                                                     |
| `time` ?              | `any`                           | Test time to test server.                                                                                                                                 |
| `timeout` ?           | `number`                        | Timeout in milliseconds to disconnect connection. Default is `70000`.                                                                                     |

# `TokenGenerator()`

Returns `string | Promise<string>`.

## `TypeOptions`

| Property  | Type     | Description                                                                                     |
| --------- | -------- | ----------------------------------------------------------------------------------------------- |
| `queue` ? | `string` | Name of the queue that will be used to process actions of the specified type. Default is 'main' |

## `Versions`

| Property    | Type     |
| ----------- | -------- |
| `supported` | `number` |
| `used`      | `number` |

## `WITH_TIME`

Type: `unique symbol`.

## `WithoutTime`

| Property      | Type        |
| ------------- | ----------- |
| `[WITH_TIME]` | `false`     |
| `time`        | `undefined` |
| `value`       | `Value`     |

## `WithTime`

| Property      | Type     |
| ------------- | -------- |
| `[WITH_TIME]` | `true`   |
| `time`        | `number` |
| `value`       | `Value`  |

# 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. |

## `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`    | `ServerMeta`                   |

# `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]`.

## `del`

Type: `any`.

# `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. |

# `filterMeta(meta)`

Remove all non-allowed keys from meta.

| Argument | Type         | Description          |
| -------- | ------------ | -------------------- |
| `meta`   | `ServerMeta` | Meta to remove keys. |

Returns `ServerMeta`. Meta with removed keys.

# `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.

# `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`.

## `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`.

## `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.

# `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.

# `wasNot403(cb)`

Return `false` if `cb()` got response error with 403.

```js
import { wasNot403 } from '@logux/server'

server.auth(({ userId, token }) => {
  return wasNot403(async () => {
    get(`/checkUser/${userId}/${token}`)
  })
})
```

| Argument | Type            | Description                    |
| -------- | --------------- | ------------------------------ |
| `cb`     | `() => Promise` | Callback with `request` calls. |

Returns `Promise<boolean>`.

## `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`.
