Join chat

Extends Component.

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

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

export const App: FC = () => {
  return <>
    <SideMenu />
    <ChannelErrors
      NotFound={NotFoundPage}
      AccessDenied={AccessDeniedPage}
      Error={ServerErrorPage}
    >
      <Layout />
    </ChannelErrors>
  <>
}
ParameterType
props{ AccessDenied?: ComponentType, Error?: ComponentType, NotFound?: ComponentType } | { AccessDenied?: ComponentType, Error?: ComponentType, NotFound?: ComponentType }
ParameterType
props{ AccessDenied?: ComponentType, Error?: ComponentType, NotFound?: ComponentType }
contextany

React.ClientContext

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

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

let client = new CrossTabClient(…)

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

Type: ReactContext.

React.useAuth()

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

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

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

Returns StoreValue.

React.useClient()

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

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

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

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

Returns Client.

React.useFilter(Builder, filter?, opts?)

The way to createFilter in React.

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

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

export const Users = ({ projectId }) => {
  let users = useFilter(User, { projectId })
  return <div>
    {users.list.map(user => <User user={user} />)}
    {users.isLoading && <Loader />}
  </div>
}
ArgumentTypeDescription
BuilderSyncMapBuilderStore class.
filter ?FilterKey-value filter for stores.
opts ?FilterOptionsFilter options.

Returns StoreValue. Filter store to use with map.

React.useSync(Builder, id)

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

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

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

export const UserPage: FC = ({ id }) => {
  let user = useSync(User, id)
  if (user.isLoading) {
    return <Loader />
  } else {
    return <h1>{user.name}</h1>
  }
}
ArgumentTypeDescription
BuilderSyncMapBuilderStore builder.
idstringStore ID.
ArgumentType
BuilderMapBuilder
idstring
argsany[]

Returns SyncMapValue. Store value.


Vue.ChannelErrors

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

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

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

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

Type: Component.

Vue.ChannelErrorsSlotProps

PropertyType
code{ }
error{ }

Vue.ClientKey

Type: InjectionKey.

Vue.ErrorsKey

Type: InjectionKey.

Vue.loguxPlugin(app, client)

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

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

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

app.use(loguxPlugin, client)
ArgumentType
appApp
clientClient

Vue.useAuth(client?)

Returns user's current authentication state and ID.

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

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

export default () => {
  let { isAuthenticated, userId } = useAuth()
  return { isAuthenticated, userId }
}
</script>
ArgumentTypeDescription
client ?ClientLogux Client instance.

Returns { isAuthenticated: ComputedRef, userId: ComputedRef }.

Vue.useClient()

Returns the Logux Client instance.

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

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

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

Returns Client.

Vue.useFilter(Builder, filter?, opts?)

The way to createFilter in Vue.

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

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

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

export default {
  props: ['projectId'],
  setup (props) {
    let users = useFilter(User, { projectId: props.projectId })
    return { users }
  }
}
</script>
ArgumentTypeDescription
BuilderSyncMapBuilderStore class.
filter ?Ref<Filter> | FilterKey-value filter for stores.
opts ?Ref<FilterOptions> | FilterOptionsFilter options.

Returns ReadonlyRef<StoreValue>. Filter store to use with map.

Vue.useSync(Builder, id)

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

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

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

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

export default {
  props: ['id'],
  setup (props) {
    let user = useSync(User, props.id)
    return { user }
  }
}
</script>
ArgumentTypeDescription
BuilderSyncMapBuilderStore builder.
idRef<string> | stringStore ID.
ArgumentType
BuilderMapBuilder
idRef<string> | string
argsany[]

Returns ReadonlyRef<SyncMapValue>. Store value.


Extends Component.

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

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

export const App: FC = () => {
  return <>
    <SideMenu />
    <ChannelErrors
      NotFound={NotFoundPage}
      AccessDenied={AccessDeniedPage}
      Error={ServerErrorPage}
    >
      <Layout />
    </ChannelErrors>
  <>
}
ParameterType
props ?{ AccessDenied?: ComponentType, Error?: ComponentType, NotFound?: ComponentType }
context ?any

Preact.ClientContext

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

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

let client = new CrossTabClient(…)

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

Type: PreactContext.

Preact.useAuth()

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

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

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

Returns StoreValue.

Preact.useClient()

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

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

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

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

Returns Client.

Preact.useFilter(Builder, filter?, opts?)

The way to createFilter in React.

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

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

export const Users = ({ projectId }) => {
  let users = useFilter(User, { projectId })
  return <div>
    {users.list.map(user => <User user={user} />)}
    {users.isLoading && <Loader />}
  </div>
}
ArgumentTypeDescription
BuilderSyncMapBuilderStore class.
filter ?FilterKey-value filter for stores.
opts ?FilterOptionsFilter options.

Returns StoreValue. Filter store to use with map.

Preact.useSync(Builder, id)

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

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

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

export const UserPage: FC = ({ id }) => {
  let user = useSync(User, id)
  if (user.isLoading) {
    return <Loader />
  } else {
    return <h1>{user.name}</h1>
  }
}
ArgumentTypeDescription
BuilderSyncMapBuilderStore builder.
idstringStore ID.
ArgumentType
BuilderMapBuilder
idstring
argsany[]

Returns SyncMapValue. Store value.


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

import { Client } from '@logux/client'

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

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

Client#clientId

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

Type: string.

Client#connected

Is leader tab connected to server.

Type: boolean.

Client#log

Client events log.

client.log.add(action)

Type: Log.

Client#node

Node instance to synchronize logs.

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

Type: ClientNode.

Client#nodeId

Unique Logux node ID.

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

Type: string.

Client#options

Client options.

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

Type: ClientOptions.

Client#state

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

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

Type: NodeState.

Client#tabId

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

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

Type: string.

Client#changeUser(userId, token?)

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

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

You need manually chang user ID in all browser tabs.

ArgumentTypeDescription
userIdstringThe new user ID.
token ?stringCredentials for new user.

Client#clean()

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

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

Returns Promise. Promise when all data will be removed.

Client#destroy()

Disconnect and stop synchronization.

