Join chat

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

PropertyTypeDescription
connectionConnectionConnection to remote node.
logLogLogux log instance to be synchronized.
nodeIdstringUnique current machine name.
optionsobject?Synchronization options.
options.authauthCallback?Function to check client credentials.
options.credentialsobject?Client credentials. For example, access token.
options.fixTimeboolean?Detect difference between client and server and fix time in synchronized actions.
options.inFilterfilter?Function to filter actions from remote node. Best place for access control.
options.inMapmapper?Map function to change remote node’s action before put it to current log.
options.outFilterfilter?Filter function to select actions to synchronization.
options.outMapmapper?Map function to change action before sending it to remote client.
options.pingnumber?Milliseconds since last message to test connection by sending ping.
options.subprotocolstring?Application subprotocol version in SemVer format.
options.timeoutnumber?Timeout in milliseconds to wait answer before disconnect.

BaseNode#authenticated

Type: boolean.

Did we finish remote node authentication.

BaseNode#connected

Type: boolean.

Is synchronization in process.

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

BaseNode#connection

Type: Connection.

Connection used to communicate to remote node.

BaseNode#lastReceived

Type: number.

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

BaseNode#lastSent

Type: number.

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

BaseNode#localNodeId

Type: string.

Unique current machine name.

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

BaseNode#localProtocol

Type: number.

Array with major and minor versions of used protocol.

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

BaseNode#log

Type: Log.

Log for synchronization.

BaseNode#minProtocol

Type: number.

Minimum version of Logux protocol, which is supported.

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

BaseNode#options

Type: object.

Synchronization options.

BaseNode#remoteNodeId

Type: string | undefined.

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

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

BaseNode#remoteProtocol

Type: number | undefined.

Array with major and minor versions of remote node protocol.

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

BaseNode#remoteSubprotocol

Type: string | undefined.

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()
}

BaseNode#state

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

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')
  }
})

BaseNode#catch(listener)

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

PropertyTypeDescription
listenererrorListenerThe listener function.
node.catch(error => {
  console.error(error)
})

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.
PropertyTypeDescription
event"state" | "connect" | "error" | "clientError" | "debug"Event name.
listenerlistenerThe listener function.

Returns function. Unbind listener from event.

node.on('clientError', error => {
  logError(error)
})

BaseNode#waitFor(state)

Return Promise until sync will have specific state.

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

PropertyTypeDescription
statestringThe expected synchronization state value.

Returns Promise. Promise until specific state.

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

Base class for browser API to be extended in CrossTabClient.

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

PropertyTypeDescription
optsobjectClient options.
opts.allowDangerousProtocolboolean?Do not show warning when using ws:// in production.
opts.attemptsnumber?Maximum reconnection attempts.
opts.credentialsany?Client credentials for authentication.
opts.maxDelaynumber?Maximum delay between reconnections.
opts.minDelaynumber?Minimum delay between reconnections.
opts.pingnumber?Milliseconds since last message to test connection by sending ping.
opts.prefixstring?Prefix for IndexedDB database to run multiple Logux instances in the same browser.
opts.serverstring | ConnectionServer URL.
opts.storeStore?Store to save log data. IndexedStore by default (if available)
opts.subprotocolstringClient subprotocol version in SemVer format.
opts.timeTestTime?Test time to test client.
opts.timeoutnumber?Timeout in milliseconds to break connection.
opts.userIdnumber | string | falseUser ID. Pass false if no user.
import Client from '@logux/client/client'

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

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

Client#clientId

Type: string.

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

Client#log

Type: Log.

Client events log.

app.log.keep(customKeeper)

Client#node

Type: ClientNode.

Node instance to synchronize logs.

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

Client#nodeId

Type: string.

Unique Logux node ID.

console.log('Client ID: ', app.nodeId)

Client#options

Type: object.

Client options.

console.log('Connecting to ' + app.options.server)

Client#tabId

Type: string.

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

app.log.add(action, { tab: app.tabId })

Client#clean()

Clear stored data. Removes action log from IndexedDB.

Returns Promise. Promise when all data will be removed.

signout.addEventListener('click', () => {
  app.clean()
})

Client#destroy()

Disconnect and stop synchronization.

shutdown.addEventListener('click', () => {
  app.destroy()
})

Client#on(event, listener)

Subscribe for synchronization events. It implements Nano Events API. Supported events:

  • preadd: action is going to be added (in current tab).
  • add: action has been added to log (by any tab).
  • clean: action has been removed from log (by any tab).
