Join chat

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

ParameterTypeDescription
nodeIdstringUnique current machine name.
logLLogux log instance to be synchronized.
connectionConnectionConnection to remote node.
options ?NodeOptions<H>Synchronization options.

BaseNode#authenticated

Did we finish remote node authentication.

Type: boolean.

BaseNode#connected

Is synchronization in process.

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

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.

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

Type: string.

BaseNode#localProtocol

Used Logux protocol.

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

Type: number.

BaseNode#log

Log for synchronization.

Type: L.

BaseNode#minProtocol

Minimum version of Logux protocol, which is supported.

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

Type: number.

BaseNode#options

Synchronization options.

Type: NodeOptions<H>.

BaseNode#remoteHeaders

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

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

Type: H | EmptyHeaders.

BaseNode#remoteNodeId

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

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

Type: string | undefined.

BaseNode#remoteProtocol

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

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

Type: number | undefined.

BaseNode#remoteSubprotocol

Remote node’s application subprotocol version in SemVer format.

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

if (semver.satisfies(node.remoteSubprotocol, '>= 5.0.0') {
  useNewAPI()
} else {
  useOldAPI()
}

Type: string | 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.
node.on('state', () => {
  if (node.state === 'sending') {
    console.log('Do not close browser')
  }
})

Type: 'disconnected' | 'connecting' | 'sending' | 'synchronized'.

BaseNode#timeFix

Time difference between nodes.

Type: number.

BaseNode#catch(listener)

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

node.catch(error => {
  console.error(error)
})
ParameterTypeDescription
listener(error: LoguxError) => voidThe error listener.

Returns Unsubscribe. Unbind listener from event.

BaseNode#destroy()

Shut down the connection and unsubscribe from log events.

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

BaseNode#on(event, listener)

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

  • state: synchronization state was changed.
  • connect: custom check before node authentication. You can throw a LoguxError to send error to remote node.
  • error: synchronization error was raised.
  • clientError: when error was sent to remote node.
  • debug: when debug information received from remote node.
  • headers: headers was receive from remote node.
node.on('clientError', error => {
  logError(error)
})
ParameterTypeDescription
event'state' | 'connect' | 'debug' | 'headers'Event name.
listener() => voidThe listener function.
ParameterType
event'error' | 'clientError'
listener(error: Error) => void
ParameterType
event'debug'
listener(type: 'error', data: string) => void
ParameterType
event'headers'
listener(headers: H) => void

Returns Unsubscribe. Unbind listener from event.

BaseNode#setLocalHeaders(headers)

Set headers for current node.

if (navigator) {
  node.setLocalHeaders({ language: navigator.language })
}
node.connection.connect()
ParameterTypeDescription
headersobjectThe 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.

await node.waitFor('synchronized')
console.log('Everything is synchronized')
ParameterTypeDescription
state'disconnected' | 'connecting' | 'sending' | 'synchronized'The expected synchronization state value.

Returns Promise<void>. Promise until specific state.


Base server class to extend.

ParameterTypeDescription
optsBaseServerOptionsServer 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.

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

Type: Map<string, ServerClient>.

BaseServer#controls

Add callback to internal HTTP server.

Type: { [path: string]: GetProcessor | PostProcessor }.

BaseServer#env

Production or development mode.

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

Type: 'production' | 'development'.

BaseServer#log

Server actions log.

server.log.each(finder)

Type: L.

BaseServer#nodeId

Server unique ID.

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.

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

Type: BaseServerOptions.

BaseServer#reporter

Function to show current server status.

Type: Reporter.

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: { [channel: string]: { [nodeId: string]: ChannelFilter<{ }> | true } }.

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.

server.addClient(test.right)
ParameterTypeDescription
connectionServerConnectionLogux 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.

server.auth(async ({ userId, token }) => {
  const user = await findUserByToken(token)
  return !!user && userId === user.id
})
ParameterTypeDescription
authenticatorAuthenticator<H>The authentication callback.

BaseServer#channel(pattern, callbacks)

Define the channel.

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 })
  }
})
ParameterTypeDescription
patternstringPattern for channel name.
callbacksChannelCallbacks<A, D, P, H>Callback during subscription process.
ParameterTypeDescription
patternRegExpRegular expression for channel name.
callbacksChannelCallbacks<A, D, P, H>Callback during subscription process.

Type templates for TypeScript:

TemplatesTypeDescription
PobjectType for ctx.params.
DobjectType for ctx.data.
ALoguxSubscribeActionlogux/subscribe Action’s type.

BaseServer#debugError(error)

Send runtime error stacktrace to all clients.

process.on('uncaughtException', e => {
  server.debugError(e)
})
ParameterTypeDescription
errorErrorRuntime error instance.

BaseServer#destroy()

Stop server and unbind all listeners.

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

Returns Promise<void>. Promise when all listeners will be removed.

BaseServer#listen()

Start WebSocket server and listen for clients.

Returns Promise<void>. When the server has been bound.

BaseServer#on(event, listener)

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

  • error: server error during action processing.
  • fatal: server error during loading.
  • clientError: wrong client behaviour.
  • connected: new client was connected.
  • disconnected: client was disconnected.
  • preadd: action is going to be added to the log. The best place to set reasons.
  • add: action was added to the log.
  • clean: action was cleaned from the log.
  • processed: action processing was finished.
  • subscribed: channel initial data was loaded.
server.on('error', error => {
  trackError(error)
})
ParameterTypeDescription
event'fatal' | 'clientError'The event name.
listener(err: Error) => voidThe listener function.
ParameterTypeDescription
event'error'The event name.
listener(err: Error, action: Action, meta: ServerMeta) => voidError listener.
ParameterTypeDescription
event'connected' | 'disconnected'The event name.
listener(client: ServerClient) => voidClient listener.
ParameterTypeDescription
event'authenticated'The event name.
listener(client: ServerClient, latencyMilliseconds: number) => voidClient listener.
ParameterTypeDescription
event'preadd' | 'add' | 'clean' | 'backendSent'The event name.
listener(action: Action, meta: ServerMeta) => voidAction listener.
ParameterTypeDescription
event'processed' | 'backendGranted' | 'backendProcessed'The event name.
listener(action: Action, meta: ServerMeta, latencyMilliseconds: number) => voidProcessing listener.
ParameterTypeDescription
event'subscribed'The event name.
listener(action: LoguxSubscribeAction, meta: ServerMeta, latencyMilliseconds: number) => voidSubscription listener.
ParameterTypeDescription
event'subscriptionCancelled'The event name.
listener() => voidEvent listener.

Returns Unsubscribe. Unbind listener from event.

BaseServer#otherChannel(callbacks)

Set callbacks for unknown channel subscription.

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'
    }
  }
})
ParameterTypeDescription
callbacksChannelCallbacks <LoguxSubscribeAction, D, [string], H>Callback during subscription process.

Type templates for TypeScript:

TemplatesTypeDescription
DobjectType for ctx.data.

BaseServer#otherType(callbacks)

Define callbacks for actions, which type was not defined by any Server#type. Useful for proxy or some hacks.

Without this settings, server will call Server#unknownType on unknown type.

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)
  }
})
ParameterTypeDescription
callbacksActionCallbacks<Action, D, H>Callbacks for actions with this type.

Type templates for TypeScript:

TemplatesTypeDescription
DobjectType for ctx.data.

BaseServer#process(action, meta?)

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

ParameterTypeDescription
actionActionNew 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.