shutdown.addEventListener('click', () => {
  client.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).
  • user: user ID was changed.

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

client.on('add', (action, meta) => {
  dispatch(action)
})
ArgumentTypeDescription
event"state"The event name.
listener() => voidThe listener function.
ArgumentType
event"preadd" | "add" | "clean"
listenerClientActionListener
ArgumentType
event"user"
listener(userId: string) => void

Returns Unsubscribe. Unbind listener from event.

Client#start()

Connect to server and reconnect on any connection problem.

client.start()

Client#sync(action, meta?)

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

showLoader()
client.sync(
  { type: 'CHANGE_NAME', name }
).then(() => {
  hideLoader()
}).catch(error => {
  hideLoader()
  showError(error.action.reason)
})
ArgumentTypeDescription
actionAnyActionThe action
meta ?Partial<ClientMeta>Optional meta.

Returns Promise<ClientMeta>. Promise for server processing.

Client#type(type, listener, opts?)

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

client.type('rename', (action, meta) => {
  name = action.name
})
ArgumentTypeDescription
typeAction["type"]Action’s type.
listenerClientActionListener
opts ?{ event?: "preadd" | "add" | "clean", id?: string }

Returns Unsubscribe. Unbind listener from event.

Client#waitFor(state)

Wait for specific state of the leader tab.

await client.waitFor('synchronized')
hideLoader()
ArgumentTypeDescription
stateNodeStateState name

Returns Promise.

Extends Client.

Low-level browser API for Logux.

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

import { CrossTabClient } from '@logux/client'

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

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

CrossTabClient#clientId

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

Type: string.

CrossTabClient#connected

Is leader tab connected to server.

Type: boolean.

CrossTabClient#isLocalStorage

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

Type: boolean.

CrossTabClient#log

Client events log.

client.log.add(action)

Type: Log.

CrossTabClient#node

Node instance to synchronize logs.

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

Type: ClientNode.

CrossTabClient#nodeId

Unique Logux node ID.

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

Type: string.

CrossTabClient#options

Client options.

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

Type: ClientOptions.

CrossTabClient#role

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

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

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

CrossTabClient#state

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

Type: NodeState.

CrossTabClient#tabId

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

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

Type: string.

CrossTabClient#changeUser(userId, token?)

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

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

You need manually chang user ID in all browser tabs.

ArgumentTypeDescription
userIdstringThe new user ID.
token ?stringCredentials for new user.

CrossTabClient#clean()

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

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

Returns Promise. Promise when all data will be removed.

CrossTabClient#destroy()

Disconnect and stop synchronization.

shutdown.addEventListener('click', () => {
  client.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.
  • user: user ID was changed.
client.on('add', (action, meta) => {
  dispatch(action)
})
ArgumentTypeDescription
event"state" | "role"The event name.
listener() => voidThe listener function.
ArgumentType
event"user"
listener(userId: string) => void
ArgumentType
event"preadd" | "add" | "clean"
listenerClientActionListener

Returns Unsubscribe. Unbind listener from event.

CrossTabClient#start()

Connect to server and reconnect on any connection problem.

client.start()

CrossTabClient#sync(action, meta?)

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

showLoader()
client.sync(
  { type: 'CHANGE_NAME', name }
).then(() => {
  hideLoader()
}).catch(error => {
  hideLoader()
  showError(error.action.reason)
})
ArgumentTypeDescription
actionAnyActionThe action
meta ?Partial<ClientMeta>Optional meta.

Returns Promise<ClientMeta>. Promise for server processing.

CrossTabClient#type(type, listener, opts?)

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

client.type('rename', (action, meta) => {
  name = action.name
})
ArgumentTypeDescription
typeAction["type"]Action’s type.
listenerClientActionListener
opts ?{ event?: "preadd" | "add" | "clean", id?: string }

Returns Unsubscribe. Unbind listener from event.

CrossTabClient#waitFor(state)

Wait for specific state of the leader tab.

await client.waitFor('synchronized')
hideLoader()
ArgumentTypeDescription
stateNodeStateState name

Returns Promise.

Extends LogStore.

IndexedDB store for Logux log.

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

IndexedStore#name

Database name.

Type: string.

attention(client)

Highlight tabs on synchronization errors.

import { attention } from '@logux/client'
attention(client)
ArgumentTypeDescription
clientClientObserved Client instance.

Returns () => void. Unbind listener.

badge(client, opts)

Display Logux widget in browser.

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

badge(client, {
 messages: badgeEn,
 styles: {
   ...badgeStyles,
   synchronized: { backgroundColor: 'green' }
 },
 position: 'top-left'
})
ArgumentTypeDescription
clientClientObserved Client instance.
optsBadgeOptionsWidget settings.

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

buildNewSyncMap(client, Builder, values)

Send create action and build store instance.

import { buildNewSyncMap } from '@logux/client'

let userStore = buildNewSyncMap(client, User, {
  id: nanoid(),
  login: 'test'
})
ArgumentTypeDescription
clientClientLogux Client instance.
BuilderSyncMapBuilderStore class from defineSyncMap.
valuesSyncMapValues & { id: string }Initial value.

Returns Promise<SyncMapStore>. Promise with store instance.

changeSyncMap(store, diff)

Change keys in the store’s value.

import { changeSyncMap } from '@logux/client'

showLoader()
await changeSyncMap(userStore, { name: 'New name' })
hideLoader()
ArgumentTypeDescription
storeSyncMapStoreStore’s instance.
diffPartial<Omit<SyncMapValues,"id">>Store’s changes.
ArgumentType
storeSyncMapStore
keyExclude<keyof SyncMapValues,"id">
valueSyncMapValues[Exclude<keyof SyncMapValues,"id">]

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

changeSyncMapById(client, Builder, id, diff)

Change store without store instance just by store ID.

import { changeSyncMapById } from '@logux/client'

let userStore = changeSyncMapById(client, User, 'user:4hs2jd83mf', {
  name: 'New name'
})
ArgumentTypeDescription
clientClientLogux Client instance.
BuilderSyncMapBuilderStore class from defineSyncMap.
idstring | { id: string }Store’s ID.
diffPartial<SyncMapValues>Store’s changes.
ArgumentType
clientClient
BuilderSyncMapBuilder
idstring | { id: string }
keykeyof SyncMapValues
valueSyncMapValues[keyof SyncMapValues]

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

confirm(client)

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

import { confirm } from '@logux/client'
confirm(client)
ArgumentTypeDescription
clientClientObserved Client instance.

Returns () => void. Unbind listener.

createFilter(client, Builder, filter?, opts?)

Load list of SyncMap with simple key-value requirements.

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

import { createFilter } from '@logux/client'
import { getValue } from 'nanostores'

import { User } from '../store'

let usersInProject = createFilter(client, User, { projectId })
await usersInProject.loading
console.log(getValue(usersInProject))
ArgumentTypeDescription
clientClientLogux Client.
BuilderSyncMapBuilderStore class from defineSyncMap.
filter ?FilterKey-value to filter stores.
opts ?FilterOptionsLoading options.

Returns FilterStore.

createSyncMap(client, Builder, values)

Send create action to the server or to the log.

Server will create a row in database on this action. FilterStore will update the list.

import { createSyncMap } from '@logux/client'

showLoader()
await createSyncMap(client, User, {
  id: nanoid(),
  login: 'test'
})
hideLoader()
ArgumentTypeDescription
clientClientLogux Client instance.
BuilderSyncMapBuilderStore class from defineSyncMap.
valuesSyncMapValues & { id: string }Initial value.

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

defineSyncMap(plural, opts?)

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

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

import { defineSyncMap } from '@logux/client'

export const User = defineSyncMap<{
  login: string,
  name?: string,
  isAdmin: boolean
}>('users')
ArgumentTypeDescription
pluralstringPlural store name. It will be used in action type and channel name.
opts ?{ offline?: boolean, remote?: boolean }Options to disable server validation or keep actions in log for offline support.

Returns SyncMapBuilder.

deleteSyncMap(store)

Delete store.

import { deleteSyncMap } from '@logux/client'

showLoader()
await deleteSyncMap(User)
ArgumentTypeDescription
storeSyncMapStoreStore’s instance.

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

deleteSyncMapById(client, Builder, id)

Delete store without store instance just by store ID.

import { deleteSyncMapById } from '@logux/client'

showLoader()
await deleteSyncMapById(client, User, 'user:4hs2jd83mf')
ArgumentTypeDescription
clientClientLogux Client instance.
BuilderSyncMapBuilderStore class from defineSyncMap.
idstring | { id: string }Store’s ID.

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

encryptActions(client, secret, opts?)

Encrypt actions before sending them to server.

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

import { encryptActions } from '@logux/client'
encryptActions(client, localStorage.getItem('userPassword'), {
  ignore: ['server/public'] // action.type to not be encrypted
})
ArgumentTypeDescription
clientClientObserved Client instance.
secretstringPassword for encryption.
opts ?{ ignore: string[] }Encryption options.

Returns () => void. Unbind listener.

favicon(client, links)

Change favicon to show Logux synchronization status.

import { favicon } from '@logux/client'
favicon(client, {
  normal: '/favicon.ico',
  offline: '/offline.ico',
  error: '/error.ico'
})
ArgumentTypeDescription
clientClientObserved Client instance.
linksFaviconLinksFavicon links.

Returns () => void. Unbind listener.

log(client, messages?)

Display Logux events in browser console.

import { log } from '@logux/client'
log(client, { ignoreActions: ['user/add'] })
ArgumentTypeDescription
clientClientObserved Client instance.
messages ?LogMessagesDisable specific message types.

Returns () => void. Unbind listener.

request(action, opts)

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

Useful for simple actions like signin or signup.

import { request } from '@logux/client'

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

request(action, {
  server: 'wss://example.com',
  subprotocol: '1.0.0
}).then(response => {
  saveToken(response.token)
}).catch(error => {
  showError(error.action.reason)
})
ArgumentTypeDescription
actionAnyActionAction which we need to send to the server.
optsRequestOptions

Returns Promise<Action>. Action of server response.


Extends Client.

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

import { TestClient } from '@logux/client'

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

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

  expect(user.name).toEqual('New name')
})
ParameterTypeDescription
userIdstringUser ID.
opts ?TestClientOptionsOther options.

TestClient#clientId

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

Type: string.

TestClient#connected

Is leader tab connected to server.

Type: boolean.

TestClient#log

Client events log.

client.log.add(action)

Type: TestLog.

TestClient#node

Node instance to synchronize logs.

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

Type: ClientNode.

TestClient#nodeId

Unique Logux node ID.

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

Type: string.

TestClient#options

Client options.

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

Type: ClientOptions.

TestClient#pair

Connection between client and server.

Type: TestPair.

TestClient#server

Virtual server to test client.

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

Type: TestServer.

TestClient#state

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

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

Type: NodeState.

TestClient#tabId

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

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

Type: string.

TestClient#changeUser(userId, token?)

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

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

You need manually chang user ID in all browser tabs.

ArgumentTypeDescription
userIdstringThe new user ID.
token ?stringCredentials for new user.

TestClient#clean()

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

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

Returns Promise. Promise when all data will be removed.

TestClient#connect()

Connect to virtual server.

await client.connect()

Returns Promise. Promise until connection will be established.

TestClient#destroy()

Disconnect and stop synchronization.

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

TestClient#disconnect()

Disconnect from virtual server.

client.disconnect()

TestClient#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).
  • user: user ID was changed.

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

client.on('add', (action, meta) => {
  dispatch(action)
})
ArgumentTypeDescription
event"state"The event name.
listener() => voidThe listener function.
ArgumentType
event"preadd" | "add" | "clean"
listenerClientActionListener
ArgumentType
event"user"
listener(userId: string) => void

Returns Unsubscribe. Unbind listener from event.

TestClient#sent(test)

Collect actions sent by client during the test call.

let answers = await client.sent(async () => {
  client.log.add({ type: 'local' })
})
expect(actions).toEqual([{ type: 'local' }])
ArgumentTypeDescription
test() => void | PromiseFunction, where do you expect action will be received

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

TestClient#start()

Connect to server and reconnect on any connection problem.

client.start()

TestClient#subscribed(channel)

Does client subscribed to specific channel.

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

Returns boolean. Does client has an active subscription.

TestClient#sync(action, meta?)

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

showLoader()
client.sync(
  { type: 'CHANGE_NAME', name }
).then(() => {
  hideLoader()
}).catch(error => {
  hideLoader()
  showError(error.action.reason)
})
ArgumentTypeDescription
actionAnyActionThe action
meta ?Partial<ClientMeta>Optional meta.

Returns Promise<ClientMeta>. Promise for server processing.

TestClient#type(type, listener, opts?)

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

client.type('rename', (action, meta) => {
  name = action.name
})
ArgumentTypeDescription
typeAction["type"]Action’s type.
listenerClientActionListener
opts ?{ event?: "preadd" | "add" | "clean", id?: string }

Returns Unsubscribe. Unbind listener from event.

TestClient#waitFor(state)

Wait for specific state of the leader tab.

await client.waitFor('synchronized')
hideLoader()
ArgumentTypeDescription
stateNodeStateState name

Returns Promise.

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

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 })
})
ArgumentTypeDescription
actionActionThe new action.
meta ?Partial<ClientMeta>Open structure for action metadata.