PropertyTypeDescription
event"preadd" | "add" | "clean"The event name.
listenerlistenerThe listener function.

Returns function. Unbind listener from event.

app.on('add', (action, meta) => {
  dispatch(action)
})

Client#start()

Connect to server and reconnect on any connection problem.

app.start()

Client node in synchronization pair.

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

Extends BaseNode.

PropertyTypeDescription
connectionConnectionConnection to remote node.
logLogLogux log instance to be synchronized.
nodeIdstringUnique current machine name.
optionsobject?Synchronization options.
options.authauthCallback?Function to check server credentials.
options.credentialsobject?Client credentials. For example, access token.
options.fixTimeboolean?Detect difference between client and server and fix time in synchronized actions.
options.inFilterfilter?Function to filter actions from server. Best place for permissions control.
options.inMapmapper?Map function to change remote node’s action before put it to current log.
options.outFilterfilter?Filter function to select actions to synchronization.
options.outMapmapper?Map function to change action before sending it to remote client.
options.pingnumber?Milliseconds since last message to test connection by sending ping.
options.subprotocolstring?Application subprotocol version in SemVer format.
options.timeoutnumber?Timeout in milliseconds to wait answer before disconnect.
import { ClientNode } from 'logux-core'
const connection = new BrowserConnection(url)
const node = new ClientNode(nodeId, log, connection)

ClientNode#authenticated

Type: boolean.

Did we finish remote node authentication.

ClientNode#connected

Type: boolean.

Is synchronization in process.

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

ClientNode#connection

Type: Connection.

Connection used to communicate to remote node.

ClientNode#lastReceived

Type: number.

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

ClientNode#lastSent

Type: number.

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

ClientNode#localNodeId

Type: string.

Unique current machine name.

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

ClientNode#localProtocol

Type: number.

Array with major and minor versions of used protocol.

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

ClientNode#log

Type: Log.

Log for synchronization.

ClientNode#minProtocol

Type: number.

Minimum version of Logux protocol, which is supported.

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

ClientNode#options

Type: object.

Synchronization options.

ClientNode#remoteNodeId

Type: string | undefined.

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

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

ClientNode#remoteProtocol

Type: number | undefined.

Array with major and minor versions of remote node protocol.

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

ClientNode#remoteSubprotocol

Type: string | undefined.

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()
}

ClientNode#state

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

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')
  }
})

ClientNode#catch(listener)

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

PropertyTypeDescription
listenererrorListenerThe listener function.
node.catch(error => {
  console.error(error)
})

ClientNode#destroy()

Shut down the connection and unsubscribe from log events.

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

ClientNode#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.
PropertyTypeDescription
event"state" | "connect" | "error" | "clientError" | "debug"Event name.
listenerlistenerThe listener function.

Returns function. Unbind listener from event.

node.on('clientError', error => {
  logError(error)
})

ClientNode#waitFor(state)

Return Promise until sync will have specific state.

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

PropertyTypeDescription
statestringThe expected synchronization state value.

Returns Promise. Promise until specific state.

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

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

Connection#connected

Type: boolean.

Is connection is enabled.

Connection#connect()

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

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

Returns Promise. Promise until connection will be established.

Connection#disconnect(reason?)

Finish current connection.

After disconnection, connection could be started again by Connection#connect.

PropertyTypeDescription
reason("error" | "timeout" | "destroy")?Disconnection reason. It will not be sent to other machine.

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.
PropertyTypeDescription
event"connecting" | "connect" | "disconnect" | "message" | "error"Event.
listenerfunctionThe listener function.

Returns function. Unbind listener from event.

Connection#send(message)

Send message to connection.

PropertyTypeDescription
messageMessageThe message to be sent.

Low-level browser API for Logux.

Instead of Client, this class prevents conflicts between Logux instances in different tabs on single browser.

Extends Client.