server.on('add', (action, meta) => {
  if (meta.server === server.nodeId) {
    sendToOtherServers(action, meta)
  }
})
onReceivingFromOtherServer((action, meta) => {
  server.sendAction(action, meta)
})
ParameterTypeDescription
actionActionNew action.
metaServerMetaAction’s metadata.

BaseServer#type(name, callbacks)

Define action type’s callbacks.

server.type('CHANGE_NAME', {
  access (ctx, action, meta) {
    return action.user === ctx.userId
  },
  resend (ctx, action) {
    return { channel: `user/${ action.user }` }
  }
  process (ctx, action, meta) {
    if (isFirstOlder(lastNameChange(action.user), meta)) {
      return db.changeUserName({ id: action.user, name: action.name })
    }
  }
})
ParameterTypeDescription
nameA['type']The action’s type.
callbacksActionCallbacks<A, D, H>Callbacks for actions with this type.
ParameterTypeDescription
actionCreatorACAction creator function.
callbacksActionCallbacks<ReturnType<AC>, D, H>Callbacks for action created by creator.

Type templates for TypeScript:

TemplatesTypeDescription
AActionAction’s type.
DobjectType for ctx.data.
TemplatesTypeDescription
ACActionCreatorAction creator function.
DobjectType for ctx.data.

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

Undo action from client.

if (couldNotFixConflict(action, meta)) {
  server.undo(meta)
}
ParameterTypeDescription
metaServerMetaThe action’s metadata.
reason ?stringOptional code for reason. Default is 'error'
extra ?objectExtra fields to logux/undo action.

Returns Promise<void>. 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, Logux will call it automatically.

server.otherType({
  access (ctx, action, meta) {
    if (action.type.startsWith('myapp/')) {
      return proxy.access(action, meta)
    } else {
      server.unknownType(action, meta)
    }
  }
})
ParameterTypeDescription
actionActionThe action with unknown type.
metaServerMetaAction’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({
  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'
    }
  }
})
ParameterTypeDescription
actionLoguxSubscribeActionThe subscribe action.
metaServerMetaAction’s metadata.

Extends Context.

Subscription context.

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

ChannelContext#clientId

Unique persistence client ID.

server.clientIds.get(node.clientId)

Type: string.

ChannelContext#data

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

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: D.

ChannelContext#headers

Client’s headers.

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

Type: H.

ChannelContext#isServer

Was action created by Logux server.

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

Type: boolean.

ChannelContext#nodeId

Unique node ID.

server.nodeIds.get(node.nodeId)

Type: string.

ChannelContext#params

Parsed variable parts of channel pattern.

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: P.

ChannelContext#server

Logux server

Type: Server.

ChannelContext#subprotocol

Action creator application subprotocol version in SemVer format. Use Context#isSubprotocol to check it.

Type: string.

ChannelContext#userId

User ID taken node ID.

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

Type: 'server' | string.

ChannelContext#isSubprotocol(range)

Check creator subprotocol version. It uses semver npm package to parse requirements.

if (ctx.isSubprotocol('2.x')) {
  useOldAPI()
}
ParameterTypeDescription
rangestringnpm’s version requirements.

Returns boolean. Is version satisfies requirements.

ChannelContext#sendBack(action, meta?)

Send action back to the client.

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

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

ParameterTypeDescription
actionActionThe action.
meta ?Partial<ServerMeta>Action’s meta.

Returns Promise<void>. Promise until action was added to the server log.


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

Connection#connected

Is connection is enabled.

Type: boolean.

Connection#destroy

Optional method to 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<void>. Promise until connection will be established.

Connection#disconnect(reason?)

Finish current connection.

ParameterTypeDescription
reason ?'error' | 'timeout' | 'destroy'Disconnection reason.

Connection#on(event, listener)

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

  • connecting: connection establishing was started.
  • connect: connection was established by any side.
  • disconnect: connection was closed by any side.
  • message: message was receive from remote node.
  • error: error during connection, sending or receiving.
ParameterTypeDescription
event'connecting' | 'connect' | 'disconnect'Event name.
listener() => voidEvent listener.
ParameterType
event'error'
listener(error: Error) => void
ParameterType
event'message'
listener(msg: Message) => void
ParameterType
event'disconnect'
listener(reason: string) => void

Returns Unsubscribe. Unbind listener from event.

Connection#send(message)

Send message to connection.

ParameterTypeDescription
messageMessageThe message to be sent.

Action context.

server.type('FOO', {
  access (ctx, action, meta) {
    return ctx.isSubprotocol('3.x') ? check3(action) : check4(action)
  }
})

Context#clientId

Unique persistence client ID.

server.clientIds.get(node.clientId)

Type: string.

Context#data

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

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: D.

Context#headers

Client’s headers.

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

Type: H.

Context#isServer

Was action created by Logux server.

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

Type: boolean.

Context#nodeId

Unique node ID.

server.nodeIds.get(node.nodeId)

Type: string.

Context#server

Logux server

Type: Server.

Context#subprotocol

Action creator application subprotocol version in SemVer format. Use Context#isSubprotocol to check it.

Type: string.

Context#userId

User ID taken node ID.

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

Type: 'server' | string.

Context#isSubprotocol(range)

Check creator subprotocol version. It uses semver npm package to parse requirements.

if (ctx.isSubprotocol('2.x')) {
  useOldAPI()
}
ParameterTypeDescription
rangestringnpm’s version requirements.

Returns boolean. Is version satisfies requirements.

Context#sendBack(action, meta?)

Send action back to the client.

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

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

ParameterTypeDescription
actionActionThe action.
meta ?Partial<ServerMeta>Action’s meta.

Returns Promise<void>. Promise until action was added to the server log.


Extends Connection.

LocalConnection#connected

Is connection is enabled.

Type: boolean.

LocalConnection#destroy

Optional method to 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<void>. Promise until connection will be established.

LocalConnection#disconnect(reason?)

Finish current connection.

ParameterTypeDescription
reason ?'error' | 'timeout' | 'destroy'Disconnection reason.

LocalConnection#on(event, listener)

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

  • connecting: connection establishing was started.
  • connect: connection was established by any side.
  • disconnect: connection was closed by any side.
  • message: message was receive from remote node.
  • error: error during connection, sending or receiving.
ParameterTypeDescription
event'connecting' | 'connect' | 'disconnect'Event name.
listener() => voidEvent listener.
ParameterType
event'error'
listener(error: Error) => void
ParameterType
event'message'
listener(msg: Message) => void
ParameterType
event'disconnect'
listener(reason: string) => void

Returns Unsubscribe. Unbind listener from event.

LocalConnection#other()

Returns LocalConnection.

LocalConnection#send(message)

Send message to connection.

ParameterTypeDescription
messageMessageThe message to be sent.

Two paired loopback connections.

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)
ParameterTypeDescription
delay ?numberDelay 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().

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

Type: LocalConnection.

LocalPair#right

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

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

Type: LocalConnection.


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.

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

log.on('add', beeper)
log.add({ type: 'beep' })
ParameterTypeDescription
optsLogOptions<S>Log options.

Log#nodeId

Unique node ID. It is used in action IDs.

Type: string.

Log#store

Log store.

Type: S.

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.

removeButton.addEventListener('click', () => {
  log.add({ type: 'users:remove', user: id })
})
ParameterTypeDescription
actionActionThe new action.
meta ?Partial<ServerMeta>Open structure for action metadata.

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

Log#byId(id)

Does log already has action with this ID.

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

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

Log#changeMeta(id, diff)

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

await process(action)
log.changeMeta(action, { status: 'processed' })
ParameterTypeDescription
idIDAction ID.
diffPartial<ServerMeta>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(callback)