Returns Promise<false | ClientMeta>. 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 })
}
ArgumentTypeDescription
idstringAction ID.

Returns Promise<[null, null] | [Action, ClientMeta]>. 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' })
ArgumentTypeDescription
idstringAction ID.
diffPartial<ClientMeta>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;
  }
})
ArgumentTypeDescription
callbackActionIteratorFunction will be executed on every action.
ArgumentTypeDescription
optsGetOptionsIterator options.
callbackActionIteratorFunction will be executed on every action.
ArgumentType
callbackActionIterator

Returns Promise. 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, ClientMeta][].

TestLog#generateId()

Generate next unique action ID.

const id = log.generateId()

Returns string. Unique ID for action.

TestLog#keepActions()

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

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

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.

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

log.on('preadd', (action, meta) => {
  if (action.type === 'beep') {
    meta.reasons.push('test')
  }
})
ArgumentTypeDescription
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 })
}
ArgumentTypeDescription
reasonstringThe reason name.
criteria ?CriteriaCriteria to select action for reason removing.

Returns Promise. Promise when cleaning will be finished.

TestLog#type(type, listener, opts?)

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

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

const unbind = log.type('beep', (action, meta) => {
  beep()
})
function disableBeeps () {
  unbind()
}
ArgumentTypeDescription
typestringAction’s type.
listenerActionListener
opts ?{ event?: "preadd" | "add" | "clean", id?: string }