PropertyTypeDescription
optsobjectClient options.
opts.allowDangerousProtocolboolean?Do not show warning when using ws:// in production.
opts.attemptsnumber?Maximum reconnection attempts.
opts.credentialsany?Client credentials for authentication.
opts.maxDelaynumber?Maximum delay between reconnections.
opts.minDelaynumber?Minimum delay between reconnections.
opts.pingnumber?Milliseconds since last message to test connection by sending ping.
opts.prefixstring?Prefix for IndexedDB database to run multiple Logux instances in the same browser.
opts.serverstring | ConnectionServer URL.
opts.storeStore?Store to save log data. IndexedStore by default (if available)
opts.subprotocolstringClient subprotocol version in SemVer format.
opts.timeTestTime?Test time to test client.
opts.timeoutnumber?Timeout in milliseconds to break connection.
opts.userIdnumber | string | falseUser ID. Pass false if no user.
import CrossTabClient from '@logux/client/cross-tab-client'

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

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

CrossTabClient#clientId

Type: string.

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

CrossTabClient#connected

Type: boolean.

Is leader tab connected to server.

CrossTabClient#log

Type: Log.

Client events log.

app.log.keep(customKeeper)

CrossTabClient#node

Type: ClientNode.

Node instance to synchronize logs.

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

CrossTabClient#nodeId

Type: string.

Unique Logux node ID.

console.log('Client ID: ', app.nodeId)

CrossTabClient#options

Type: object.

Client options.

console.log('Connecting to ' + app.options.server)

CrossTabClient#role

Type: "leader" | "follower" | "candidate".

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

app.on('role', () => {
  console.log('Tab role:', app.role)
})

CrossTabClient#state

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

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

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

CrossTabClient#tabId

Type: string.

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

app.log.add(action, { tab: app.tabId })

CrossTabClient#clean()

Clear stored data. Removes action log from IndexedDB.

Returns Promise. Promise when all data will be removed.

signout.addEventListener('click', () => {
  app.clean()
})

CrossTabClient#destroy()

Disconnect and stop synchronization.

shutdown.addEventListener('click', () => {
  app.destroy()
})

CrossTabClient#on(event, listener)

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

  • preadd: action is going to be added (in current tab).
  • add: action has been added to log (by any tab).
  • clean: action has been removed from log (by any tab).
  • role: tab role has been changed.
  • state: leader tab synchronization state has been changed.
PropertyTypeDescription
event"preadd" | "add" | "clean" | "role" | "state"The event name.
listenerlistenerThe listener function.

Returns function. Unbind listener from event.

app.on('add', (action, meta) => {
  dispatch(action)
})

CrossTabClient#start()

Connect to server and reconnect on any connection problem.

app.start()

IndexedDB store for Logux log.

Extends Store.

PropertyTypeDescription
namestring?Database name to run multiple Logux instances on same web page.
import IndexedStore from '@logux/client/indexed-store'
const client = new CrossTabClient({
  …,
  store: new IndexedStore()
})

IndexedStore#add(action, meta)

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

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

IndexedStore#byId(id)

Return action by action ID.

PropertyTypeDescription
idstringAction ID.

Returns Promise<Entry | Nope>. Promise with array of action and metadata.

IndexedStore#changeMeta(id, diff)

Change action metadata.

PropertyTypeDescription
diffobjectObject with values to change in action metadata.
idstringAction ID.

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

IndexedStore#clean()

Remove all database and data from IndexedDB.

Returns Promise. Promise for end of removing

afterEach(() => this.store.clean())

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

PropertyTypeDescription
optsobjectQuery options.

Returns Promise<Page>. Promise with first Page.

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

IndexedStore#getLastSynced()

Get added values for latest synchronized received/sent events.

Returns Promise<number>. Promise with LastSynced.

IndexedStore#remove(id)

Remove action from store.

PropertyTypeDescription
idstringAction ID.

Returns Promise<Entry | false>. Promise with entry if action was in store.

IndexedStore#removeReason(reason, criteria, callback)

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

PropertyTypeDescription
callbacklistenerCallback for every removed action.
criteriaobjectActions criteria.
reasonstringThe reason name.

Returns Promise. Promise when cleaning will be finished.

IndexedStore#setLastSynced(values)

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

PropertyTypeDescription
valuesLastSyncedObject with latest sent or received values.

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


Two paired loopback connections.

PropertyTypeDescription
delaynumber?Delay for connection and send events.
import { LocalPair } from 'logux-core''
const pair = new LocalPair()
const client = new ClientNode(pair.left)
const server = new ServerNode(pair.right)

LocalPair#delay

Type: number.

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

LocalPair#left

Type: Connection.

First connection. Will be connected to LocalPair#right one after Connection#connect.

new ClientNode(pair.left)

LocalPair#right

Type: Connection.

Second connection. Will be connected to LocalPair#left one after Connection#connect.