Iterates through all actions, from last to first.

Return false from callback if you want to stop iteration.

log.each((action, meta) => {
  if (compareTime(meta.id, lastBeep) <= 0) {
    return false;
  } else if (action.type === 'beep') {
    beep()
    lastBeep = meta.id
    return false;
  }
})
ParameterTypeDescription
callbackActionIteratorFunction will be executed on every action.
ParameterTypeDescription
optsGetOptionsIterator options.
callbackActionIteratorFunction will be executed on every action.
ParameterType
callbackActionIterator

Returns Promise<void>. When iteration will be finished by iterator or end of actions.

Log#generateId()

Generate next unique action ID.

const id = log.generateId()

Returns ID. Unique ID for action.

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.
const unbind = log.on('add', (action, meta) => {
  if (action.type === 'beep') beep()
})
function disableBeeps () {
  unbind()
}
ParameterTypeDescription
event'preadd' | 'add' | 'clean'The event name.
listenerActionListenerThe listener function.

Returns Unsubscribe. Unbind listener from event.

Log#removeReason(reason, criteria?)

Remove reason tag from action’s metadata and remove actions without reason from log.

onSync(lastSent) {
  log.removeReason('unsynchronized', { maxAdded: lastSent })
}
ParameterTypeDescription
reasonstringThe reason name.
criteria ?CriteriaCriteria to select action for reason removing.

Returns Promise<void>. Promise when cleaning will be finished.


Every Store class should provide 8 standard methods.

ParameterTypeDescription
actionActionThe action to add.
metaMetaAction’s metadata.

LogStore#add(action, meta)

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

ParameterTypeDescription
actionActionThe action to add.
metaMetaAction’s metadata.

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

LogStore#byId(id)

Return action by action ID.

ParameterTypeDescription
idIDAction ID.

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

LogStore#changeMeta(id, diff)

Change action metadata.

ParameterTypeDescription
idIDAction ID.
diffPartial<Meta>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<void>. 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.

ParameterTypeDescription
opts ?GetOptionsQuery options.

Returns Promise<Page>. 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.

ParameterTypeDescription
idIDAction ID.

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

LogStore#removeReason(reason, criteria, callback)

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

ParameterTypeDescription
reasonstringThe reason name.
criteriaCriteriaCriteria to select action for reason removing.
callbackActionListenerCallback for every removed action.

Returns Promise<void>. Promise when cleaning will be finished.

LogStore#setLastSynced(values)

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

ParameterTypeDescription
valuesLastSyncedObject with latest sent or received values.

Returns Promise<void>. Promise when values will be saved to store.


Extends Error.

Logux error in logs synchronization.

if (error.name === 'LoguxError') {
  console.log('Server throws: ' + error.description)
}
ParameterTypeDescription
typeTThe error code.
options ?LoguxErrorOptions[T]The error option.
received ?booleanWas error received from remote node.

LoguxError.description(type, options?)

Return a error description by it code.

ParameterTypeDescription
typeTThe error code.
options ?LoguxErrorOptions[T]The errors options depends on error code.

Returns string.

LoguxError#description

Human-readable error description.

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.

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

Type: 'LoguxError'.

LoguxError#options

Error options depends on error type.

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

Type: LoguxErrorOptions[T].

LoguxError#received

Was error received from remote client.

Type: boolean.

LoguxError#stack

Calls which cause the error.

Type: string.

LoguxError#type

The error code.

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

Type: T.


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

import { MemoryStore } from '@logux/core'

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

MemoryStore#entries

Actions in the store.

Type: [Action, Meta][].

MemoryStore#add(action, meta)

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

ParameterTypeDescription
actionActionThe action to add.
metaMetaAction’s metadata.

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

MemoryStore#byId(id)

Return action by action ID.

ParameterTypeDescription
idIDAction ID.

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

MemoryStore#changeMeta(id, diff)

Change action metadata.

ParameterTypeDescription
idIDAction ID.
diffPartial<Meta>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<void>. 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.

ParameterTypeDescription
opts ?GetOptionsQuery options.

Returns Promise<Page>. 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.

ParameterTypeDescription
idIDAction ID.

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

MemoryStore#removeReason(reason, criteria, callback)

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

ParameterTypeDescription
reasonstringThe reason name.
criteriaCriteriaCriteria to select action for reason removing.
callbackActionListenerCallback for every removed action.

Returns Promise<void>. Promise when cleaning will be finished.

MemoryStore#setLastSynced(values)

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

ParameterTypeDescription
valuesLastSyncedObject with latest sent or received values.

Returns Promise<void>. Promise when values will be saved to store.


Extends BaseServer.

End-user API to create Logux server.

const { Server } = require('@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.0.0',
  supports: '1.x || 0.x',
  root: __dirname
}, envOptions))

server.listen()
ParameterTypeDescription
optsServerOptionsServer options.

Server.loadOptions(process, defaults)

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

const server = new Server(Server.loadOptions(process, {
  subprotocol: '1.0.0',
  supports: '1.x',
  root: __dirname,
  port: 31337
}))
ParameterTypeDescription
processProcessCurrent process object.
defaultsServerOptionsDefault 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.

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

Type: Map<string, ServerClient>.

Server#controls

Add callback to internal HTTP server.

Type: { [path: string]: GetProcessor | PostProcessor }.

Server#env

Production or development mode.

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

Type: 'production' | 'development'.

Server#log

Server actions log.

server.log.each(finder)

Type: L.

Server#nodeId

Server unique ID.

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.

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

Type: ServerOptions.

Server#reporter

Function to show current server status.

Type: Reporter.

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: { [channel: string]: { [nodeId: string]: ChannelFilter<{ }> | true } }.

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.

server.addClient(test.right)
ParameterTypeDescription
connectionServerConnectionLogux 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.

server.auth(async ({ userId, token }) => {
  const user = await findUserByToken(token)
  return !!user && userId === user.id
})
ParameterTypeDescription
authenticatorAuthenticator<H>The authentication callback.

Server#channel(pattern, callbacks)

Define the channel.

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 })
  }
})
ParameterTypeDescription
patternstringPattern for channel name.
callbacksChannelCallbacks<A, D, P, H>Callback during subscription process.
ParameterTypeDescription
patternRegExpRegular expression for channel name.
callbacksChannelCallbacks<A, D, P, H>Callback during subscription process.

Type templates for TypeScript:

TemplatesTypeDescription
PobjectType for ctx.params.
DobjectType for ctx.data.
ALoguxSubscribeActionlogux/subscribe Action’s type.

Server#debugError(error)

Send runtime error stacktrace to all clients.

process.on('uncaughtException', e => {
  server.debugError(e)
})
ParameterTypeDescription
errorErrorRuntime error instance.

Server#destroy()

Stop server and unbind all listeners.

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

Returns Promise<void>. Promise when all listeners will be removed.

Server#listen()

Start WebSocket server and listen for clients.

Returns Promise<void>. When the server has been bound.

Server#on(event, listener)

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

  • error: server error during action processing.
  • fatal: server error during loading.
  • clientError: wrong client behaviour.
  • connected: new client was connected.
  • disconnected: client was disconnected.
  • preadd: action is going to be added to the log. The best place to set reasons.
  • add: action was added to the log.
  • clean: action was cleaned from the log.
  • processed: action processing was finished.
  • subscribed: channel initial data was loaded.