Returns Unsubscribe. Unbind listener from event.

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.

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.

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']]
ArgumentTypeDescription
receiver ?"left" | "right"Wait for specific receiver event.

Returns Promise<TestPair>. Promise until next event.

Virtual server to test client.

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

TestServer#log

All actions received from the client.

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

Type: TestLog.

TestServer#freezeProcessing(test)

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

await client.server.freezeProcessing(() => {
  user.rename('Another name')
  expect(user.nameIsSaving).toBe(true)
})
await delay(10)
expect(user.nameIsSaving).toBe(false)
ArgumentTypeDescription
test() => PromiseFunction, where server will not send logux/processed.

Returns Promise. Promise until test will be finished.

TestServer#onChannel(channel, response)

Define server’s responses for specific channel.

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

  client.server.onChannel('users/10', [
    { type: 'users/name', userId: 10, value: 'New name' }
  ])
  let user = new UserStore(client, '10')
  await delay(10)
  expect(user.name).toEqual('New name')
ArgumentTypeDescription
channelstringThe channel name.
responseAnyAction | AnyAction[] | [AnyAction, Partial<ClientMeta>][]Actions to send back on subscription.

TestServer#resend(type, resend)

Set channels for client’s actions.

ArgumentTypeDescription
typeAction["type"]Action type.
resend(action: Action, meta: ClientMeta) => string | string[]Callback returns channel name.

TestServer#sendAll(action, meta?)

Send action to all connected clients.

client.server.sendAll(action)
ArgumentTypeDescription
actionActionAction.
meta ?Partial<ClientMeta>Action‘s meta.

Returns Promise.

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

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

client.server.undoAction(
  { type: 'rename', userId: '10', value: 'Bad name' }
)
user.rename('Good name')
user.rename('Bad name')
await delay(10)
expect(user.name).toEqual('Good name')
ArgumentTypeDescription
actionActionAction to be undone on receiving
reason ?stringOptional code for reason. Default is 'error'.
extra ?objectExtra fields to logux/undo action.

TestServer#undoNext(reason?, extra?)

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

client.server.undoNext()
user.rename('Another name')
await delay(10)
expect(user.name).toEqual('Old name')
ArgumentTypeDescription
reason ?stringOptional code for reason. Default is 'error'.
extra ?objectExtra fields to logux/undo action.

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()
})
ArgumentTypeDescription
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()
})
ArgumentTypeDescription
opts ?TestLogOptionsLog options.

Returns TestLog.

emptyInTest(Builder)

Disable loader for filter for this builder.

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

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

afterEach(() => {
  cleanStores(User)
})
ArgumentTypeDescription
BuilderSyncMapBuilderStore builder.

prepareForTest(client, Builder, value)

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

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

import { User } from '../store'

let client = new TestClient('10')

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

afterEach(() => {
  cleanStores(User)
})
ArgumentTypeDescription
clientClientTestClient instance.
BuilderSyncMapBuilderStore builder.
valueOmit<SyncMapValues,"id"> & { id?: string }Store values.
ArgumentTypeDescription
clientClientTestClient instance.
BuilderMapBuilderStore builder.
valueOmit<object,"id"> & { id?: string }Store values.

Returns SyncMapStore. The mocked store.


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

ParameterTypeDescription
nodeIdstringUnique current machine name.
logLogLogux log instance to be synchronized.
connectionConnectionConnection to remote node.
options ?NodeOptionsSynchronization 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.

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

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.

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: EmptyHeaders | object.

BaseNode#remoteNodeId

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

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

Type: string.

BaseNode#remoteProtocol

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

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

Type: number.

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.

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

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)
})
ArgumentTypeDescription
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)
})
ArgumentTypeDescription
event"state" | "connect" | "debug" | "headers"Event name.
listener() => voidThe listener function.
ArgumentType
event"error" | "clientError"
listener(error: LoguxError) => void
ArgumentType
event"debug"
listener(type: "error", data: string) => void
ArgumentType
event"headers"
listener(headers: object) => 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()
ArgumentTypeDescription
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')
ArgumentTypeDescription
stateNodeStateThe expected synchronization state value.

Returns Promise. Promise until specific state.

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

Connection#connected

Is connection is enabled.

Type: boolean.

Connection#destroy

Disconnect and unbind all even listeners.

Type: () => void.

Connection#connect()

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

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

Returns Promise. Promise until connection will be established.

Connection#disconnect(reason?)

Finish current connection.