new ServerNode(pair.right)

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.

PropertyTypeDescription
optsobjectOptions.
opts.nodeIdstring | numberUnique current machine name.
opts.storeStoreStore for log.
import Log from 'logux-core/log'
const log = new Log({
  store: new MemoryStore(),
  nodeId: 'client:134'
})

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

Log#nodeId

Type: string | number.

Unique node ID. It is used in action IDs.

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.

PropertyTypeDescription
actionActionThe new action.
metaMeta?Open structure for action metadata.

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

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

Log#byId(id)

Does log already has action with this ID.

PropertyTypeDescription
idstringAction ID.

Returns Promise<Entry | Nope>. Promise with entry.

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

Log#changeMeta(id, diff)

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

PropertyTypeDescription
diffobjectObject with values to change in action metadata.
idstringAction ID.

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

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

Log#each(opts?, callback)

Iterates through all actions, from last to first.

Return false from callback if you want to stop iteration.

PropertyTypeDescription
callbackiteratorFunction will be executed on every action.
optsobject?Iterator options.

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

log.each((action, meta) => {
  if (compareTime(meta.id, lastBeep) <= 0) {
    return false;
  } else if (action.type === 'beep') {
    beep()
    lastBeep = meta.id
    return false;
  }
})

Log#generateId()

Generate next unique action ID.

Returns string. Unique action ID.

const id = log.generateId()

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.
PropertyTypeDescription
event"preadd" | "add" | "clean"The event name.
listenerlistenerThe listener function.

Returns function. Unbind listener from event.

const unbind = log.on('add', (action, meta) => {
  if (action.type === 'beep') beep()
})
function disableBeeps () {
  unbind()
}

Log#removeReason(reason, criteria?)

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

PropertyTypeDescription
criteriaobject?Actions criteria.
reasonstringReason’s name.

Returns Promise. Promise when cleaning will be finished.

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

Logux error in logs synchronization.

PropertyTypeDescription
optionsanyThe error option.
receivedboolean?Was error received from remote node.
typestringThe error code.
if (error.name === 'LoguxError') {
  console.log('Server throws: ' + error.description)
}

LoguxError.describe(type, options)

Return a error description by it code.

PropertyTypeDescription
optionsanyThe errors options depends on error code.
typestringThe error code.

Returns string. Human-readable error description.

errorMessage(msg) {
  console.log(LoguxError.describe(msg[1], msg[2]))
}

LoguxError#description

Type: string.

Human-readable error description.

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

LoguxError#name

Type: string.

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

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

LoguxError#options

Type: any.

Error options depends on error type.

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

LoguxError#received

Type: boolean.

Was error received from remote client.

LoguxError#type

Type: string.

The error code.

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

Redux store with Logux extensions.

LoguxStore#client

Type: CrossTabClient.

Logux synchronization client.

LoguxStore#log

Type: Log.

The Logux log.

LoguxStore#dispatch(action)

Add action to log with Redux compatible API.

Use dispatchLocal, dispatchCrossTab or dispatchSync instead.

PropertyTypeDescription
actionobjectA plain object representing “what changed”.

Returns object. For convenience, the same action object you dispatched.

PropertyTypeDescription
crossTabdispatchCrossTabAdd sync action to log and update store state. This action will be visible for all tabs.
localdispatchLocalAdd sync action to log and update store state. This action will be visible only for current tab.
syncdispatchSyncAdd sync action to log and update store state. This action will be visible for server and all browser tabs.

LoguxStore#getState()

Reads the state tree managed by the store.

Returns any. The current state tree of your application.

LoguxStore#observable()

Interoperability point for observable/reactive libraries.

Returns Observable. A minimal observable of state changes.

LoguxStore#on(event, listener)

Subscribe for store events.

PropertyTypeDescription
event"change"The event name.
listenerchangeListenerThe listener function.

Returns function. Unbind listener from event.

store.on('change', (state, prevState, action, meta) => {
  console.log(state, prevState, action, meta)
})

LoguxStore#replaceReducer(nextReducer)

Replaces the reducer currently used by the store to calculate the state.

PropertyTypeDescription
nextReducerfunctionThe reducer for the store to use instead.

LoguxStore#subscribe(listener)

Adds a store change listener.

PropertyTypeDescription
listenerfunctionA callback to be invoked on every new action.

Returns function. A function to remove this change listener.


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.

Extends Store.