server.on('error', error => {
  trackError(error)
})
ParameterTypeDescription
event'fatal' | 'clientError'The event name.
listener(err: Error) => voidThe listener function.
ParameterTypeDescription
event'error'The event name.
listener(err: Error, action: Action, meta: ServerMeta) => voidError listener.
ParameterTypeDescription
event'connected' | 'disconnected'The event name.
listener(client: ServerClient) => voidClient listener.
ParameterTypeDescription
event'authenticated'The event name.
listener(client: ServerClient, latencyMilliseconds: number) => voidClient listener.
ParameterTypeDescription
event'preadd' | 'add' | 'clean' | 'backendSent'The event name.
listener(action: Action, meta: ServerMeta) => voidAction listener.
ParameterTypeDescription
event'processed' | 'backendGranted' | 'backendProcessed'The event name.
listener(action: Action, meta: ServerMeta, latencyMilliseconds: number) => voidProcessing listener.
ParameterTypeDescription
event'subscribed'The event name.
listener(action: LoguxSubscribeAction, meta: ServerMeta, latencyMilliseconds: number) => voidSubscription listener.
ParameterTypeDescription
event'subscriptionCancelled'The event name.
listener() => voidEvent listener.

Returns Unsubscribe. Unbind listener from event.

Server#otherChannel(callbacks)

Set callbacks for unknown channel subscription.

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'
    }
  }
})
ParameterTypeDescription
callbacksChannelCallbacks <LoguxSubscribeAction, D, [string], H>Callback during subscription process.

Type templates for TypeScript:

TemplatesTypeDescription
DobjectType for ctx.data.

Server#otherType(callbacks)

Define callbacks for actions, which type was not defined by any Server#type. Useful for proxy or some hacks.

Without this settings, server will call Server#unknownType on unknown type.

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)
  }
})
ParameterTypeDescription
callbacksActionCallbacks<Action, D, H>Callbacks for actions with this type.

Type templates for TypeScript:

TemplatesTypeDescription
DobjectType for ctx.data.

Server#process(action, meta?)

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

ParameterTypeDescription
actionActionNew 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.

server.on('add', (action, meta) => {
  if (meta.server === server.nodeId) {
    sendToOtherServers(action, meta)
  }
})
onReceivingFromOtherServer((action, meta) => {
  server.sendAction(action, meta)
})
ParameterTypeDescription
actionActionNew action.
metaServerMetaAction’s metadata.

Server#type(name, callbacks)

Define action type’s callbacks.

server.type('CHANGE_NAME', {
  access (ctx, action, meta) {
    return action.user === ctx.userId
  },
  resend (ctx, action) {
    return { channel: `user/${ action.user }` }
  }
  process (ctx, action, meta) {
    if (isFirstOlder(lastNameChange(action.user), meta)) {
      return db.changeUserName({ id: action.user, name: action.name })
    }
  }
})
ParameterTypeDescription
nameA['type']The action’s type.
callbacksActionCallbacks<A, D, H>Callbacks for actions with this type.
ParameterTypeDescription
actionCreatorACAction creator function.
callbacksActionCallbacks<ReturnType<AC>, D, H>Callbacks for action created by creator.

Type templates for TypeScript:

TemplatesTypeDescription
AActionAction’s type.
DobjectType for ctx.data.
TemplatesTypeDescription
ACActionCreatorAction creator function.
DobjectType for ctx.data.

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

Undo action from client.

if (couldNotFixConflict(action, meta)) {
  server.undo(meta)
}
ParameterTypeDescription
metaServerMetaThe action’s metadata.
reason ?stringOptional code for reason. Default is 'error'
extra ?objectExtra fields to logux/undo action.

Returns Promise<void>. 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, Logux will call it automatically.

server.otherType({
  access (ctx, action, meta) {
    if (action.type.startsWith('myapp/')) {
      return proxy.access(action, meta)
    } else {
      server.unknownType(action, meta)
    }
  }
})
ParameterTypeDescription
actionActionThe action with unknown type.
metaServerMetaAction’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({
  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'
    }
  }
})
ParameterTypeDescription
actionLoguxSubscribeActionThe subscribe action.
metaServerMetaAction’s metadata.

Logux client connected to server.

const client = server.connected.get(0)

ServerClient#app

Server, which received client.

Type: Server.

ServerClient#clientId

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

Type: string.

ServerClient#connection

The Logux wrapper to WebSocket connection.

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

Type: ServerConnection.

ServerClient#key

Client number used as app.connected key.

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

Type: string.

ServerClient#node

Node instance to synchronize logs.

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.

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

Type: boolean.

ServerClient#remoteAddress

Client IP address.

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#isSubprotocol(range)

Check client subprotocol version. It uses semver npm package to parse requirements.

if (client.isSubprotocol('4.x')) {
  useOldAPI()
}
ParameterTypeDescription
rangestringnpm’s version requirements.

Returns boolean. Is version satisfies requirements.


Extends Connection.

Logux connection for server WebSocket.

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)
})
ParameterTypeDescription
wsWebSocketWebSocket connection instance

ServerConnection#connected

Is connection is enabled.

Type: boolean.

ServerConnection#destroy

Optional method to disconnect and unbind all even listeners.

Type: () => void.

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<void>. Promise until connection will be established.

ServerConnection#disconnect(reason?)

Finish current connection.

ParameterTypeDescription
reason ?'error' | 'timeout' | 'destroy'Disconnection reason.

ServerConnection#on(event, listener)

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

  • connecting: connection establishing was started.
  • connect: connection was established by any side.
  • disconnect: connection was closed by any side.
  • message: message was receive from remote node.
  • error: error during connection, sending or receiving.
ParameterTypeDescription
event'connecting' | 'connect' | 'disconnect'Event name.
listener() => voidEvent listener.
ParameterType
event'error'
listener(error: Error) => void
ParameterType
event'message'
listener(msg: Message) => void
ParameterType
event'disconnect'
listener(reason: string) => void

Returns Unsubscribe. Unbind listener from event.

ServerConnection#send(message)

Send message to connection.

ParameterTypeDescription
messageMessageThe message to be sent.

Extends BaseNode.

Server node in synchronization pair.

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

import { ServerNode } from '@logux/core'
startServer(ws => {
  const connection = new ServerConnection(ws)
  const node = new ServerNode('server' + id, log, connection)
})
ParameterTypeDescription
nodeIdstringUnique current machine name.
logLLogux log instance to be synchronized.
connectionConnectionConnection to remote node.
options ?NodeOptions<H>Synchronization options.

ServerNode#authenticated

Did we finish remote node authentication.

Type: boolean.

ServerNode#connected

Is synchronization in process.

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

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.

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

Type: string.

ServerNode#localProtocol

Used Logux protocol.

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

Type: number.

ServerNode#log

Log for synchronization.

Type: L.

ServerNode#minProtocol

Minimum version of Logux protocol, which is supported.

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

Type: number.

ServerNode#options

Synchronization options.

Type: NodeOptions<H>.

ServerNode#remoteHeaders

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

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

Type: H | EmptyHeaders.

ServerNode#remoteNodeId

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

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

Type: string | undefined.

ServerNode#remoteProtocol

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

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

Type: number | undefined.

ServerNode#remoteSubprotocol

Remote node’s application subprotocol version in SemVer format.

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