ArgumentTypeDescription
reason ?"timeout" | "error" | "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.
ArgumentTypeDescription
event"connecting" | "connect" | "disconnect"Event name.
listener() => voidEvent listener.
ArgumentType
event"error"
listener(error: Error) => void
ArgumentType
event"message"
listener(msg: Message) => void
ArgumentType
event"disconnect"
listener(reason: string) => void

Returns Unsubscribe. Unbind listener from event.

Connection#send(message)

Send message to connection.

ArgumentTypeDescription
messageMessageThe message to be sent.

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
optsLogOptionsLog options.

Log#nodeId

Unique node ID. It is used in action IDs.

Type: string.

Log#store

Log store.

Type: LogStore.

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 })
})
ArgumentTypeDescription
actionActionThe new action.
meta ?Partial<ClientMeta>Open structure for action metadata.

Returns Promise<false | ClientMeta>. 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 })
}
ArgumentTypeDescription
idstringAction ID.

Returns Promise<[null, null] | [Action, ClientMeta]>. 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' })
ArgumentTypeDescription
idstringAction ID.
diffPartial<ClientMeta>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;
  }
})
ArgumentTypeDescription
callbackActionIteratorFunction will be executed on every action.
ArgumentTypeDescription
optsGetOptionsIterator options.
callbackActionIteratorFunction will be executed on every action.
ArgumentType
callbackActionIterator

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

Log#generateId()

Generate next unique action ID.

const id = log.generateId()

Returns string. 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.

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

log.on('preadd', (action, meta) => {
  if (action.type === 'beep') {
    meta.reasons.push('test')
  }
})
ArgumentTypeDescription
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 })
}
ArgumentTypeDescription
reasonstringThe reason name.
criteria ?CriteriaCriteria to select action for reason removing.

Returns Promise. Promise when cleaning will be finished.

Log#type(type, listener, opts?)

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

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

const unbind = log.type('beep', (action, meta) => {
  beep()
})
function disableBeeps () {
  unbind()
}
ArgumentTypeDescription
typestringAction’s type.
listenerActionListener
opts ?{ event?: "preadd" | "add" | "clean", id?: string }

Returns Unsubscribe. Unbind listener from event.

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, ClientMeta][].

MemoryStore#add(action, meta)

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

ArgumentTypeDescription
actionAnyActionThe action to add.
metaClientMetaAction’s metadata.

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

MemoryStore#byId(id)

Return action by action ID.

ArgumentTypeDescription
idstringAction ID.

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

MemoryStore#changeMeta(id, diff)

Change action metadata.

ArgumentTypeDescription
idstringAction ID.
diffPartial<ClientMeta>Object with values to change in action metadata.

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

MemoryStore#clean()

Remove all data from the store.

Returns Promise. Promise when cleaning will be finished.

MemoryStore#get(opts?)

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

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

ArgumentTypeDescription
opts ?GetOptionsQuery options.

Returns Promise<LogPage>. Promise with first page.

MemoryStore#getLastAdded()

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

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

MemoryStore#getLastSynced()

Get added values for latest synchronized received/sent events.

Returns Promise<LastSynced>. Promise with added values

MemoryStore#remove(id)

Remove action from store.

ArgumentTypeDescription
idstringAction ID.

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

MemoryStore#removeReason(reason, criteria, callback)

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

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

Returns Promise. Promise when cleaning will be finished.

MemoryStore#setLastSynced(values)

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

ArgumentTypeDescription
valuesLastSyncedObject with latest sent or received values.

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

Extends Connection.

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

import { ClientNode, Reconnect } from '@logux/core'
const recon = new Reconnect(connection)
new ClientNode(nodeId, log, recon, options)
ParameterTypeDescription
connectionConnectionThe connection to be re-connectable.
options ?ReconnectOptionsRe-connection options.

Reconnect#attempts

Fails attempts since the last connected state.

Type: number.

Reconnect#connected

Is connection is enabled.

Type: boolean.

Reconnect#connecting

Are we in the middle of connecting.

Type: boolean.

Reconnect#connection

Wrapped connection.

Type: Connection.

Reconnect#destroy

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

Type: () => void.

Reconnect#options

Re-connection options.

Type: ReconnectOptions.

Reconnect#reconnecting

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

function lastTry () {
  recon.reconnecting = false
}

Type: boolean.

Reconnect#connect()

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

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

Returns Promise. Promise until connection will be established.

Reconnect#disconnect(reason?)

Finish current connection.

ArgumentTypeDescription
reason ?"timeout" | "error" | "destroy"Disconnection reason.

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.
ArgumentTypeDescription
event"connecting" | "connect" | "disconnect"Event name.
listener() => voidEvent listener.
ArgumentType
event"error"
listener(error: Error) => void
ArgumentType
event"message"
listener(msg: Message) => void
ArgumentType
event"disconnect"
listener(reason: string) => void

Returns Unsubscribe. Unbind listener from event.

Reconnect#send(message)

Send message to connection.

ArgumentTypeDescription
messageMessageThe message to be sent.

defineAction(type)

ArgumentType
typestring
ArgumentType
typestring
creator(args: any[]) => Action

Returns ActionCreator.

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
}
ArgumentTypeDescription
firstMetaClientMeta | undefinedSome action’s metadata.
secondMetaClientMeta | 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)
ArgumentTypeDescription
idstringAction or Node ID

Returns IDComponents.


AbstractActionCreator(args)

ArgumentType
argsany

Returns Action.

AbstractActionCreator#type

Type: string.

Action

PropertyTypeDescription
typestringAction type name.

ActionCreator(args)

ArgumentType
argsany[]

Returns Action.

ActionCreator#type

Type: string.

ActionCreator#match(action)

ArgumentType
actionany

Returns action is Action.

ActionIterator(action, meta)

ArgumentType
actionAction
metaClientMeta

Returns boolean | void.

ActionListener(action, meta)

ArgumentType
actionAction
metaClientMeta

AnyAction

PropertyType
typestring
[extra: string]any

Auth

Auth store. Use createAuth to create it.

Type: MapStore.

Authentificator(nodeId, token, headers)

ArgumentType
nodeIdstring
tokenstring
headers{ } | object

Returns Promise<boolean>.

BadgeMessages

PropertyType
deniedstring
disconnectedstring
errorstring
protocolErrorstring
sendingstring
syncErrorstring
synchronizedstring
waitstring

BadgeOptions