import { MemoryStore } from 'logux-core'

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

MemoryStore#add(action, meta)

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

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

PropertyTypeDescription
idstringAction ID.

Returns Promise<Entry | Nope>. Promise with array of action and metadata.

MemoryStore#changeMeta(diff, id)

Change action metadata.

PropertyTypeDescription
diffobjectObject with values to change in action metadata.
idstringAction ID.

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

MemoryStore#clean()

Remove all data from the store.

Returns Promise. Promise when cleaning will be finished.

MemoryStore#get(opts)

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

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

PropertyTypeDescription
optsobjectQuery 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<number>. Promise with LastSynced.

MemoryStore#remove(id)

Remove action from store.

PropertyTypeDescription
idstringAction ID.

Returns Promise<Entry | false>. Promise with entry if action was in store.

MemoryStore#removeReason(callback, criteria, reason)

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

PropertyTypeDescription
callbacklistenerCallback for every removed action.
criteriaobjectActions criteria.
reasonstringThe reason name.

Returns Promise. Promise when cleaning will be finished.

MemoryStore#setLastSynced(values)

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

PropertyTypeDescription
valuesLastSyncedObject with latest sent or received values.

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


Wrapper for Connection for reconnecting it on every disconnect.

Extends Connection.

PropertyTypeDescription
connectionConnectionThe connection to be reconnectable.
optionsobject?Options.
options.attemptsnumber?Maximum reconnecting attempts.
options.maxDelaynumber?Maximum delay between reconnecting.
options.minDelaynumber?Minimum delay between reconnecting.
import { Reconnect } from 'logux-core'
const recon = new Reconnect(connection)
ClientHost(nodeId, log, recon, options)

Reconnect#connected

Type: boolean.

Is connection is enabled.

Reconnect#reconnecting

Type: boolean.

Should we reconnect connection on next connection break. Next Reconnect#connect call will set to true.

function lastTry () {
  recon.reconnecting = false
}

Reconnect#connect()

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

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

Returns Promise. Promise until connection will be established.

Reconnect#destroy()

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

Reconnect#disconnect(reason?)

Finish current connection.

After disconnection, connection could be started again by Connection#connect.

PropertyTypeDescription
reason("error" | "timeout" | "destroy")?Disconnection reason. It will not be sent to other machine.

Reconnect#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.
PropertyTypeDescription
event"connecting" | "connect" | "disconnect" | "message" | "error"Event.
listenerfunctionThe listener function.

Returns function. Unbind listener from event.

Reconnect#send(message)

Send message to connection.

PropertyTypeDescription
messageMessageThe message to be sent.

Every Store class should provide 8 standard methods: add, has, get, remove, changeMeta, removeReason, getLastAdded, getLastSynced, setLastSynced.

See MemoryStore sources for example.

Store#add(action, meta)

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

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

Store#byId(id)

Return action by action ID.

PropertyTypeDescription
idstringAction ID.

Returns Promise<Entry | Nope>. Promise with array of action and metadata.

Store#changeMeta(diff, id)

Change action metadata.

PropertyTypeDescription
diffobjectObject with values to change in action metadata.
idstringAction ID.

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

Store#clean()

Remove all data from the store.

Returns Promise. Promise when cleaning will be finished.

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

PropertyTypeDescription
optsobjectQuery options.

Returns Promise<Page>. Promise with first Page.

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

Store#getLastSynced()

Get added values for latest synchronized received/sent events.

Returns Promise<number>. Promise with LastSynced.

Store#remove(id)

Remove action from store.

PropertyTypeDescription
idstringAction ID.

Returns Promise<Entry | false>. Promise with entry if action was in store.

Store#removeReason(callback, criteria, reason)

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

PropertyTypeDescription
callbacklistenerCallback for every removed action.
criteriaobjectActions criteria.
reasonstringThe reason name.

Returns Promise. Promise when cleaning will be finished.

Store#setLastSynced(values)

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

PropertyTypeDescription
valuesLastSyncedObject with latest sent or received values.

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


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

Use TestTime to create test log.

Extends Log.

PropertyTypeDescription
idnumberLog sequence number created from this test time.
optsobject?Options.
opts.nodeId(string | number)?Unique current machine name.
opts.storeStore?Store for log. Will use MemoryStore by default.
timeTestTimeThis test time.
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

Type: string | number.

Unique node ID. It is used in action IDs.

TestLog#actions()

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