if (semver.satisfies(node.remoteSubprotocol, '>= 5.0.0') {
  useNewAPI()
} else {
  useOldAPI()
}

Type: string | 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.
node.on('state', () => {
  if (node.state === 'sending') {
    console.log('Do not close browser')
  }
})

Type: 'disconnected' | 'connecting' | 'sending' | 'synchronized'.

ServerNode#timeFix

Time difference between nodes.

Type: number.

ServerNode#catch(listener)

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

node.catch(error => {
  console.error(error)
})
ParameterTypeDescription
listener(error: LoguxError) => voidThe error listener.

Returns Unsubscribe. Unbind listener from event.

ServerNode#destroy()

Shut down the connection and unsubscribe from log events.

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

ServerNode#on(event, listener)

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

  • state: synchronization state was changed.
  • connect: custom check before node authentication. You can throw a LoguxError to send error to remote node.
  • error: synchronization error was raised.
  • clientError: when error was sent to remote node.
  • debug: when debug information received from remote node.
  • headers: headers was receive from remote node.
node.on('clientError', error => {
  logError(error)
})
ParameterTypeDescription
event'state' | 'connect' | 'debug' | 'headers'Event name.
listener() => voidThe listener function.
ParameterType
event'error' | 'clientError'
listener(error: Error) => void
ParameterType
event'debug'
listener(type: 'error', data: string) => void
ParameterType
event'headers'
listener(headers: H) => void

Returns Unsubscribe. Unbind listener from event.

ServerNode#setLocalHeaders(headers)

Set headers for current node.

if (navigator) {
  node.setLocalHeaders({ language: navigator.language })
}
node.connection.connect()
ParameterTypeDescription
headersobjectThe 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.

await node.waitFor('synchronized')
console.log('Everything is synchronized')
ParameterTypeDescription
state'disconnected' | 'connecting' | 'sending' | 'synchronized'The expected synchronization state value.

Returns Promise<void>. Promise until specific state.


Client to test server.

import { TestServer } from '@logux/server'
import postsModule from '.'
import authModule from '.'

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 }
  ])
})
ParameterTypeDescription
serverTestServerTest server.
userIdstringUser ID.
opts ?TestClientOptionsOther options.

TestClient#log

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

console.log(client.log.entries())

Type: TestLog.

TestClient#nodeId

Client’s node ID.

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

Type: string.

TestClient#pair

Connection channel between client and server to track sent messages.

console.log(client.pair.leftSent)

Type: TestPair.

TestClient#collect(test)

Actions added server and other clients during the test call.

let answers = await client.collect(async () => {
  client.log.add({ type: 'pay' })
  await delay(10)
})
expect(actions).toEqual([{ type: 'paid' }])
ParameterTypeDescription
test() => Promise<void>Function, where do you expect action will be received

Returns Promise<Action[]>. Promise will all received actions

TestClient#connect(opts?)

Connect to test server.

let client = new TestClient(server, '10')
await client.connect()
ParameterType
opts ?{ token: string }

Returns Promise<void>. Promise until the authorization.

TestClient#disconnect()

Disconnect from test server.

await client.disconnect()

Returns Promise<void>. Promise until connection close.

TestClient#process(action, meta?)

Send action to the sever and collect all response actions.

await client.process({ type: 'posts/add', post })
let posts = await client.subscribe('posts')
expect(posts).toHaveLength(1)
ParameterTypeDescription
actionActionNew action.
meta ?Partial<ServerMeta>Optional action’s meta.

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

TestClient#subscribe(channel)

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

let posts = await client.subscribe('posts')
expect(posts).toEqual([
  { type: 'posts/add', post }
])
ParameterTypeDescription
channelstring | LoguxAnySubscribeActionChannel name or logux/subscribe action.

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

TestClient#unsubscribe(channel)

Unsubscribe client from the channel.

await client.unsubscribe('posts')
ParameterTypeDescription
channelstring | LoguxUnsubscribeActionChannel name or logux/subscribe action.

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


Extends Log.

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

Use TestTime to create test log.

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: S.

TestLog#actions()

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

This shortcut works only with MemoryStore.

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.

removeButton.addEventListener('click', () => {
  log.add({ type: 'users:remove', user: id })
})
ParameterTypeDescription
actionActionThe new action.
meta ?Partial<ServerMeta>Open structure for action metadata.

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

TestLog#byId(id)

Does log already has action with this ID.

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

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

TestLog#changeMeta(id, diff)

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

await process(action)
log.changeMeta(action, { status: 'processed' })
ParameterTypeDescription
idIDAction ID.
diffPartial<ServerMeta>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(callback)

Iterates through all actions, from last to first.

Return false from callback if you want to stop iteration.

log.each((action, meta) => {
  if (compareTime(meta.id, lastBeep) <= 0) {
    return false;
  } else if (action.type === 'beep') {
    beep()
    lastBeep = meta.id
    return false;
  }
})
ParameterTypeDescription
callbackActionIteratorFunction will be executed on every action.
ParameterTypeDescription
optsGetOptionsIterator options.
callbackActionIteratorFunction will be executed on every action.
ParameterType
callbackActionIterator

Returns Promise<void>. When iteration will be finished by iterator or end of actions.

TestLog#entries()

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

This shortcut works only with MemoryStore.

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

Returns [Action, Meta][].

TestLog#generateId()

Generate next unique action ID.

const id = log.generateId()

Returns ID. Unique ID for action.

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.
const unbind = log.on('add', (action, meta) => {
  if (action.type === 'beep') beep()
})
function disableBeeps () {
  unbind()
}
ParameterTypeDescription
event'preadd' | 'add' | 'clean'The event name.
listenerActionListenerThe listener function.

Returns Unsubscribe. Unbind listener from event.

TestLog#removeReason(reason, criteria?)

Remove reason tag from action’s metadata and remove actions without reason from log.

onSync(lastSent) {
  log.removeReason('unsynchronized', { maxAdded: lastSent })
}
ParameterTypeDescription
reasonstringThe reason name.
criteria ?CriteriaCriteria to select action for reason removing.

Returns Promise<void>. Promise when cleaning will be finished.


Extends LocalPair.

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

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])
})
ParameterTypeDescription
delay ?numberDelay 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().

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

Type: LocalConnection.

TestPair#leftEvents

Emitted events from left connection.

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

Type: string[][].

TestPair#leftNode

Node instance used in this test, connected with left.

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

Type: BaseNode<{ }, TestLog>.

TestPair#leftSent

Sent messages from left connection.

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

Type: Message[].

TestPair#right

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

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

Type: LocalConnection.

TestPair#rightEvents

Emitted events from right connection.

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

Type: string[][].

TestPair#rightNode

Node instance used in this test, connected with right.

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

Type: BaseNode<{ }, TestLog>.

TestPair#rightSent

Sent messages from right connection.

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

Type: Message[].

TestPair#clear()

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

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.

pair.left.send(['test'])
await pair.wait('left')
pair.leftSend //=> [['test']]
ParameterTypeDescription
receiver ?'left' | 'right'Wait for specific receiver event.

Returns Promise<void>. Promise until next event.


Extends BaseServer.

Server to be used in test.

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

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

it('connects to the server', () => {
  server = new TestServer()
  usersModule(server)
  let client = await server.connect('10')
})
ParameterTypeDescription
opts ?TestServerOptionsThe 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.

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

Type: Map<string, ServerClient>.

TestServer#controls

Add callback to internal HTTP server.

Type: { [path: string]: GetProcessor | PostProcessor }.

TestServer#env

Production or development mode.

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

Type: 'production' | 'development'.

TestServer#log

Server actions log, with methods to check actions inside.

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

Type: TestLog<ServerMeta>.

TestServer#nodeId

Server unique ID.

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.

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

Type: BaseServerOptions.

TestServer#reporter

Function to show current server status.

Type: Reporter.

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: { [channel: string]: { [nodeId: string]: ChannelFilter<{ }> | true } }.

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.

server.addClient(test.right)
ParameterTypeDescription
connectionServerConnectionLogux 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.