PropertyTypeDescription
duration ?numberSynchronized state duration. Default is 3000.
messagesBadgeMessagesWidget text for different states.
position ?"top-left" | "top-center" | "top-right" | "middle-left" | "middle-center" | "middle-right" | "bottom-left" | "bottom-center" | "bottom-right"Widget position. Default is bottom-right.
stylesBadgeStylesInline styles for different states.

BadgeStyles

PropertyType
baseobject
connectingobject
disconnectedobject
errorobject
icon{ disconnected: string, error: string, protocolError: string, sending: string, synchronized: string, wait: string }
protocolErrorobject
sendingobject
synchronizedobject
textobject
waitobject

ChannelDeniedError

Type: LoguxUndoError.

ChannelError

Type: ChannelNotFoundError | ChannelDeniedError | ChannelServerError.

ChannelNotFoundError

Type: LoguxUndoError.

ChannelServerError

Type: LoguxUndoError.

ClientActionListener(action, meta)

ArgumentType
actionAction
metaClientMeta

ClientMeta

PropertyTypeDescription
noAutoReason ?booleanDisable setting timeTravel reason.
sync ?booleanThis action should be synchronized with other browser tabs and server.
tab ?stringAction should be visible only for browser tab with the same client.tabId.

Extends BaseNode.

Client node in synchronization pair.

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

import { ClientNode } from '@logux/core'
const connection = new BrowserConnection(url)
const node = new ClientNode(nodeId, log, connection)
ParameterTypeDescription
nodeIdstringUnique current machine name.
logLogLogux log instance to be synchronized.
connectionConnectionConnection to remote node.
options ?NodeOptionsSynchronization options.

ClientNode#authenticated

Did we finish remote node authentication.

Type: boolean.

ClientNode#connected

Is synchronization in process.

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

Type: boolean.

ClientNode#connection

Connection used to communicate to remote node.

Type: Connection.

ClientNode#initializing

Promise for node data initial loadiging.

Type: Promise.

ClientNode#lastReceived

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

Type: number.

ClientNode#lastSent

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

Type: number.

ClientNode#localNodeId

Unique current machine name.

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

Type: string.

ClientNode#localProtocol

Used Logux protocol.

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

Type: number.

ClientNode#log

Log for synchronization.

Type: Log.

ClientNode#minProtocol

Minimum version of Logux protocol, which is supported.

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

Type: number.

ClientNode#options

Synchronization options.

Type: NodeOptions.

ClientNode#remoteHeaders

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

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

Type: EmptyHeaders | object.

ClientNode#remoteNodeId

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

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

Type: string.

ClientNode#remoteProtocol

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

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

Type: number.

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

ClientNode#state

Current synchronization state.

  • disconnected: no connection.
  • connecting: connection was started and we wait for node answer.
  • sending: new actions was sent, waiting for answer.
  • synchronized: all actions was synchronized and we keep connection.
node.on('state', () => {
  if (node.state === 'sending') {
    console.log('Do not close browser')
  }
})

Type: NodeState.

ClientNode#timeFix

Time difference between nodes.

Type: number.

ClientNode#catch(listener)

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

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

Returns Unsubscribe. Unbind listener from event.

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.
  • headers: headers was receive from remote node.
node.on('clientError', error => {
  logError(error)
})
ArgumentTypeDescription
event"state" | "connect" | "debug" | "headers"Event name.
listener() => voidThe listener function.
ArgumentType
event"error" | "clientError"
listener(error: LoguxError) => void
ArgumentType
event"debug"
listener(type: "error", data: string) => void
ArgumentType
event"headers"
listener(headers: object) => void

Returns Unsubscribe. Unbind listener from event.

ClientNode#setLocalHeaders(headers)

Set headers for current node.

if (navigator) {
  node.setLocalHeaders({ language: navigator.language })
}
node.connection.connect()
ArgumentTypeDescription
headersobjectThe data object will be set as headers for current node.

ClientNode#waitFor(state)

Return Promise until sync will have specific state.

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

await node.waitFor('synchronized')
console.log('Everything is synchronized')
ArgumentTypeDescription
stateNodeStateThe expected synchronization state value.

Returns Promise. Promise until specific state.

ClientOptions

PropertyTypeDescription
allowDangerousProtocol ?booleanDo not show warning when using ws:// in production.
attempts ?numberMaximum reconnection attempts. Default is Infinity.
maxDelay ?numberMaximum delay between reconnections. Default is 5000.
minDelay ?numberMinimum delay between reconnections. Default is 1000.
ping ?numberMilliseconds since last message to test connection by sending ping. Default is 10000.
prefix ?stringPrefix for IndexedDB database to run multiple Logux instances in the same browser. Default is logux.
serverstring | ConnectionServer URL.
store ?LogStoreStore to save log data. Default is MemoryStore.
subprotocolstringClient subprotocol version in SemVer format.
time ?TestTimeTest time to test client.
timeout ?numberTimeout in milliseconds to break connection. Default is 20000.
token ?string | TokenGeneratorClient credentials for authentication.
userIdstringUser ID.

CompressedMeta

PropertyType
idnumber | [number, string, number]
timenumber

Criteria

PropertyTypeDescription
id ?stringRemove 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 ?ClientMetaRemove reason only older than specific action.
youngerThan ?ClientMetaRemove reason only younger than specific action.

EmptyHeaders

PropertyType
[key: string]undefined
PropertyTypeDescription
error ?stringError favicon link.
normal ?stringDefault favicon link. By default, it will be taken from current favicon.
offline ?stringOffline favicon link.

Fields

PropertyType
[key: string]any

Filter

Type: { [Key: keyof object]?: object[keyof SyncMapValues] }.

FilterOptions

PropertyType
listChangesOnly ?boolean

FilterStore

PropertyTypeDescription
loadingPromiseWhile store is loading initial data from server or log.

GetOptions

PropertyTypeDescription
index ?stringGet entries with a custom index.
order ?"created" | "added"Sort entries by created time or when they was added to current log.

ID

Action unique ID accross all nodes.

"1564508138460 380:R7BNGAP5:px3-J3oc 0"

Type: string.

IDComponents

PropertyType
clientIdstring
nodeIdstring
userIdstring

LastSynced

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

LoadedSyncMapValue

Type: SyncMapValues & { id: string, isLoading: false }.

Extends Connection.

LocalConnection#connected

Is connection is enabled.

Type: boolean.

LocalConnection#destroy

Disconnect and unbind all even listeners.