This shortcut works only with MemoryStore. To use it, do not change store type by store option in TestTime.getLog.

Returns Action[]. Log’s action.

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

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.

PropertyTypeDescription
actionActionThe new action.
metaMeta?Open structure for action metadata.

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

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

TestLog#byId(id)

Does log already has action with this ID.

PropertyTypeDescription
idstringAction ID.

Returns Promise<Entry | Nope>. Promise with entry.

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

TestLog#changeMeta(diff, id)

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

PropertyTypeDescription
diffobjectObject with values to change in action metadata.
idstringAction ID.

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

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

TestLog#each(callback, opts?)

Iterates through all actions, from last to first.

Return false from callback if you want to stop iteration.

PropertyTypeDescription
callbackiteratorFunction will be executed on every action.
optsobject?Iterator options.

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

log.each((action, meta) => {
  if (compareTime(meta.id, lastBeep) <= 0) {
    return false;
  } else if (action.type === 'beep') {
    beep()
    lastBeep = meta.id
    return false;
  }
})

TestLog#entries()

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

This shortcut works only with MemoryStore. To use it, do not change store type by store option in TestTime.getLog.

Returns Entry[]. Log’s entries.

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

TestLog#generateId()

Generate next unique action ID.

Returns string. Unique action ID.

const id = log.generateId()

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.
PropertyTypeDescription
event"preadd" | "add" | "clean"The event name.
listenerlistenerThe listener function.

Returns function. Unbind listener from event.

const unbind = log.on('add', (action, meta) => {
  if (action.type === 'beep') beep()
})
function disableBeeps () {
  unbind()
}

TestLog#removeReason(criteria?, reason)

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

PropertyTypeDescription
criteriaobject?Actions criteria.
reasonstringReason’s name.

Returns Promise. Promise when cleaning will be finished.

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

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

Extends LocalPair.

PropertyTypeDescription
delaynumber?Delay for connection and send events.
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])
})

TestPair#delay

Type: number.

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

TestPair#left

Type: Connection.

First connection. Will be connected to LocalPair#right one after Connection#connect.

new ClientNode(pair.left)

TestPair#leftEvents

Type: Array[].

Emitted events from TestPair#left connection.

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

TestPair#leftNode

Type: BaseNode.

Node instance used in this test, connected with TestPair#left.

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

TestPair#leftSent

Type: Message[].

Sent messages from TestPair#left connection.

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

TestPair#right

Type: Connection.

Second connection. Will be connected to LocalPair#left one after Connection#connect.

new ServerNode(pair.right)

TestPair#rightEvents

Type: Array[].

Emitted events from TestPair#right connection.

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

TestPair#rightNode

Type: BaseNode.

Node instance used in this test, connected with TestPair#right.

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

TestPair#rightSent

Type: Message[].

Sent messages from TestPair#right connection.

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

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.

PropertyTypeDescription
receiver("left" | "right")?Wait for specific receiver event.

Returns Promise. Promise until next event.

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

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.

PropertyTypeDescription
optsobject?Log options.

Returns TestLog. Test log in this time.

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

TestTime#lastId

Type: number.

Last used number in log’s nodeId.

TestTime#nextLog(opts?)

Return next test log in same time.

PropertyTypeDescription
optsobject?Log options.

Returns TestLog. Test log in this time.

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

Logux connection for browser WebSocket.

Extends Connection.

PropertyTypeDescription
WSfunction?WebSocket class if you want change implementation.
optsobject?Extra option for WebSocket constructor.
urlstringWebSocket server URL.
import { WsConnection } from 'logux-core'

const connection = new WsConnection('wss://logux.example.com/')
const node = new ClientNode(nodeId, log, connection, opts)

WsConnection#connected

Type: boolean.

Is connection is enabled.

WsConnection#connect()

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

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

Returns Promise. Promise until connection will be established.

WsConnection#disconnect(reason?)

Finish current connection.

After disconnection, connection could be started again by Connection#connect.

PropertyTypeDescription
reason("error" | "timeout" | "destroy")?Disconnection reason. It will not be sent to other machine.

WsConnection#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.
PropertyTypeDescription
event"connecting" | "connect" | "disconnect" | "message" | "error"Event.
listenerfunctionThe listener function.

Returns function. Unbind listener from event.

WsConnection#send(message)

Send message to connection.

PropertyTypeDescription
messageMessageThe message to be sent.

Functions

attention(client)