server.auth(async ({ userId, token }) => {
  const user = await findUserByToken(token)
  return !!user && userId === user.id
})
ParameterTypeDescription
authenticatorAuthenticator<H>The authentication callback.

TestServer#channel(pattern, callbacks)

Define the channel.

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 })
  }
})
ParameterTypeDescription
patternstringPattern for channel name.
callbacksChannelCallbacks<A, D, P, H>Callback during subscription process.
ParameterTypeDescription
patternRegExpRegular expression for channel name.
callbacksChannelCallbacks<A, D, P, H>Callback during subscription process.

Type templates for TypeScript:

TemplatesTypeDescription
PobjectType for ctx.params.
DobjectType for ctx.data.
ALoguxSubscribeActionlogux/subscribe Action’s type.

TestServer#connect(userId, opts?)

Create and connect client.

server = new TestServer()
let client = await server.connect('10')
ParameterTypeDescription
userIdstringUser ID.
opts ?TestClientOptionsOther options.

Returns Promise<TestClient>.

TestServer#debugError(error)

Send runtime error stacktrace to all clients.

process.on('uncaughtException', e => {
  server.debugError(e)
})
ParameterTypeDescription
errorErrorRuntime error instance.

TestServer#destroy()

Stop server and unbind all listeners.

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

Returns Promise<void>. Promise when all listeners will be removed.

TestServer#listen()

Start WebSocket server and listen for clients.

Returns Promise<void>. When the server has been bound.

TestServer#on(event, listener)

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

  • error: server error during action processing.
  • fatal: server error during loading.
  • clientError: wrong client behaviour.
  • connected: new client was connected.
  • disconnected: client was disconnected.
  • preadd: action is going to be added to the log. The best place to set reasons.
  • add: action was added to the log.
  • clean: action was cleaned from the log.
  • processed: action processing was finished.
  • subscribed: channel initial data was loaded.
server.on('error', error => {
  trackError(error)
})
ParameterTypeDescription
event'fatal' | 'clientError'The event name.
listener(err: Error) => voidThe listener function.
ParameterTypeDescription
event'error'The event name.
listener(err: Error, action: Action, meta: ServerMeta) => voidError listener.
ParameterTypeDescription
event'connected' | 'disconnected'The event name.
listener(client: ServerClient) => voidClient listener.
ParameterTypeDescription
event'authenticated'The event name.
listener(client: ServerClient, latencyMilliseconds: number) => voidClient listener.
ParameterTypeDescription
event'preadd' | 'add' | 'clean' | 'backendSent'The event name.
listener(action: Action, meta: ServerMeta) => voidAction listener.
ParameterTypeDescription
event'processed' | 'backendGranted' | 'backendProcessed'The event name.
listener(action: Action, meta: ServerMeta, latencyMilliseconds: number) => voidProcessing listener.
ParameterTypeDescription
event'subscribed'The event name.
listener(action: LoguxSubscribeAction, meta: ServerMeta, latencyMilliseconds: number) => voidSubscription listener.
ParameterTypeDescription
event'subscriptionCancelled'The event name.
listener() => voidEvent listener.

Returns Unsubscribe. Unbind listener from event.

TestServer#otherChannel(callbacks)

Set callbacks for unknown channel subscription.

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'
    }
  }
})
ParameterTypeDescription
callbacksChannelCallbacks <LoguxSubscribeAction, D, [string], H>Callback during subscription process.

Type templates for TypeScript:

TemplatesTypeDescription
DobjectType for ctx.data.

TestServer#otherType(callbacks)

Define callbacks for actions, which type was not defined by any Server#type. Useful for proxy or some hacks.

Without this settings, server will call Server#unknownType on unknown type.

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)
  }
})
ParameterTypeDescription
callbacksActionCallbacks<Action, D, H>Callbacks for actions with this type.

Type templates for TypeScript:

TemplatesTypeDescription
DobjectType for ctx.data.

TestServer#process(action, meta?)

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

ParameterTypeDescription
actionActionNew 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.

server.on('add', (action, meta) => {
  if (meta.server === server.nodeId) {
    sendToOtherServers(action, meta)
  }
})
onReceivingFromOtherServer((action, meta) => {
  server.sendAction(action, meta)
})
ParameterTypeDescription
actionActionNew action.
metaServerMetaAction’s metadata.

TestServer#type(name, callbacks)

Define action type’s callbacks.

server.type('CHANGE_NAME', {
  access (ctx, action, meta) {
    return action.user === ctx.userId
  },
  resend (ctx, action) {
    return { channel: `user/${ action.user }` }
  }
  process (ctx, action, meta) {
    if (isFirstOlder(lastNameChange(action.user), meta)) {
      return db.changeUserName({ id: action.user, name: action.name })
    }
  }
})
ParameterTypeDescription
nameA['type']The action’s type.
callbacksActionCallbacks<A, D, H>Callbacks for actions with this type.
ParameterTypeDescription
actionCreatorACAction creator function.
callbacksActionCallbacks<ReturnType<AC>, D, H>Callbacks for action created by creator.

Type templates for TypeScript:

TemplatesTypeDescription
AActionAction’s type.
DobjectType for ctx.data.
TemplatesTypeDescription
ACActionCreatorAction creator function.
DobjectType for ctx.data.

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

Undo action from client.

if (couldNotFixConflict(action, meta)) {
  server.undo(meta)
}
ParameterTypeDescription
metaServerMetaThe action’s metadata.
reason ?stringOptional code for reason. Default is 'error'
extra ?objectExtra fields to logux/undo action.

Returns Promise<void>. 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, Logux will call it automatically.

server.otherType({
  access (ctx, action, meta) {
    if (action.type.startsWith('myapp/')) {
      return proxy.access(action, meta)
    } else {
      server.unknownType(action, meta)
    }
  }
})
ParameterTypeDescription
actionActionThe action with unknown type.
metaServerMetaAction’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({
  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'
    }
  }
})
ParameterTypeDescription
actionLoguxSubscribeActionThe subscribe action.
metaServerMetaAction’s metadata.

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.

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.

it('tests log', () => {
  const log = TestTime.getLog()
})
ParameterTypeDescription
opts ?TestLogOptionsLog options.

Returns TestLog.

TestTime#lastId

Last letd number in log’s nodeId.

Type: number.

TestTime#nextLog(opts?)

Return next test log in same time.

it('tests 2 logs', () => {
  const time = new TestTime()
  const log1 = time.nextLog()
  const log2 = time.nextLog()
})
ParameterTypeDescription
opts ?TestLogOptionsLog options.

Returns TestLog.


Functions

eachStoreCheck(test)

Pass all common tests for Logux store to callback.

import { eachStoreCheck } from '@logux/core'

eachStoreCheck((desc, creator) => {
  it(desc, creator(() => new CustomStore()))
})
ParameterTypeDescription
test(name: string, testCreator: (storeCreator: () => LogStore) => () => void) => voidCallback to create tests in your test framework.

filterMeta(meta)

Remove all non-allowed keys from meta.

ParameterTypeDescription
metaServerMetaMeta to remove keys.

Returns ServerMeta. Meta with removed keys.

isFirstOlder(firstMeta, secondMeta)

Compare time, when log entries were created.

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