Type: () => void.

LocalConnection#connect()

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

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

Returns Promise. Promise until connection will be established.

LocalConnection#disconnect(reason?)

Finish current connection.

ArgumentTypeDescription
reason ?"timeout" | "error" | "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.
ArgumentTypeDescription
event"connecting" | "connect" | "disconnect"Event name.
listener() => voidEvent listener.
ArgumentType
event"error"
listener(error: Error) => void
ArgumentType
event"message"
listener(msg: Message) => void
ArgumentType
event"disconnect"
listener(reason: string) => void

Returns Unsubscribe. Unbind listener from event.

LocalConnection#other()

Returns LocalConnection.

LocalConnection#send(message)

Send message to connection.

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

LogFilter(action, meta)

ArgumentType
actionAction
metaClientMeta

Returns Promise<boolean>.

LogMapper(action, meta)

ArgumentType
actionAction
metaClientMeta

Returns Promise<[AnyAction, ClientMeta]>.

LogMessages

PropertyTypeDescription
add ?booleanDisable action added messages.
clean ?booleanDisable action cleaned messages.
error ?booleanDisable error messages.
ignoreActions ?string[]Disable action messages with specific types.
role ?booleanDisable tab role messages.
state ?booleanDisable connection state messages.
user ?booleanDisable user ID changing.

LogOptions

PropertyTypeDescription
nodeIdstringUnique current machine name.
storeLogStoreStore for log.

LogPage

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

Every Store class should provide 8 standard methods.

LogStore#add(action, meta)

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

ArgumentTypeDescription
actionAnyActionThe action to add.
metaClientMetaAction’s metadata.

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

LogStore#byId(id)

Return action by action ID.

ArgumentTypeDescription
idstringAction ID.

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

LogStore#changeMeta(id, diff)

Change action metadata.

ArgumentTypeDescription
idstringAction ID.
diffPartial<ClientMeta>Object with values to change in action metadata.

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

LogStore#clean()

Remove all data from the store.

Returns Promise. Promise when cleaning will be finished.

LogStore#get(opts?)

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

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

ArgumentTypeDescription
opts ?GetOptionsQuery options.

Returns Promise<LogPage>. Promise with first page.

LogStore#getLastAdded()

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

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

LogStore#getLastSynced()

Get added values for latest synchronized received/sent events.

Returns Promise<LastSynced>. Promise with added values

LogStore#remove(id)

Remove action from store.

ArgumentTypeDescription
idstringAction ID.

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

LogStore#removeReason(reason, criteria, callback)

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

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

Returns Promise. Promise when cleaning will be finished.

LogStore#setLastSynced(values)

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

ArgumentTypeDescription
valuesLastSyncedObject with latest sent or received values.

Returns Promise. 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
typekeyof LoguxErrorOptionsThe error code.
options ?LoguxErrorOptions[keyof LoguxErrorOptions]The error option.
received ?booleanWas error received from remote node.

LoguxError.description(type, options?)

Return a error description by it code.

ArgumentTypeDescription
typekeyof LoguxErrorOptionsThe error code.
options ?LoguxErrorOptions[keyof LoguxErrorOptions]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[keyof LoguxErrorOptions].

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: keyof LoguxErrorOptions.

LoguxErrorOptions

PropertyType
bruteforcevoid
timeoutnumber
unknown-messagestring
wrong-credentialsvoid
wrong-formatstring
wrong-protocolVersions
wrong-subprotocolVersions

Extends Error.

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

import { LoguxNotFoundError } from '@logux/server'

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

LoguxNotFoundError#name

Type: "LoguxNotFoundError".

LoguxProcessedAction

PropertyType
idstring
type"logux/processed"

LoguxSubscribeAction

PropertyType
channelstring
creating ?true
filter ?{ [key: string]: string | number | boolean }
since ?{ id: string, time: number }
type"logux/subscribe"

LoguxSubscribedAction

PropertyType
channelstring
type"logux/subscribed"

LoguxUndoAction

PropertyType
actionAction
idstring
reasonstring
type"logux/undo"

Extends Error.

Error on logux/undo action from the server.

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

LoguxUndoError#action

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

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

Type: LoguxUndoAction.

LoguxUndoError#name

The better way to check error, than instanceof.

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

Type: "LoguxUndoError".

LoguxUnsubscribeAction

PropertyType
channelstring
filter ?{ [key: string]: string | number | boolean }
type"logux/unsubscribe"

Message

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

Meta

PropertyTypeDescription
addednumberSequence number of action in current log. Log fills it.
idstringAction unique ID. Log sets it automatically.
indexes ?string[]Indexes for action quick extraction.
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.
[extra: string]any

NodeOptions

PropertyTypeDescription
auth ?AuthentificatorFunction to check client credentials.
fixTime ?booleanDetect difference between client and server and fix time in synchronized actions.
inFilter ?LogFilterFunction to filter actions from remote node. Best place for access control.
inMap ?LogMapperMap function to change remote node’s action before put it to current log.
outFilter ?LogFilterFilter function to select actions to synchronization.
outMap ?LogMapperMap 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.

NodeState

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

PrepareForTest(client, Builder, value)

ArgumentType
clientClient
BuilderSyncMapBuilder
valueOmit<SyncMapValues,"id"> & { id?: string }
ArgumentType
clientClient
BuilderMapBuilder
valueOmit<object,"id"> & { id?: string }

Returns SyncMapStore.

ReconnectOptions

PropertyTypeDescription
attempts ?numberMaximum reconnecting attempts.
maxDelay ?numberMaximum delay between re-connecting.
minDelay ?numberMinimum delay between re-connecting.

RequestOptions

PropertyTypeDescription
allowDangerousProtocol ?booleanDo not show warning when using ws:// in production.
attempts ?numberMaximum reconnection attempts. Default is Infinity.
maxDelay ?numberMaximum delay between reconnections. Default is 5000.
minDelay ?numberMinimum delay between reconnections. Default is 1000.
ping ?numberMilliseconds since last message to test connection by sending ping. Default is 10000.
prefix ?stringPrefix for IndexedDB database to run multiple Logux instances in the same browser. Default is logux.
serverstring | ConnectionServer URL.
store ?LogStoreStore to save log data. Default is MemoryStore.
subprotocolstringClient subprotocol version in SemVer format.
time ?TestTimeTest time to test client.
timeout ?numberTimeout in milliseconds to break connection. Default is 20000.
token ?string | TokenGeneratorClient credentials for authentication.
userId ?string