Highlight tabs on synchronization errors.

PropertyTypeDescription
clientClientObserved Client instance.

Returns function. Unbind attention listener.

import attention from '@logux/client/attention'
attention(client)

badge(client, opts)

Display Logux widget in browser.

PropertyTypeDescription
clientClientObserved Client instance.
optsobjectWidget settings.

Returns function. Unbind badge listener and remove widget from DOM.

import badge from '@logux/client/badge'
import styles from '@logux/client/badge/default'
import messages from '@logux/client/badge/en'

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

confirm(client)

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

PropertyTypeDescription
clientCrossTabClientObserved Client instance.

Returns function. Unbind confirm listener.

import confirm from '@logux/client/confirm'
confirm(client)

createLoguxCreator(config)

Creates Logux client and connect it to Redux createStore function.

PropertyTypeDescription
configobjectLogux Client config.

Returns storeCreator. Redux createStore compatible function.

import { createLoguxCreator } from '@logux/redux/create-logux-store'

const createStore = createLoguxCreator({
  subprotocol: '1.0.0',
  server: process.env.NODE_ENV === 'development'
    ? 'ws://localhost:31337'
    : 'wss://logux.example.com',
  userId: false,  // TODO: We will fill it in next chapter
  credentials: '' // TODO: We will fill it in next chapter
})

const store = createStore(reducer)
store.client.start()

eachStoreCheck(test)

Pass all common tests for Logux store to callback.

PropertyTypeDescription
testcreatorCallback to create tests in your test framework.
import { eachStoreCheck } from 'logux-core'

eachStoreCheck((desc, creator) => {
  it(desc, creator(() => new CustomStore()))
})

favicon(client, links)

Change favicon to show Logux synchronization status.

PropertyTypeDescription
clientCrossTabClientObserved Client instance.
linksobjectSet favicon links.

Returns function. Unbind favicon listener.

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

isFirstOlder(firstMeta, secondMeta)

Compare time, when log entries were created.

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

PropertyTypeDescription
firstMetaMetaSome action’s metadata.
secondMetaMetaOther action’s metadata.

Returns boolean. Is first action is older than second.