import { isFirstOlder } from '@logux/core'
if (isFirstOlder(lastBeep, meta) {
  beep(action)
  lastBeep = meta
}
ParameterTypeDescription
firstMetaMeta | undefinedSome action’s metadata.
secondMetaMeta | undefinedOther action’s metadata.

Returns boolean.

parseId(id)

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

import { parseId } from '@logux/core'
const { userId, clientId } = parseId(meta.id)
ParameterTypeDescription
idstringAction or Node ID

Returns IDComponents.


Variables

ALLOWED_META

List of meta keys permitted for clients.

const { ALLOWED_META } = require('@logux/server')
async function outMap (action, meta) {
  const filtered = { }
  for (const i in meta) {
    if (ALLOWED_META.includes(i)) {
      filtered[i] = meta[i]
    }
  }
  return [action, filtered]
}

Type: string[].


Types

Action

PropertyTypeDescription
typestringAction type name.

ActionCallbacks

Type: { access: Authorizer<A, D, H>, finally?: ActionFinally<A, D, H>, process?: Processor<A, D, H>, resend?: Resender<A, D, H> }.

ActionCreator(args)

ParameterType
argsany

Returns Action.

ActionCreator#toString()

Returns string.

ActionFinally(ctx, action, meta)

Callback which will be run on the end of action/subscription processing or on an error.

ParameterTypeDescription
ctxContext<D, H>Information about node, who create this action.
actionAThe action data.
metaServerMetaThe action metadata.

ActionIterator(action, meta)

ParameterType
actionAction
metaServerMeta

Returns boolean | void.

ActionListener(action, meta)

ParameterType
actionAction
metaServerMeta

ActionReporter

Type: { action: Action, meta: ServerMeta }.

AnyAction

Type: Action & { [extra: string]: any }.

AuthenticationReporter

Type: { connectionId: string, nodeId: string, subprotocol: string }.

Authenticator(user)

The authentication callback.

ParameterType
userAuthenticatorOptions<H>

Returns boolean | Promise<boolean>. true if credentials was correct

AuthenticatorOptions

Type: { client: ServerClient, cookie: { [name: string]: string }, headers: H, token: string, userId: string }.

Authentificator(nodeId, token, headers)

ParameterType
nodeIdstring
tokenstring
headersH | { }

Returns Promise<boolean>.

Authorizer(ctx, action, meta)

Check does user can do this action.

ParameterTypeDescription
ctxContext<D, H>Information about node, who create this action.
actionAThe action data.
metaServerMetaThe action metadata.

Returns boolean | Promise<boolean>. true if client are allowed to use this action.

BaseServerOptions

PropertyTypeDescription
backend ?stringURL to PHP, Ruby on Rails, or other backend to process actions and authentication.
cert ?stringSSL certificate or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.
controlMask ?stringCIDR masks for IP address, where control requests could came from.
controlSecret ?stringSecret to control the server.
env ?'production' | 'development'Development or production server mode. By default, it will be taken from NODE_ENV environment variable. On empty NODE_ENV it will be 'development'.
host ?stringIP-address to bind server. Default is 127.0.0.1.
id ?stringCustom 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.
pid ?numberProcess ID, to display in reporter.
ping ?numberMilliseconds since last message to test connection by sending ping. Default is 10000.
port ?numberPort to bind server. It will create HTTP server manually to connect WebSocket server to it. Default is 31337.
redis ?stringURL to Redis for Logux Server Pro scaling.
root ?stringApplication root to load files and show errors. Default is process.cwd().
server ?HTTPServerHTTP server to connect WebSocket server to it. Same as in ws.Server.
store ?LogStoreStore to save log. Will be {@link @logux/core:MemoryStore}, by default.
subprotocol ?stringServer current application subprotocol version in SemVer format.
supports ?stringnpm’s version requirements for client subprotocol version.
time ?TestTimeTest time to test server.
timeout ?numberTimeout in milliseconds to disconnect connection. Default is 20000.

ChannelAuthorizer(ctx, action, meta)

Channel authorizer callback

ParameterTypeDescription
ctxChannelContext<D, P, H>Information about node, who create this action.
actionAThe action data.
metaServerMetaThe action metadata.

Returns boolean | Promise<boolean>. true if client are allowed to subscribe to this channel.

ChannelCallbacks

Type: { access: ChannelAuthorizer<A, D, P, H>, filter?: FilterCreator<A, D, P, H>, finally?: ChannelFinally<A, D, P, H>, load?: ChannelLoader<A, D, P, H> }.

ChannelFilter(ctx, action, meta)

Channel filter callback

ParameterTypeDescription
ctxContext<{ }, H>Information about node, who create this action.
actionActionThe action data.
metaServerMetaThe action metadata.

Returns boolean. Should action be sent to client.

ChannelFinally(ctx, action, meta)

Callback which will be run on the end of subscription processing or on an error.

ParameterTypeDescription
ctxChannelContext<D, P, H>Information about node, who create this action.
actionAThe action data.
metaServerMetaThe action metadata.

ChannelLoader(ctx, action, meta)

Send actions with current state.

ParameterTypeDescription
ctxChannelContext<D, P, H>Information about node, who create this action.
actionAThe action data.
metaServerMetaThe action metadata.

Returns SendBackActions | Promise<SendBackActions>. Promise during current actions loading.

CleanReporter

Type: { actionId: ID }.

Criteria

PropertyTypeDescription
id ?IDRemove reason only for action with id.
maxAdded ?numberRemove reason only for actions with lower added.
minAdded ?numberRemove reason only for actions with bigger added.
olderThan ?MetaRemove reason only older than specific action.
youngerThan ?MetaRemove reason only younger than specific action.

EmptyHeaders

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

Filter(action, meta)

ParameterType
actionAction
metaMeta

Returns Promise<boolean>.

FilterCreator(ctx, action, meta)

Generates custom filter for channel’s actions.

ParameterTypeDescription
ctxChannelContext<D, P, H>Information about node, who create this action.
actionAThe action data.
metaServerMetaThe action metadata.

Returns Promise<ChannelFilter<H>> | ChannelFilter<H> | void. Actions filter.

GetOptions

PropertyTypeDescription
order ?'created' | 'added'Sort entries by created time or when they was added to current log.

GetProcessor

Type: { safe?: boolean, request: (request: object) => Response }.

ID

Action unique ID accross all nodes.

"1564508138460 380:R7BNGAP5:px3-J3oc 0"

Type: string.

IDComponents

Type: { clientId: string, nodeId: string, userId: string | undefined }.

LastSynced

PropertyTypeDescription
receivednumberThe added value of latest received event.
sentnumberThe added value of latest sent event.

Logger

Type: { error: (details: object, message: string) => void, fatal: (details: object, message: string) => void, info: (details: object, message: string) => void, warn: (details: object, message: string) => void }.

LogOptions

PropertyTypeDescription
nodeIdstringUnique current machine name.
storeSStore for log.

LoguxAction

Type: LoguxSubscribeAction | LoguxUnsubscribeAction | LoguxProcessedAction | LoguxUndoAction.

LoguxAnySubscribeAction

Type: LoguxSubscribeAction & { [key: string]: any }.

LoguxErrorOptions

Type: { bruteforce: void, timeout: number, unknown-message: string, wrong-credentials: void, wrong-format: string, wrong-protocol: Versions, wrong-subprotocol: Versions }.

LoguxProcessedAction

Type: { id: ID, type: 'logux/processed' }.

LoguxSubscribeAction

Type: { channel: string, since?: { id: string, time: number }, type: 'logux/subscribe' }.

LoguxUndoAction

Type: { id: ID, reason?: string, type: 'logux/undo' }.

LoguxUnsubscribeAction

Type: { channel: string, type: 'logux/unsubscribe' }.

Mapper(action, meta)

ParameterType
actionAction
metaMeta

Returns Promise<[Action, Meta]>.

Message

Type: ['error', keyof LoguxErrorOptions, any] | ['connect', number, string, number, object] | ['connected', number, string, [number, number], object] | ['ping', number] | ['pong', number] | ['sync', number, object, object] | ['synced', number] | ['debug', 'error', string] | ['headers', object].

Meta

PropertyTypeDescription
addednumberSequence number of action in current log. Log fills it.
idIDAction unique ID. Log sets it automatically.
keepLast ?stringSet value to reasons and this reason from old action.
reasonsstring[]Why action should be kept in log. Action without reasons will be removed.
subprotocol ?stringSet code as reason and remove this reasons from previous actions.
timenumberAction created time in current node time. Milliseconds since UNIX epoch.

NodeOptions

PropertyTypeDescription
auth ?Authentificator<H>Function to check client credentials.
fixTime ?booleanDetect difference between client and server and fix time in synchronized actions.
inFilter ?FilterFunction to filter actions from remote node. Best place for access control.
inMap ?MapperMap function to change remote node’s action before put it to current log.
outFilter ?FilterFilter function to select actions to synchronization.
outMap ?MapperMap function to change action before sending it to remote client.
ping ?numberMilliseconds since last message to test connection by sending ping.
subprotocol ?stringApplication subprotocol version in SemVer format.
timeout ?numberTimeout in milliseconds to wait answer before disconnect.
token ?string | TokenGeneratorClient credentials. For example, access token.

Page

PropertyTypeDescription
entries[Action, Meta][]Pagination page.
next ?() => Promise<Page>

PostProcessor

Type: { command: (command: object, request: object) => Promise<void>, isValid: (command: object) => false }.

Processor(ctx, action, meta)

Action business logic.

ParameterTypeDescription
ctxContext<D, H>Information about node, who create this action.
actionAThe action data.
metaServerMetaThe action metadata.

Returns void | Promise<void>. Promise when processing will be finished.

ReconnectOptions

PropertyTypeDescription
attempts ?numberMaximum reconnecting attempts.
maxDelay ?numberMaximum delay between reconnecting.
minDelay ?numberMinimum delay between reconnecting.

Reporter

Type: (event: E, payload: ReportersArguments[E]) => void.

ReportersArguments

Type: { add: 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?: ID, connectionId?: string, err: Error, fatal?: true, nodeId?: string }, listen: { backend: string, cert: boolean, controlMask: string, controlSecret: string, environment: 'production' | 'development', host: string, loguxServer: string, nodeId: string, notes: object, port: string, redis: string, server: boolean, subprotocol: string, supports: string }, 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[], node?: string, nodes?: string[], user?: string, users?: string[] }.