StatusListener(current, details)

ArgumentType
current"disconnected" | "connecting" | "synchronized" | "synchronizedAfterWait" | "connectingAfterWait" | "protocolError" | "syncError" | "error" | "denied" | "wait"
detailsError | { action: Action, meta: ClientMeta }

StatusOptions

PropertyTypeDescription
duration ?numberSynchronized state duration. Default is 3000.

SyncMapBuilder(id, args)

ArgumentType
idstring
args[Client] | [Client, Action, ClientMeta, boolean]

Returns MapStore & SyncMapStoreExt.

SyncMapBuilder#offline

Type: boolean.

SyncMapBuilder#plural

Type: string.

SyncMapBuilder#remote

Type: boolean.

SyncMapChangeAction

PropertyType
fieldsPartial<Omit<SyncMapValues,"id">>
idstring
typestring

SyncMapChangedAction

PropertyType
fieldsPartial<Omit<SyncMapValues,"id">>
idstring
typestring

SyncMapCreateAction

PropertyType
fieldsOmit<SyncMapValues,"id">
idstring
typestring

SyncMapCreatedAction

PropertyType
fieldsOmit<SyncMapValues,"id">
idstring
typestring

SyncMapDeleteAction

PropertyType
idstring
typestring

SyncMapDeletedAction

PropertyType
idstring
typestring

SyncMapStore

Type: MapStore & SyncMapStoreExt.

SyncMapStoreExt

PropertyTypeDescription
clientClientLogux Client instance.
createdAt ?ClientMetaMeta from create action if the store was created locally.
loadingPromiseWhile store is loading initial data from server or log.
offlinebooleanDoes store keep data in the log after store is destroyed.
pluralstringName of map class.
remotebooleanDoes store use server to load and save data.

SyncMapTypes

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

SyncMapValue

Type: { id: string, isLoading: true } | LoadedSyncMapValue.

SyncMapValues

PropertyType
[key: string]SyncMapTypes | SyncMapTypes[]

TabID

Type: string.

TestClientOptions

PropertyType
headers ?object
server ?TestServer
subprotocol ?string

TestLogOptions

PropertyTypeDescription
nodeId ?stringUnique log name.
store ?LogStoreStore for log. Will use MemoryStore by default.

TokenGenerator()

Returns string | Promise<string>.

Versions

PropertyType
supportedstring
usedstring

Extends Connection.

Logux connection for browser WebSocket.

import { WsConnection } from '@logux/core'

const connection = new WsConnection('wss://logux.example.com/')
const node = new ClientNode(nodeId, log, connection, opts)
ParameterTypeDescription
urlstringWebSocket server URL.
Class ?any
opts ?anyExtra option for WebSocket constructor.

WsConnection#connected

Is connection is enabled.

Type: boolean.

WsConnection#destroy

Disconnect and unbind all even listeners.

Type: () => void.

WsConnection#ws

WebSocket instance.

Type: WebSocket.

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.

ArgumentTypeDescription
reason ?"timeout" | "error" | "destroy"Disconnection reason.

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.
ArgumentTypeDescription
event"connecting" | "connect" | "disconnect"Event name.
listener() => voidEvent listener.
ArgumentType
event"error"
listener(error: Error) => void
ArgumentType
event"message"
listener(msg: Message) => void
ArgumentType
event"disconnect"
listener(reason: string) => void

Returns Unsubscribe. Unbind listener from event.

WsConnection#send(message)

Send message to connection.

ArgumentTypeDescription
messageMessageThe message to be sent.

ZeroAction

PropertyType
dstring
ivstring
type"0"

ZeroCleanAction

PropertyType
idstring
type"0/clean"

actionEvents(emitter, event, action, meta)

ArgumentType
emitterEmitter
event"preadd" | "add" | "clean"
actionAction
metaClientMeta

badgeEn

English translation for widget.

Type: BadgeMessages.

badgeRu

Russian translation for widget.

Type: BadgeMessages.

badgeStyles

Type: BadgeStyles.

createAuth(client)

Create store with user’s authentication state.

import { createAuth } from '@logux/client'
import { getValue } from 'nanostores'

let auth = createAuth(client)
auth.subscribe(({ isAuthenticated, userId }) => {
  console.log(isAuthenticated, userId)
})
ArgumentTypeDescription
clientClientLogux Client.

Returns Auth.

defineChangedSyncMap(plural)

ArgumentType
pluralstring

Returns ActionCreator.

defineChangeSyncMap(plural)

ArgumentType
pluralstring

Returns ActionCreator.

defineCreatedSyncMap(plural)

ArgumentType
pluralstring

Returns ActionCreator.

defineCreateSyncMap(plural)

ArgumentType
pluralstring

Returns ActionCreator.

defineDeletedSyncMap(plural)

ArgumentType
pluralstring

Returns ActionCreator.

defineDeleteSyncMap(plural)

ArgumentType
pluralstring

Returns ActionCreator.

defineSyncMapActions(plural)

ArgumentType
pluralstring

Returns [ActionCreator, ActionCreator, ActionCreator, ActionCreator, ActionCreator, ActionCreator].

eachStoreCheck(test)

Pass all common tests for Logux store to callback.

import { eachStoreCheck } from '@logux/core'

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

loguxProcessed

Type: ActionCreator.

loguxSubscribe

Type: ActionCreator.

loguxSubscribed

Type: ActionCreator.

loguxUndo(fields)

ArgumentType
fields{ action: Action, id: string, reason: string }

Returns LoguxUndoAction.

loguxUnsubscribe

Type: ActionCreator.

status(client, callback, options?)

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

import { status } from '@logux/client'
status(client, current => {
  updateUI(current)
})
ArgumentTypeDescription
clientClientObserved Client instance.
callbackStatusListener
options ?StatusOptions

Returns () => void. Unbind listener.

track(client, id)

Track for logux/processed or logux/undo answer from server for the cases when Client#sync can’t be used.

client.type('pay', (action, meta) => {
  track(client, id).then(() => {
    console.log('paid')
  }).catch(() => {
    console.log('unpaid')
  })
})
ArgumentTypeDescription
clientLog | ClientLogux Client.
idIDAction ID.

Returns Promise. Promise when action was proccessed.

zero

Type: ActionCreator.

zeroClean

Type: ActionCreator.