import { isFirstOlder } from 'logux-core'
if (isFirstOlder(lastBeep, meta) {
  beep(action)
  lastBeep = meta
}

log(client, messages?)

Display Logux events in browser console.

PropertyTypeDescription
clientCrossTabClientObserved Client instance.
messagesobject?Disable specific message types.

Returns function. Unbind log listener.

import log from '@logux/client/log'
log(client, { add: false })

status(client, callback, options?)

Low-level function to show Logux synchronization status with your custom UI. It is used in badge widget.

PropertyTypeDescription
callbackstatusReceiverStatus callback.
clientClientObserved Client instance.
optionsobject?Options.

Returns function. Unbind status listener.

import status from '@logux/client/status'
status(client, current => {
  updateUI(current)
})

subscribe(subscriber, opts?)

Decorator to add subscribe action on component mount and unsubscribe on unmount.

PropertyTypeDescription
optsobject?Redux options.
subscribersubscriberCallback to return subscribe action properties according to component props.

Returns function. Class wrapper.

import subscribe from '@logux/redux/subscribe'
class User extends React.Component { … }
export default subscribe(({ id }) => `user/${ id }`)(User)

useSubscription(channels, opts)

Hook to subscribe for channel during component render and unsubscribe on component unmount.

PropertyTypeDescription
channelsChannel[]Channels to subscribe.
optsobjectOptions.

Returns boolean. true during data loading.

import useSubscription from '@logux/redux/use-subscription'
import { useSelector } from 'react-redux'

const UserPage = ({ userId }) => {
  const isSubscribing = useSubscription([`user/${ userId }`])
  const user = useSelector(state => state.users.find(i => i.id === userId))
  if (isSubscribing) {
    return <Loader />
  } else {
    return <h1>{ user.name }</h1>
  }
}

Callbacks

authCallback

PropertyTypeDescription
credentialsobjectRemote node credentials.
nodeIdstringUnique ID of remote node instance.

Returns Promise<boolean>. Promise with boolean value.

changeListener

PropertyTypeDescription
actionActionThe new action, which changed the store.
metaMetaAction’s metadata.
prevStateanyThe state before new action.
stateanyCurrent state of the store

checker

PropertyTypeDescription
actionActionThe new action.

creator

PropertyTypeDescription
generatorcreatorThe test creator.
namestringThe test name.

dispatchCrossTab

Add cross-tab action to log and update store state. This action will be visible only for all tabs.

PropertyTypeDescription
actionActionThe new action.
metaMetaAction’s metadata.

Returns Promise. Promise when action will be saved to the log.

store.dispatch.crossTab(
  { type: 'CHANGE_FAVICON', favicon },
  { reasons: ['lastFavicon'] }
).then(meta => {
  store.log.removeReason('lastFavicon', { maxAdded: meta.added - 1 })
})

dispatchLocal

Add local action to log and update store state. This action will be visible only for current tab.

PropertyTypeDescription
actionActionThe new action.
metaMetaAction’s metadata.

Returns Promise. Promise when action will be saved to the log.

store.dispatch.local(
  { type: 'OPEN_MENU' },
  { reasons: ['lastMenu'] }
).then(meta => {
  store.log.removeReason('lastMenu', { maxAdded: meta.added - 1 })
})

dispatchSync

Add sync action to log and update store state. This action will be visible only for server and all browser tabs.

PropertyTypeDescription
actionActionThe new action.
metaMetaAction’s metadata.

Returns Promise. Promise when action will be saved to the log.

store.dispatch.crossTab(
  { type: 'CHANGE_NAME', name },
  { reasons: ['lastName'] }
).then(meta => {
  store.log.removeReason('lastName', { maxAdded: meta.added - 1 })
})

errorListener

PropertyTypeDescription
errorstringThe error description.

filter

PropertyTypeDescription
actionActionNew action from log.
metaMetaNew action metadata.

Returns Promise<boolean>. Promise with true if action should be synchronized with remote log.

generator

PropertyTypeDescription
storeStoreThe store instance.

Returns function. The test function to be used in test framework.

iterator

PropertyTypeDescription
actionActionNext action.
metaMetaNext action’s metadata.

Returns boolean. returning false will stop iteration.

listener

PropertyTypeDescription
actionActionNew action.
metaMetaThe action’s metadata.

mapper

PropertyTypeDescription
actionActionNew action from log.
metaMetaNew action metadata.

Returns Promise<Entry>. Promise with array of changed action and changed metadata.

next

Returns Promise<Page>. Promise with next Page.

statusReceiver

PropertyTypeDescription
detailsobject | undefinedStatus details.
type"synchronized" | "synchronizedAfterWait" | "disconnected" | "wait" | "error" | "connecting" | "connectingAfterWait" | "syncError" | "denied" | "protocolError"Status type.

storeCreator

PropertyTypeDescription
enhancerfunction?Redux middleware.
preloadedStateany?The initial state.
reducerfunctionA function that returns the next state tree, given the current state tree and the action to handle.

Returns LoguxStore. Redux store with Logux extensions.

subscriber

PropertyTypeDescription
propsobjectThe component properties.

Returns string | Subscription. The subscription action properties.


Types

Action

Type: object.

Action from the log.

PropertyTypeDescription
typestringAction type name.
{ type: 'add', id: 'project:12:price' value: 12 }

Channel

Type: string | SubscribeAction.

Entry

Type: Array.

Array with Action and its Meta.

PropertyTypeDescription
0ActionAction’s object.
1MetaAction’s metadata.

LastSynced

Type: object.

The added values for latest synchronized received/sent events.

PropertyTypeDescription
receivedstringThe added value of latest received event.
sentstringThe added value of latest sent event.

Message

Type: Array.

Logux protocol message. It is a array with string of message type in first position and strings/numbers/objects or arrays in next positions.

PropertyTypeDescription
0stringMessage type

Meta

Type: object.

Action’s metadata.

PropertyTypeDescription
addednumberSequence number of action in current log. Log#add will fill it.
idstringAction unique ID. Log#add set it automatically.
keepLaststring?Set code as reason and remove this reasons from previous actions.
reasonsstring[]?Why action should be kept in log. Action without reasons will be removed.
timenumber?Action created time. Milliseconds since UNIX epoch.

Nope

Type: Array.

If entry was not found Log return [null, null].

PropertyTypeDescription
0null
1null

Page

Type: object.

Part of log from Store.

PropertyTypeDescription
entriesEntry[]Pagination page.

SubscribeAction

Type: object.

PropertyTypeDescription
channelstring

Subscription

Type: object.

Details for subscription action.

PropertyTypeDescription
channelstringThe channel name.