Resender(ctx, action, meta)

Return object with keys for meta to resend action to other users.

ParameterTypeDescription
ctxContext<D, H>Information about node, who create this action.
actionAThe action data.
metaServerMetaThe action metadata.

Returns Resend | Promise<Resend>. Meta’s keys.

Response

Type: { body: string, header?: { [name: string]: string } }.

SendBackActions

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

ServerMeta

Extends Meta.

PropertyTypeDescription
addednumberSequence number of action in current log. Log fills it.
idIDAction unique ID. Log sets it automatically.
keepLast ?stringSet value to reasons and this reason from old action.
reasonsstring[]Why action should be kept in log. Action without reasons will be removed.
subprotocol ?stringSet code as reason and remove this reasons from previous actions.
timenumberAction created time in current node time. Milliseconds since UNIX epoch.
channel ?stringAll nodes subscribed to channel will receive the action.
channels ?string[]All nodes subscribed to listed channels will receive the action.
client ?stringAll nodes with listed client ID will receive the action.
clients ?string[]All nodes with listed client IDs will receive the action.
node ?stringNode with listed node ID will receive the action.
nodes ?string[]All nodes with listed node IDs will receive the action.
serverstringNode ID of the server received the action.
status ?'waiting' | 'processed' | 'error'Action processing status
user ?stringAll nodes with listed user ID will receive the action.
users ?string[]All nodes with listed user IDs will receive the action.

ServerOptions

Extends BaseServerOptions.

PropertyTypeDescription
backend ?stringURL to PHP, Ruby on Rails, or other backend to process actions and authentication.
cert ?stringSSL certificate or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.
controlMask ?stringCIDR masks for IP address, where control requests could came from.
controlSecret ?stringSecret to control the server.
env ?'production' | 'development'Development or production server mode. By default, it will be taken from NODE_ENV environment variable. On empty NODE_ENV it will be 'development'.
host ?stringIP-address to bind server. Default is 127.0.0.1.
id ?stringCustom 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.
pid ?numberProcess ID, to display in reporter.
ping ?numberMilliseconds since last message to test connection by sending ping. Default is 10000.
port ?numberPort to bind server. It will create HTTP server manually to connect WebSocket server to it. Default is 31337.
redis ?stringURL to Redis for Logux Server Pro scaling.
root ?stringApplication root to load files and show errors. Default is process.cwd().
server ?HTTPServerHTTP server to connect WebSocket server to it. Same as in ws.Server.
store ?LogStoreStore to save log. Will be {@link @logux/core:MemoryStore}, by default.
subprotocol ?stringServer current application subprotocol version in SemVer format.
supports ?stringnpm’s version requirements for client subprotocol version.
time ?TestTimeTest time to test server.
timeout ?numberTimeout in milliseconds to disconnect connection. Default is 20000.
logger ?'human' | 'json' | LoggerLogger with custom settings.
reporter ?ReporterCustom reporter for process/errors. You should use it only for test purposes or unavoidable hacks.
reporterStream ?{ write: (str: string) => void }Stream to be used by reporter to write log.

SubscriptionReporter

Type: { actionId: ID, channel: string }.

TestClientOptions

Type: { cookie?: object, headers?: object, subprotocol?: string, token?: string }.

TestLogOptions

PropertyTypeDescription
nodeIdstringUnique log name.
storeLogStoreStore for log. Will use MemoryStore by default.

TestServerOptions

PropertyTypeDescription
backend ?stringURL to PHP, Ruby on Rails, or other backend to process actions and authentication.
cert ?stringSSL certificate or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.
controlMask ?stringCIDR masks for IP address, where control requests could came from.
controlSecret ?stringSecret to control the server.
env ?'production' | 'development'Development or production server mode. By default, it will be taken from NODE_ENV environment variable. On empty NODE_ENV it will be 'development'.
host ?stringIP-address to bind server. Default is 127.0.0.1.
id ?stringCustom 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.
pid ?numberProcess ID, to display in reporter.
ping ?numberMilliseconds since last message to test connection by sending ping. Default is 10000.
port ?numberPort to bind server. It will create HTTP server manually to connect WebSocket server to it. Default is 31337.
redis ?stringURL to Redis for Logux Server Pro scaling.
root ?stringApplication root to load files and show errors. Default is process.cwd().
server ?HTTPServerHTTP server to connect WebSocket server to it. Same as in ws.Server.
store ?LogStoreStore to save log. Will be {@link @logux/core:MemoryStore}, by default.
time ?TestTimeTest time to test server.
timeout ?numberTimeout in milliseconds to disconnect connection. Default is 20000.
auth ?falseDisable built-in auth.
logger ?'human'Print logs in human readable format
reporter ?ReporterLow-level API to server logs for tests.
reporterStream ?{ write: (str: string) => void }Stream to be used by reporter to write log.
subprotocol ?string
supports ?string

TokenGenerator()

Returns string | Promise<string>.

Versions

Type: { supported: string, used: string }.