Extends Context.

Subscription context.

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

ChannelContext#clientId

Unique persistence client ID.

server.clientIds.get(node.clientId)

Type: string.

ChannelContext#data

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

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

Type: object.

ChannelContext#headers

Client’s headers.

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

Type: object.

ChannelContext#isServer

Was action created by Logux server.

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

Type: boolean.

ChannelContext#nodeId

Unique node ID.

server.nodeIds.get(node.nodeId)

Type: string.

ChannelContext#params

Parsed variable parts of channel pattern.

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

Type: object | string[].

ChannelContext#server

Logux server

Type: Server.

ChannelContext#subprotocol

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

Type: string.

ChannelContext#userId

User ID taken node ID.

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

Type: string.

ChannelContext#isSubprotocol(range)

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

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

Returns boolean. Is version satisfies requirements.

ChannelContext#sendBack(action, meta?)

Send action back to the client.

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

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

ArgumentTypeDescription
actionAnyActionThe action.
meta ?Partial<ServerMeta>Action’s meta.

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

Action context.

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

Context#clientId

Unique persistence client ID.

server.clientIds.get(node.clientId)

Type: string.

Context#data

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

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

Type: object.

Context#headers

Client’s headers.

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

Type: object.

Context#isServer

Was action created by Logux server.

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

Type: boolean.

Context#nodeId

Unique node ID.

server.nodeIds.get(node.nodeId)

Type: string.

Context#server

Logux server

Type: Server.

Context#subprotocol

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

Type: string.

Context#userId

User ID taken node ID.

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

Type: string.

Context#isSubprotocol(range)

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

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

Returns boolean. Is version satisfies requirements.

Context#sendBack(action, meta?)

Send action back to the client.

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

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

ArgumentTypeDescription
actionAnyActionThe action.
meta ?Partial<ServerMeta>Action’s meta.

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

Extends Error.

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

ParameterType
statusCodenumber
urlstring
optsRequestInit
responsestring

ResponseError#name

Type: "ResponseError".

ResponseError#statusCode

Type: number.

Extends BaseServer.

End-user API to create Logux server.

import { Server } from '@logux/server'

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

const server = new Server(Object.assign({
  subprotocol: '1.0.0',
  supports: '1.x || 0.x',
  fileUrl: import.meta.url
}, envOptions))

server.listen()
ParameterTypeDescription
optsServerOptionsServer options.

Server.loadOptions(process, defaults)

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

const server = new Server(Server.loadOptions(process, {
  subprotocol: '1.0.0',
  supports: '1.x',
  fileUrl: import.meta.url,
  port: 31337
}))
ArgumentTypeDescription
processProcessCurrent process object.
defaultsServerOptionsDefault server options. Arguments and environment variables will override them.

Returns ServerOptions. Parsed options object.

Server#clientIds

Connected client by client ID.

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

Type: Map<string,ServerClient>.

Server#connected

Connected clients.

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

Type: Map<string,ServerClient>.

Server#controls

Add callback to internal HTTP server.

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

Server#env

Production or development mode.

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

Type: "development" | "production".

Server#log

Server actions log.

server.log.each(finder)

Type: Log.

Server#logger

Console for custom log records. It uses pino API.

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

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

Server#nodeId

Server unique ID.

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

Type: string.

Server#nodeIds

Connected client by node ID.

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

Type: Map<string,ServerClient>.

Server#options

Server options.

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

Type: ServerOptions.

Server#subscribers

Clients subscribed to some channel.

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

Type: { [channel: string]: { [nodeId: string]: { filters: { [key: string]: ChannelFilter | true }, unsubscribe?: (action: LoguxUnsubscribeAction, meta: ServerMeta) => void } } }.

Server#userIds

Connected client by user ID.

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

Type: Map<string,ServerClient[]>.

Server#addClient(connection)

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

server.addClient(test.right)
ArgumentTypeDescription
connectionServerConnectionLogux connection to client.

Returns number. Client ID.

Server#auth(authenticator)

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

server.auth(async ({ userId, cookie }) => {
  const user = await findUserByToken(cookie.token)
  return !!user && userId === user.id
})
ArgumentTypeDescription
authenticatorAuthenticatorThe authentication callback.

Server#autoloadModules(files?)

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

await server.autoloadModules()
ArgumentTypeDescription
files ?string | string[]Pattern for module files.

Returns Promise.

Server#channel(pattern, callbacks)

Define the channel.

server.channel('user/:id', {
  access (ctx, action, meta) {
    return ctx.params.id === ctx.userId
  }
  filter (ctx, action, meta) {
    return (otherCtx, otherAction, otherMeta) => {
      return !action.hidden
    }
  }
  async load (ctx, action, meta) {
    const user = await db.loadUser(ctx.params.id)
    ctx.sendBack({ type: 'USER_NAME', name: user.name })
  }
})
ArgumentTypeDescription
patternstringPattern for channel name.
callbacksChannelCallbacksCallback during subscription process.
ArgumentTypeDescription
patternRegExpRegular expression for channel name.
callbacksChannelCallbacksCallback during subscription process.

Server#debugError(error)

Send runtime error stacktrace to all clients.

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

Server#destroy()

Stop server and unbind all listeners.

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

Returns Promise. Promise when all listeners will be removed.

Server#http(listener)

Add non-WebSocket HTTP request processor.

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

Server#listen()

Start WebSocket server and listen for clients.

Returns Promise. When the server has been bound.

Server#on(event, listener)

ArgumentTypeDescription
event"subscriptionCancelled"The event name.
listener() => voidEvent listener.
ArgumentTypeDescription
event"subscribing"The event name.
listener(action: LoguxSubscribeAction, meta: ServerMeta) => voidSubscription listener.
ArgumentTypeDescription
event"processed" | "backendGranted" | "backendProcessed"The event name.
listener(action: Action, meta: ServerMeta, latencyMilliseconds: number) => voidProcessing listener.
ArgumentTypeDescription
event"add" | "clean" | "backendSent"The event name.
listener(action: Action, meta: ServerMeta) => voidAction listener.
ArgumentTypeDescription
event"connected" | "disconnected"The event name.
listener(client: ServerClient) => voidClient listener.
ArgumentTypeDescription
event"clientError" | "fatal"The event name.
listener(err: Error) => voidThe listener function.
ArgumentTypeDescription
event"error"The event name.
listener(err: Error, action: Action, meta: ServerMeta) => voidError listener.
ArgumentTypeDescription
event"authenticated"The event name.
listener(client: ServerClient, latencyMilliseconds: number) => voidClient listener.
ArgumentTypeDescription
event"preadd"The event name.
listener(action: Action, meta: ServerMeta) => voidAction listener.
ArgumentTypeDescription
event"subscribed"The event name.
listener(action: LoguxSubscribeAction, meta: ServerMeta, latencyMilliseconds: number) => voidSubscription listener.
ArgumentTypeDescription
event"unsubscribed"The event name.
listener(action: LoguxUnsubscribeAction, meta: ServerMeta) => voidSubscription listener.
ArgumentTypeDescription
event"report"The event name.
listenerReporterReport listener.

Returns Unsubscribe.

Server#otherChannel(callbacks)

Set callbacks for unknown channel subscription.

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

Server#otherType(callbacks)

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

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

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

Server#process(action, meta?)

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

ArgumentTypeDescription
actionAnyActionNew action to resend and process.
meta ?Partial<ServerMeta>Action’s meta.

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

Server#sendAction(action, meta)

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

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

Returns void | Promise.

Server#subscribe(nodeId, channel)

Send logux/subscribed if client was not already subscribed.

server.subscribe(ctx.nodeId, `users/${loaded}`)
ArgumentTypeDescription
nodeIdstringNode ID.
channelstringChannel name.

Server#type(actionCreator, callbacks)

Define action type’s callbacks.

server.type('CHANGE_NAME', {
  access (ctx, action, meta) {
    return action.user === ctx.userId
  },
  resend (ctx, action) {
    return `user/${ action.user }`
  }
  process (ctx, action, meta) {
    if (isFirstOlder(lastNameChange(action.user), meta)) {
      return db.changeUserName({ id: action.user, name: action.name })
    }
  }
})
ArgumentTypeDescription
actionCreatorAbstractActionCreatorAction creator function.
callbacksActionCallbacksCallbacks for action created by creator.
ArgumentTypeDescription
nameRegExp | Action["type"]The action’s type or action’s type matching rule as RegExp..
callbacksActionCallbacksCallbacks for actions with this type.

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

Undo action from client.

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

Returns Promise. When action was saved to the log.

Server#unknownType(action, meta)

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

If you didn’t set Server#otherType, Logux will call it automatically.

server.otherType({
  access (ctx, action, meta) {
    if (action.type.startsWith('myapp/')) {
      return proxy.access(action, meta)
    } else {
      server.unknownType(action, meta)
    }
  }
})
ArgumentTypeDescription
actionActionThe action with unknown type.
metaServerMetaAction’s metadata.

Server#wrongChannel(action, meta)

Report that client try to subscribe for unknown channel.

Logux call it automatically, if you will not set Server#otherChannel.

server.otherChannel({
  async access (ctx, action, meta) {
    const res = phpBackend.checkChannel(params[0], ctx.userId)
    if (res.code === 404) {
      this.wrongChannel(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
})
ArgumentTypeDescription
actionLoguxSubscribeActionThe subscribe action.
metaServerMetaAction’s metadata.

Logux client connected to server.

const client = server.connected.get(0)

ServerClient#app

Server, which received client.

Type: BaseServer.

ServerClient#clientId

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

Type: string.

ServerClient#connection

The Logux wrapper to WebSocket connection.

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

Type: ServerConnection.

ServerClient#httpHeaders

HTTP headers of WS connection.

client.httpHeaders['User-Agent']

Type: { [name: string]: string }.

ServerClient#key

Client number used as app.connected key.

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

Type: string.

ServerClient#node

Node instance to synchronize logs.

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

Type: ServerNode.

ServerClient#nodeId

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

Type: string.

ServerClient#processing

Does server process some action from client.

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

Type: boolean.

ServerClient#remoteAddress

Client IP address.

const clientCity = detectLocation(client.remoteAddress)

Type: string.

ServerClient#userId

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

Type: string.

ServerClient#destroy()

Disconnect client.

ServerClient#isSubprotocol(range)

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

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

Returns boolean. Is version satisfies requirements.

addSyncMap(server, plural, operations)

Add callbacks for client’s SyncMap.

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

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

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

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

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

  async delete (ctx, id) {
    await Task.delete(id)
  }
})
ArgumentTypeDescription
serverBaseServerServer instance.
pluralstringPrefix for channel names and action types.
operationsSyncMapOperationsCallbacks.

addSyncMapFilter(server, plural, operations)

Add callbacks for client’s useFilter.

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

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

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

  actions (filterCtx, filter) {
    return (actionCtx, action, meta) => {
      return actionCtx.userId === filterCtx.userId
    }
  }
})
ArgumentTypeDescription
serverBaseServerServer instance.
pluralstringPrefix for channel names and action types.
operationsSyncMapFilterOperationsCallbacks.

del(url, opts?, fetcher?)

Syntax sugar for DELETE requests by node-fetch, throwing ResponseError on non-2xx response and parsing JSON response.

ArgumentTypeDescription
urlstringResource URL.
opts ?Omit<RequestInit,"method">fetch() options.
fetcher ?(url: URL | RequestInfo, init?: RequestInit) => Promise<Response>A way to replace fetch() for tests.

Returns any.

get(url, opts?, fetcher?)

Syntax sugar for GET requests by node-fetch, throwing ResponseError on non-2xx response and parsing JSON response.

import { get } from '@logux/server'

let user = get(url)
ArgumentTypeDescription
urlstringResource URL.
opts ?Omit<RequestInit,"method">fetch() options.
fetcher ?(url: URL | RequestInfo, init?: RequestInit) => Promise<Response>A way to replace fetch() for tests.

Returns any.

patch(url, opts?, fetcher?)

Syntax sugar for PATCH requests by node-fetch, throwing ResponseError on non-2xx response and parsing JSON response.

ArgumentTypeDescription
urlstringResource URL.
opts ?Omit<RequestInit,"method">fetch() options.
fetcher ?(url: URL | RequestInfo, init?: RequestInit) => Promise<Response>A way to replace fetch() for tests.

Returns any.

post(url, opts?, fetcher?)

Syntax sugar for POST requests by node-fetch, throwing ResponseError on non-2xx response.

ArgumentTypeDescription
urlstringResource URL.
opts ?Omit<RequestInit,"method">fetch() options.
fetcher ?(url: URL | RequestInfo, init?: RequestInit) => Promise<Response>A way to replace fetch() for tests.

Returns any.

put(url, opts?, fetcher?)

Syntax sugar for PUT requests by node-fetch, throwing ResponseError on non-2xx response and parsing JSON response.

ArgumentTypeDescription
urlstringResource URL.
opts ?Omit<RequestInit,"method">fetch() options.
fetcher ?(url: URL | RequestInfo, init?: RequestInit) => Promise<Response>A way to replace fetch() for tests.

Returns any.

request(url, opts?, fetcher?)

Syntax sugar around node-fetch, which throws ResponseError on non-2xx response.

ArgumentTypeDescription
urlstringResource URL.
opts ?RequestInitfetch() options.
fetcher ?(url: URL | RequestInfo, init?: RequestInit) => Promise<Response>A way to replace fetch() for tests.

Returns any.


Client to test server.

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

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

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

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

it('creates and loads posts', () => {
  let server = createServer()
  postsModule(server)
  let client1 = await server.connect('1')
  await client1.process({ type: 'posts/add', post })
  let client1 = await server.connect('2')
  expect(await client2.subscribe('posts')).toEqual([
    { type: 'posts/add', post }
  ])
})
ParameterTypeDescription
serverTestServerTest server.
userIdstringUser ID.
opts ?TestClientOptionsOther options.

TestClient#clientId

Client’s ID.

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

Type: string.

TestClient#log

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

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

Type: TestLog.

TestClient#nodeId

Client’s node ID.

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

Type: string.

TestClient#pair

Connection channel between client and server to track sent messages.

console.log(client.pair.leftSent)

Type: TestPair.

TestClient#userId

User ID.

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

Type: string.

TestClient#collect(test)

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

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

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

TestClient#connect(opts?)

Connect to test server.

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

Returns Promise. Promise until the authorization.

TestClient#disconnect()

Disconnect from test server.

await client.disconnect()

Returns Promise. Promise until connection close.

TestClient#process(action, meta?)

Send action to the sever and collect all response actions.

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

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

TestClient#received(test)

Collect actions received from server during the test call.

let answers = await client1.received(async () => {
  await client2.process({ type: 'resend' })
})
expect(actions).toEqual([{ type: 'local' }])
ArgumentTypeDescription
test() => anyFunction, where do you expect action will be received

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

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

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

let posts = await client.subscribe('posts')
expect(posts).toEqual([
  { type: 'posts/add', post }
])
ArgumentTypeDescription
channelstring | LoguxSubscribeActionChannel name or logux/subscribe action.
filter ?objectOptional filter for subscription.
since ?{ id: string, time: number }Optional time from last data.

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

TestClient#unsubscribe(channel, filter?)

Unsubscribe client from the channel.

await client.unsubscribe('posts')
ArgumentTypeDescription
channelstring | LoguxUnsubscribeActionChannel name or logux/subscribe action.
filter ?objectOptional filter for subscription.

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

Extends Log.

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

Use TestTime to create test log.

import { TestTime } from '@logux/core'

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

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

TestLog#nodeId

Unique node ID. It is used in action IDs.

Type: string.

TestLog#store

Log store.

Type: 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<ServerMeta>Open structure for action metadata.

Returns Promise<false | ServerMeta>. 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, ServerMeta]>. 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<ServerMeta>Object with values to change in action metadata.

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

TestLog#each(opts, callback)

ArgumentTypeDescription
optsGetOptionsIterator options.
callbackActionIteratorFunction will be executed on every action.
ArgumentTypeDescription
callbackActionIteratorFunction will be executed on every action.
ArgumentType
callbackActionIterator

Returns Promise.

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

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"add" | "clean"The event name.
listenerReadonlyListenerThe listener function.
ArgumentType
event"preadd"
listenerPreaddListener

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.
listenerReadonlyListenerThe listener function.
opts ?{ event?: "add" | "clean", id?: string }
ArgumentType
typestring
listenerPreaddListener
opts{ event: "preadd", 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.

Extends BaseServer.

Server to be used in test.

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

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

it('connects to the server', () => {
  server = new TestServer()
  usersModule(server)
  let client = await server.connect('10')
})
ParameterTypeDescription
opts ?TestServerOptionsThe limit subset of server options.

TestServer#clientIds

Connected client by client ID.

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

Type: Map<string,ServerClient>.

TestServer#connected

Connected clients.

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

Type: Map<string,ServerClient>.

TestServer#controls

Add callback to internal HTTP server.

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

TestServer#env

Production or development mode.

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

Type: "development" | "production".

TestServer#log

Server actions log, with methods to check actions inside.

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

Type: TestLog.

TestServer#logger

Console for custom log records. It uses pino API.

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

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

TestServer#nodeId

Server unique ID.

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

Type: string.

TestServer#nodeIds

Connected client by node ID.

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

Type: Map<string,ServerClient>.

TestServer#options

Server options.

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

Type: BaseServerOptions.

TestServer#subscribers

Clients subscribed to some channel.

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

Type: { [channel: string]: { [nodeId: string]: { filters: { [key: string]: ChannelFilter | true }, unsubscribe?: (action: LoguxUnsubscribeAction, meta: ServerMeta) => void } } }.

TestServer#time

Time replacement without variable parts like current timestamp.

Type: TestTime.

TestServer#userIds

Connected client by user ID.

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

Type: Map<string,ServerClient[]>.

TestServer#addClient(connection)

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

server.addClient(test.right)
ArgumentTypeDescription
connectionServerConnectionLogux connection to client.

Returns number. Client ID.

TestServer#auth(authenticator)

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

server.auth(async ({ userId, cookie }) => {
  const user = await findUserByToken(cookie.token)
  return !!user && userId === user.id
})
ArgumentTypeDescription
authenticatorAuthenticatorThe authentication callback.

TestServer#channel(pattern, callbacks)

Define the channel.

server.channel('user/:id', {
  access (ctx, action, meta) {
    return ctx.params.id === ctx.userId
  }
  filter (ctx, action, meta) {
    return (otherCtx, otherAction, otherMeta) => {
      return !action.hidden
    }
  }
  async load (ctx, action, meta) {
    const user = await db.loadUser(ctx.params.id)
    ctx.sendBack({ type: 'USER_NAME', name: user.name })
  }
})
ArgumentTypeDescription
patternstringPattern for channel name.
callbacksChannelCallbacksCallback during subscription process.
ArgumentTypeDescription
patternRegExpRegular expression for channel name.
callbacksChannelCallbacksCallback during subscription process.

TestServer#connect(userId, opts?)

Create and connect client.

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

Returns Promise<TestClient>. Promise with new client.

TestServer#debugError(error)

Send runtime error stacktrace to all clients.

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

TestServer#destroy()

Stop server and unbind all listeners.

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

Returns Promise. Promise when all listeners will be removed.

TestServer#expectDenied(test)

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

await server.expectDenied(async () => {
  client.subscribe('secrets')
})
ArgumentTypeDescription
test() => anyCallback with subscripting or action sending.

Returns Promise.

TestServer#expectError(text, test)

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

ArgumentTypeDescription
textstring | RegExpRegExp or string of error message.
test() => anyCallback with subscripting or action sending.

Returns Promise.

TestServer#expectUndo(reason, test)

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

await server.expectUndo('notFound', async () => {
  client.subscribe('projects/nothing')
})
ArgumentTypeDescription
reasonstringThe reason in undo action.
test() => anyCallback with subscripting or action sending.

Returns Promise.

TestServer#expectWrongCredentials(userId, opts?)

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

server = new TestServer()
await server.expectWrongCredentials('10')
ArgumentTypeDescription
userIdstringUser ID.
opts ?TestClientOptionsOther options.

Returns Promise. Promise until check.

TestServer#http(listener)

Add non-WebSocket HTTP request processor.

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

TestServer#listen()

Start WebSocket server and listen for clients.

Returns Promise. When the server has been bound.

TestServer#on(event, listener)

ArgumentTypeDescription
event"subscriptionCancelled"The event name.
listener() => voidEvent listener.
ArgumentTypeDescription
event"subscribing"The event name.
listener(action: LoguxSubscribeAction, meta: ServerMeta) => voidSubscription listener.
ArgumentTypeDescription
event"processed" | "backendGranted" | "backendProcessed"The event name.
listener(action: Action, meta: ServerMeta, latencyMilliseconds: number) => voidProcessing listener.
ArgumentTypeDescription
event"add" | "clean" | "backendSent"The event name.
listener(action: Action, meta: ServerMeta) => voidAction listener.
ArgumentTypeDescription
event"connected" | "disconnected"The event name.
listener(client: ServerClient) => voidClient listener.
ArgumentTypeDescription
event"clientError" | "fatal"The event name.
listener(err: Error) => voidThe listener function.
ArgumentTypeDescription
event"error"The event name.
listener(err: Error, action: Action, meta: ServerMeta) => voidError listener.
ArgumentTypeDescription
event"authenticated"The event name.
listener(client: ServerClient, latencyMilliseconds: number) => voidClient listener.
ArgumentTypeDescription
event"preadd"The event name.
listener(action: Action, meta: ServerMeta) => voidAction listener.
ArgumentTypeDescription
event"subscribed"The event name.
listener(action: LoguxSubscribeAction, meta: ServerMeta, latencyMilliseconds: number) => voidSubscription listener.
ArgumentTypeDescription
event"unsubscribed"The event name.
listener(action: LoguxUnsubscribeAction, meta: ServerMeta) => voidSubscription listener.
ArgumentTypeDescription
event"report"The event name.
listenerReporterReport listener.

Returns Unsubscribe.

TestServer#otherChannel(callbacks)

Set callbacks for unknown channel subscription.

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

TestServer#otherType(callbacks)

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

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

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

TestServer#process(action, meta?)

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

ArgumentTypeDescription
actionAnyActionNew action to resend and process.
meta ?Partial<ServerMeta>Action’s meta.

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

TestServer#sendAction(action, meta)

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

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

Returns void | Promise.

TestServer#subscribe(nodeId, channel)

Send logux/subscribed if client was not already subscribed.

server.subscribe(ctx.nodeId, `users/${loaded}`)
ArgumentTypeDescription
nodeIdstringNode ID.
channelstringChannel name.

TestServer#type(actionCreator, callbacks)

Define action type’s callbacks.

server.type('CHANGE_NAME', {
  access (ctx, action, meta) {
    return action.user === ctx.userId
  },
  resend (ctx, action) {
    return `user/${ action.user }`
  }
  process (ctx, action, meta) {
    if (isFirstOlder(lastNameChange(action.user), meta)) {
      return db.changeUserName({ id: action.user, name: action.name })
    }
  }
})
ArgumentTypeDescription
actionCreatorAbstractActionCreatorAction creator function.
callbacksActionCallbacksCallbacks for action created by creator.
ArgumentTypeDescription
nameRegExp | Action["type"]The action’s type or action’s type matching rule as RegExp..
callbacksActionCallbacksCallbacks for actions with this type.

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

Undo action from client.

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

Returns Promise. When action was saved to the log.

TestServer#unknownType(action, meta)

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

If you didn’t set Server#otherType, Logux will call it automatically.

server.otherType({
  access (ctx, action, meta) {
    if (action.type.startsWith('myapp/')) {
      return proxy.access(action, meta)
    } else {
      server.unknownType(action, meta)
    }
  }
})
ArgumentTypeDescription
actionActionThe action with unknown type.
metaServerMetaAction’s metadata.

TestServer#wrongChannel(action, meta)

Report that client try to subscribe for unknown channel.

Logux call it automatically, if you will not set Server#otherChannel.

server.otherChannel({
  async access (ctx, action, meta) {
    const res = phpBackend.checkChannel(params[0], ctx.userId)
    if (res.code === 404) {
      this.wrongChannel(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
})
ArgumentTypeDescription
actionLoguxSubscribeActionThe subscribe action.
metaServerMetaAction’s metadata.

Creates special logs for test purposes.

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

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

import { TestTime } from '@logux/core'

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

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

TestTime.getLog(opts?)

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

it('tests log', () => {
  const log = TestTime.getLog()
})
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.


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)

ArgumentType
event"headers"
listener(headers: object) => void
ArgumentType
event"error" | "clientError"
listener(error: LoguxError) => void
ArgumentTypeDescription
event"connect" | "headers" | "debug" | "state"Event name.
listener() => voidThe listener function.
ArgumentType
event"debug"
listener(type: "error", data: string) => void

Returns Unsubscribe.

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

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 ?"error" | "destroy" | "timeout"Disconnection reason.

Connection#on(event, listener)

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

Returns Unsubscribe.

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<ServerMeta>Open structure for action metadata.

Returns Promise<false | ServerMeta>. 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, ServerMeta]>. 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<ServerMeta>Object with values to change in action metadata.

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

Log#each(opts, callback)

ArgumentTypeDescription
optsGetOptionsIterator options.
callbackActionIteratorFunction will be executed on every action.
ArgumentTypeDescription
callbackActionIteratorFunction will be executed on every action.
ArgumentType
callbackActionIterator

Returns Promise.

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"add" | "clean"The event name.
listenerReadonlyListenerThe listener function.
ArgumentType
event"preadd"
listenerPreaddListener

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.
listenerReadonlyListenerThe listener function.
opts ?{ event?: "add" | "clean", id?: string }
ArgumentType
typestring
listenerPreaddListener
opts{ event: "preadd", 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, ServerMeta][].

MemoryStore#add(action, meta)

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

ArgumentTypeDescription
actionAnyActionThe action to add.
metaServerMetaAction’s metadata.

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

MemoryStore#byId(id)

Return action by action ID.

ArgumentTypeDescription
idstringAction ID.

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

MemoryStore#changeMeta(id, diff)

Change action metadata.

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

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

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, ServerMeta]>. 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.
callbackReadonlyListenerCallback 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
valuesPartial<LastSynced>Object with latest sent or received values.

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

Extends Connection.

Logux connection for server WebSocket.

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

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

ServerConnection#connected

Is connection is enabled.

Type: boolean.

ServerConnection#destroy

Type: () => void.

ServerConnection#ws

WebSocket connection instance

Type: WebSocket.

ServerConnection#connect()

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

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

Returns Promise. Promise until connection will be established.

ServerConnection#disconnect(reason?)

Finish current connection.

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

ServerConnection#on(event, listener)

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

Returns Unsubscribe.

ServerConnection#send(message)

Send message to connection.

ArgumentTypeDescription
messageMessageThe message to be sent.

defineAction(type)

ArgumentType
typeAction["type"]
ArgumentType
typeAction["type"]
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
firstMetastring | ServerMetaSome action’s metadata.
secondMetastring | ServerMetaOther 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.

ActionCallbacks

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

ActionCreator(args)

ArgumentType
argsany[]

Returns Action.

ActionCreator#type

Type: string.

ActionCreator#match(action)

ArgumentType
actionAnyAction

Returns action is Action.

ActionFinally(ctx, action, meta)

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

ArgumentTypeDescription
ctxContextInformation about node, who create this action.
actionActionThe action data.
metaServerMetaThe action metadata.

ActionIterator(action, meta)

ArgumentType
actionAction
metaServerMeta

Returns boolean | void.

ActionReporter

PropertyType
actionAction
metaServerMeta

ALLOWED_META

List of meta keys permitted for clients.

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

Type: string[].

AnyAction

PropertyType
typestring
[extra: string]any

AuthenticationReporter

PropertyType
connectionIdstring
nodeIdstring
subprotocolstring

Authenticator(user)

The authentication callback.

ArgumentType
userAuthenticatorOptions

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

AuthenticatorOptions

PropertyType
clientServerClient
cookie{ [key: string]: string }
headersobject
tokenstring
userIdstring

Authentificator(nodeId, token, headers)

ArgumentType
nodeIdstring
tokenstring
headers{ } | object

Returns Promise<boolean>.

Authorizer(ctx, action, meta)

Check does user can do this action.

ArgumentTypeDescription
ctxContextInformation about node, who create this action.
actionActionThe action data.
metaServerMetaThe action metadata.

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

Base server class to extend.

ParameterTypeDescription
optsBaseServerOptionsServer options.

BaseServer#clientIds

Connected client by client ID.

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

Type: Map<string,ServerClient>.

BaseServer#connected

Connected clients.

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

Type: Map<string,ServerClient>.

BaseServer#controls

Add callback to internal HTTP server.

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

BaseServer#env

Production or development mode.

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

Type: "development" | "production".

BaseServer#log

Server actions log.

server.log.each(finder)

Type: Log.

BaseServer#logger

Console for custom log records. It uses pino API.

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

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

BaseServer#nodeId

Server unique ID.

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

Type: string.

BaseServer#nodeIds

Connected client by node ID.

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

Type: Map<string,ServerClient>.

BaseServer#options

Server options.

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

Type: BaseServerOptions.

BaseServer#subscribers

Clients subscribed to some channel.

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

Type: { [channel: string]: { [nodeId: string]: { filters: { [key: string]: ChannelFilter | true }, unsubscribe?: (action: LoguxUnsubscribeAction, meta: ServerMeta) => void } } }.

BaseServer#userIds

Connected client by user ID.

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

Type: Map<string,ServerClient[]>.

BaseServer#addClient(connection)

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

server.addClient(test.right)
ArgumentTypeDescription
connectionServerConnectionLogux connection to client.

Returns number. Client ID.

BaseServer#auth(authenticator)

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

server.auth(async ({ userId, cookie }) => {
  const user = await findUserByToken(cookie.token)
  return !!user && userId === user.id
})
ArgumentTypeDescription
authenticatorAuthenticatorThe authentication callback.

BaseServer#channel(pattern, callbacks)

Define the channel.

server.channel('user/:id', {
  access (ctx, action, meta) {
    return ctx.params.id === ctx.userId
  }
  filter (ctx, action, meta) {
    return (otherCtx, otherAction, otherMeta) => {
      return !action.hidden
    }
  }
  async load (ctx, action, meta) {
    const user = await db.loadUser(ctx.params.id)
    ctx.sendBack({ type: 'USER_NAME', name: user.name })
  }
})
ArgumentTypeDescription
patternstringPattern for channel name.
callbacksChannelCallbacksCallback during subscription process.
ArgumentTypeDescription
patternRegExpRegular expression for channel name.
callbacksChannelCallbacksCallback during subscription process.

BaseServer#debugError(error)

Send runtime error stacktrace to all clients.

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

BaseServer#destroy()

Stop server and unbind all listeners.

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

Returns Promise. Promise when all listeners will be removed.

BaseServer#http(listener)

Add non-WebSocket HTTP request processor.

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

BaseServer#listen()

Start WebSocket server and listen for clients.

Returns Promise. When the server has been bound.

BaseServer#on(event, listener)

ArgumentTypeDescription
event"subscriptionCancelled"The event name.
listener() => voidEvent listener.
ArgumentTypeDescription
event"subscribing"The event name.
listener(action: LoguxSubscribeAction, meta: ServerMeta) => voidSubscription listener.
ArgumentTypeDescription
event"processed" | "backendGranted" | "backendProcessed"The event name.
listener(action: Action, meta: ServerMeta, latencyMilliseconds: number) => voidProcessing listener.
ArgumentTypeDescription
event"add" | "clean" | "backendSent"The event name.
listener(action: Action, meta: ServerMeta) => voidAction listener.
ArgumentTypeDescription
event"connected" | "disconnected"The event name.
listener(client: ServerClient) => voidClient listener.
ArgumentTypeDescription
event"clientError" | "fatal"The event name.
listener(err: Error) => voidThe listener function.
ArgumentTypeDescription
event"error"The event name.
listener(err: Error, action: Action, meta: ServerMeta) => voidError listener.
ArgumentTypeDescription
event"authenticated"The event name.
listener(client: ServerClient, latencyMilliseconds: number) => voidClient listener.
ArgumentTypeDescription
event"preadd"The event name.
listener(action: Action, meta: ServerMeta) => voidAction listener.
ArgumentTypeDescription
event"subscribed"The event name.
listener(action: LoguxSubscribeAction, meta: ServerMeta, latencyMilliseconds: number) => voidSubscription listener.
ArgumentTypeDescription
event"unsubscribed"The event name.
listener(action: LoguxUnsubscribeAction, meta: ServerMeta) => voidSubscription listener.
ArgumentTypeDescription
event"report"The event name.
listenerReporterReport listener.

Returns Unsubscribe.

BaseServer#otherChannel(callbacks)

Set callbacks for unknown channel subscription.

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

BaseServer#otherType(callbacks)

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

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

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

BaseServer#process(action, meta?)

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

ArgumentTypeDescription
actionAnyActionNew action to resend and process.
meta ?Partial<ServerMeta>Action’s meta.

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

BaseServer#sendAction(action, meta)

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

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

Returns void | Promise.

BaseServer#subscribe(nodeId, channel)

Send logux/subscribed if client was not already subscribed.

server.subscribe(ctx.nodeId, `users/${loaded}`)
ArgumentTypeDescription
nodeIdstringNode ID.
channelstringChannel name.

BaseServer#type(actionCreator, callbacks)

Define action type’s callbacks.

server.type('CHANGE_NAME', {
  access (ctx, action, meta) {
    return action.user === ctx.userId
  },
  resend (ctx, action) {
    return `user/${ action.user }`
  }
  process (ctx, action, meta) {
    if (isFirstOlder(lastNameChange(action.user), meta)) {
      return db.changeUserName({ id: action.user, name: action.name })
    }
  }
})
ArgumentTypeDescription
actionCreatorAbstractActionCreatorAction creator function.
callbacksActionCallbacksCallbacks for action created by creator.
ArgumentTypeDescription
nameRegExp | Action["type"]The action’s type or action’s type matching rule as RegExp..
callbacksActionCallbacksCallbacks for actions with this type.

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

Undo action from client.

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

Returns Promise. When action was saved to the log.

BaseServer#unknownType(action, meta)

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

If you didn’t set Server#otherType, Logux will call it automatically.

server.otherType({
  access (ctx, action, meta) {
    if (action.type.startsWith('myapp/')) {
      return proxy.access(action, meta)
    } else {
      server.unknownType(action, meta)
    }
  }
})
ArgumentTypeDescription
actionActionThe action with unknown type.
metaServerMetaAction’s metadata.

BaseServer#wrongChannel(action, meta)

Report that client try to subscribe for unknown channel.

Logux call it automatically, if you will not set Server#otherChannel.

server.otherChannel({
  async access (ctx, action, meta) {
    const res = phpBackend.checkChannel(params[0], ctx.userId)
    if (res.code === 404) {
      this.wrongChannel(action, meta)
      return false
    } else {
      return response.body === 'granted'
    }
  }
})
ArgumentTypeDescription
actionLoguxSubscribeActionThe subscribe action.
metaServerMetaAction’s metadata.

BaseServerOptions

PropertyTypeDescription
backend ?stringURL to PHP, Ruby on Rails, or other backend to process actions and authentication.
cert ?stringSSL certificate or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.
cleanFromLog ?RegExpRegular expression which should be cleaned from error message and stack.
controlMask ?stringCIDR masks for IP address, where control requests could came from.
controlSecret ?stringSecret to control the server.
disableHttpServer ?booleanDisable health check endpoint, control HTTP API, Server#http.
env ?"development" | "production"Development or production server mode. By default, it will be taken from NODE_ENV environment variable. On empty NODE_ENV it will be 'development'.
fileUrl ?stringURL of main JS file in the root dir. Shortcut to set root in ES modules without fileURLToPath.
host ?stringIP-address to bind server. Default is 127.0.0.1.
id ?stringCustom random ID to be used in node ID.
key ?string | { pem: string }SSL key or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.
pid ?numberProcess ID, to display in logs.
ping ?numberMilliseconds since last message to test connection by sending ping. Default is 20000.
port ?numberPort to bind server. It will create HTTP server manually to connect WebSocket server to it. Default is 31337.
redis ?stringURL to Redis for Logux Server Pro scaling.
root ?stringApplication root to load files and show errors. Default is process.cwd().
server ?HTTPServerHTTP server to serve Logux’s WebSocket and HTTP requests.
store ?LogStoreStore to save log. Will be {@link @logux/core:MemoryStore}, by default.
subprotocol ?stringServer current application subprotocol version in SemVer format.
supports ?stringnpm’s version requirements for client subprotocol version.
time ?TestTimeTest time to test server.
timeout ?numberTimeout in milliseconds to disconnect connection. Default is 70000.

ChangedAt(value, time)

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

If you do not know the time, use NoConflictResolution.

ArgumentTypeDescription
valueSyncMapTypes | SyncMapTypes[]The value.
timenumberUNIX milliseconds.

Returns WithTime. Wrapper.

ChannelAuthorizer(ctx, action, meta)

Channel authorizer callback

ArgumentTypeDescription
ctxChannelContextInformation about node, who create this action.
actionActionThe action data.
metaServerMetaThe action metadata.

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

ChannelCallbacks

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

ChannelFilter(ctx, action, meta)

Channel filter callback

ArgumentTypeDescription
ctxContextInformation about node, who create this action.
actionActionThe action data.
metaServerMetaThe action metadata.

Returns boolean | Promise<boolean>. Should action be sent to client.

ChannelFinally(ctx, action, meta)

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

ArgumentTypeDescription
ctxChannelContextInformation about node, who create this action.
actionActionThe action data.
metaServerMetaThe action metadata.

ChannelLoader(ctx, action, meta)

Send actions with current state.

ArgumentTypeDescription
ctxChannelContextInformation about node, who create this action.
actionActionThe action data.
metaServerMetaThe action metadata.

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

ChannelUnsubscribe(ctx, action, meta)

Callback which will be called on listener unsubscribe (with explicit intent or because of disconnect)

ArgumentTypeDescription
ctxChannelContextInformation about node, who create this action.
actionLoguxUnsubscribeActionThe action data.
metaServerMetaThe action metadata.

CleanReporter

PropertyType
actionIdstring

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 ?ServerMetaRemove reason only older than specific action.
youngerThan ?ServerMetaRemove reason only younger than specific action.

EmptyHeaders

PropertyType
[key: string]undefined

Fields

PropertyType
[key: string]any

FilterCreator(ctx, action, meta)

Generates custom filter for channel’s actions.

ArgumentTypeDescription
ctxChannelContextInformation about node, who create this action.
actionActionThe action data.
metaServerMetaThe action metadata.

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

GetOptions

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

GetProcessor

PropertyType
safe ?boolean
request(request: object) => Response | Promise<Response>

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.

Extends Connection.

LocalConnection#connected

Is connection is enabled.

Type: boolean.

LocalConnection#destroy

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 ?"error" | "destroy" | "timeout"Disconnection reason.

LocalConnection#on(event, listener)

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

Returns Unsubscribe.

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
metaServerMeta

Returns Promise<boolean>.

Logger

PropertyType
error(details: object, message: string) => void
fatal(details: object, message: string) => void
info(details: object, message: string) => void
warn(details: object, message: string) => void

LoggerOptions

PropertyTypeDescription
stream ?{ flushSync?: () => void, write: (str: string) => void }Stream to be used by logger to write log.
type ?"human" | "json"Logger message format.

LogMapper(action, meta)

ArgumentType
actionAction
metaServerMeta

Returns Promise<[AnyAction, ServerMeta]>.

LogOptions

PropertyTypeDescription
nodeIdstringUnique current machine name.
storeLogStoreStore for log.

LogPage

PropertyTypeDescription
entries[Action, ServerMeta][]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.
metaServerMetaAction’s metadata.

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

LogStore#byId(id)

Return action by action ID.

ArgumentTypeDescription
idstringAction ID.

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

LogStore#changeMeta(id, diff)

Change action metadata.

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

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

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, ServerMeta]>. 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.
callbackReadonlyListenerCallback 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
valuesPartial<LastSynced>Object with latest sent or received values.

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

Extends Error.

ParameterType
message ?string

LoguxActionError#action

Type: Action.

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/actions'

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]: boolean | number | string }
since ?{ id: string, time: number }
type"logux/subscribe"

LoguxSubscribedAction

PropertyType
channelstring
type"logux/subscribed"

LoguxUndoAction

PropertyType
actionAction
idstring
reasonstring
type"logux/undo"

LoguxUnsubscribeAction

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

Message

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

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

NoConflictResolution(value)

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

ArgumentTypeDescription
valueSyncMapTypes | SyncMapTypes[]The value.

Returns WithTime. Wrapper.

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: "connecting" | "disconnected" | "sending" | "synchronized".

PostProcessor

PropertyType
command(command: object, request: object) => Promise
isValid(command: object) => boolean

PreaddListener(action, meta)

ArgumentType
actionAction
metaServerMeta

Processor(ctx, action, meta)

Action business logic.

ArgumentTypeDescription
ctxContextInformation about node, who create this action.
actionActionThe action data.
metaServerMetaThe action metadata.

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

ReadonlyListener(action, meta)

ArgumentType
actionAction
metaServerMeta

ReconnectOptions

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

Reporter(event, payload)

ArgumentType
eventkeyof ReportersArguments
payloadReportersArguments[keyof ReportersArguments]

ReportersArguments

PropertyType
addActionReporter
authenticatedAuthenticationReporter
cleanCleanReporter
clientError{ connectionId?: string, err: Error, nodeId?: string }
connect{ connectionId: string, ipAddress: string }
deniedCleanReporter
destroyvoid
disconnect{ connectionId?: string, nodeId?: string }
error{ actionId?: string, connectionId?: string, err: Error, fatal?: true, nodeId?: string }
listen{ backend: string, cert: boolean, controlMask: string, controlSecret: string, environment: "development" | "production", host: string, loguxServer: string, nodeId: string, notes: object, port: string, redis: string, server: boolean, subprotocol: string, supports: string }
processed{ actionId: string, latency: number }
subscribedSubscriptionReporter
unauthenticatedAuthenticationReporter
unknownType{ actionId: string, type: string }
unsubscribedSubscriptionReporter
uselessActionReporter
wrongChannelSubscriptionReporter
zombie{ nodeId: string }

Resend

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

Resender(ctx, action, meta)

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

ArgumentTypeDescription
ctxContextInformation about node, who create this action.
actionActionThe action data.
metaServerMetaThe action metadata.

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

Response

PropertyType
bodystring
header ?{ [name: string]: string }

SendBackActions

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

ServerMeta

PropertyTypeDescription
channel ?stringAll nodes subscribed to channel will receive the action.
channels ?string[]All nodes subscribed to listed channels will receive the action.
client ?stringAll nodes with listed client ID will receive the action.
clients ?string[]All nodes with listed client IDs will receive the action.
excludeClients ?string[]Client IDs, which will not receive the action.
node ?stringNode with listed node ID will receive the action.
nodes ?string[]All nodes with listed node IDs will receive the action.
serverstringNode ID of the server received the action.
status ?"error" | "processed" | "waiting"Action processing status
user ?stringAll nodes with listed user ID will receive the action.
users ?string[]All nodes with listed user IDs will receive the action.

Extends BaseNode.

Server node in synchronization pair.

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

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

ServerNode#authenticated

Did we finish remote node authentication.

Type: boolean.

ServerNode#connected

Is synchronization in process.

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

Type: boolean.

ServerNode#connection

Connection used to communicate to remote node.

Type: Connection.

ServerNode#initializing

Promise for node data initial loadiging.

Type: Promise.

ServerNode#lastReceived

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

Type: number.

ServerNode#lastSent

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

Type: number.

ServerNode#localNodeId

Unique current machine name.

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

Type: string.

ServerNode#localProtocol

Used Logux protocol.

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

Type: number.

ServerNode#log

Log for synchronization.

Type: Log.

ServerNode#minProtocol

Minimum version of Logux protocol, which is supported.

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

Type: number.

ServerNode#options

Synchronization options.

Type: NodeOptions.

ServerNode#remoteHeaders

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

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

Type: EmptyHeaders | object.

ServerNode#remoteNodeId

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

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

Type: string.

ServerNode#remoteProtocol

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

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

Type: number.

ServerNode#remoteSubprotocol

Remote node’s application subprotocol version in SemVer format.

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

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

Type: string.

ServerNode#state

Current synchronization state.

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

Type: NodeState.

ServerNode#timeFix

Time difference between nodes.

Type: number.

ServerNode#catch(listener)

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

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

Returns Unsubscribe. Unbind listener from event.

ServerNode#destroy()

Shut down the connection and unsubscribe from log events.

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

ServerNode#on(event, listener)

ArgumentType
event"headers"
listener(headers: object) => void
ArgumentType
event"error" | "clientError"
listener(error: LoguxError) => void
ArgumentTypeDescription
event"connect" | "headers" | "debug" | "state"Event name.
listener() => voidThe listener function.
ArgumentType
event"debug"
listener(type: "error", data: string) => void

Returns Unsubscribe.

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

ServerNode#waitFor(state)

Return Promise until sync will have specific state.

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

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

Returns Promise. Promise until specific state.

ServerOptions

PropertyTypeDescription
backend ?stringURL to PHP, Ruby on Rails, or other backend to process actions and authentication.
cert ?stringSSL certificate or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.
cleanFromLog ?RegExpRegular expression which should be cleaned from error message and stack.
controlMask ?stringCIDR masks for IP address, where control requests could came from.
controlSecret ?stringSecret to control the server.
disableHttpServer ?booleanDisable health check endpoint, control HTTP API, Server#http.
env ?"development" | "production"Development or production server mode. By default, it will be taken from NODE_ENV environment variable. On empty NODE_ENV it will be 'development'.
fileUrl ?stringURL of main JS file in the root dir. Shortcut to set root in ES modules without fileURLToPath.
host ?stringIP-address to bind server. Default is 127.0.0.1.
id ?stringCustom random ID to be used in node ID.
key ?string | { pem: string }SSL key or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.
logger ?Logger | LoggerOptionsLogger with custom settings.
pid ?numberProcess ID, to display in logs.
ping ?numberMilliseconds since last message to test connection by sending ping. Default is 20000.
port ?numberPort to bind server. It will create HTTP server manually to connect WebSocket server to it. Default is 31337.
redis ?stringURL to Redis for Logux Server Pro scaling.
root ?stringApplication root to load files and show errors. Default is process.cwd().
server ?HTTPServerHTTP server to serve Logux’s WebSocket and HTTP requests.
store ?LogStoreStore to save log. Will be {@link @logux/core:MemoryStore}, by default.
subprotocol ?stringServer current application subprotocol version in SemVer format.
supports ?stringnpm’s version requirements for client subprotocol version.
time ?TestTimeTest time to test server.
timeout ?numberTimeout in milliseconds to disconnect connection. Default is 70000.

SubscriptionReporter

PropertyType
actionIdstring
channelstring

SyncMapActionFilter(ctx, action, meta)

ArgumentType
ctxContext
actionSyncMapChangedAction | SyncMapCreatedAction | SyncMapDeletedAction
metaServerMeta

Returns boolean | Promise<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

SyncMapData

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

SyncMapDeleteAction

PropertyType
idstring
typestring

SyncMapDeletedAction

PropertyType
idstring
typestring

SyncMapFilterOperations

PropertyType
access ?(ctx: Context, filter: Partial<SyncMapValues>, action: LoguxSubscribeAction, meta: ServerMeta) => boolean | Promise<boolean>
actions ?(ctx: Context, filter: Partial<SyncMapValues>, action: LoguxSubscribeAction, meta: ServerMeta) => void | SyncMapActionFilter | Promise<SyncMapActionFilter>
initial(ctx: Context, filter: Partial<SyncMapValues>, since: number, action: LoguxSubscribeAction, meta: ServerMeta) => SyncMapData[] | Promise<SyncMapData[]>

SyncMapOperations

PropertyType
access(ctx: Context, id: string, action: LoguxSubscribeAction | SyncMapDeleteAction | SyncMapChangeAction | SyncMapCreateAction, meta: ServerMeta) => boolean | Promise<boolean>
change ?(ctx: Context, id: string, fields: Partial<SyncMapValues>, time: number, action: SyncMapChangeAction, meta: ServerMeta) => boolean | void | Promise<boolean | void>
create ?(ctx: Context, id: string, fields: SyncMapValues, time: number, action: SyncMapCreateAction, meta: ServerMeta) => boolean | void | Promise<boolean | void>
delete ?(ctx: Context, id: string, action: SyncMapDeleteAction, meta: ServerMeta) => boolean | void | Promise<boolean | void>
load ?(ctx: Context, id: string, since: number, action: LoguxSubscribeAction, meta: ServerMeta) => false | SyncMapData | Promise<false | SyncMapData>

SyncMapTypes

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

SyncMapValues

PropertyType
[key: string]SyncMapTypes | SyncMapTypes[]

TestClientOptions

PropertyType
cookie ?object
headers ?object
httpHeaders ?{ [key: string]: string }
subprotocol ?string
token ?string

TestLogOptions

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

TestServerOptions

PropertyTypeDescription
auth ?falseDisable built-in auth.
backend ?stringURL to PHP, Ruby on Rails, or other backend to process actions and authentication.
cert ?stringSSL certificate or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.
cleanFromLog ?RegExpRegular expression which should be cleaned from error message and stack.
controlMask ?stringCIDR masks for IP address, where control requests could came from.
controlSecret ?stringSecret to control the server.
disableHttpServer ?booleanDisable health check endpoint, control HTTP API, Server#http.
env ?"development" | "production"Development or production server mode. By default, it will be taken from NODE_ENV environment variable. On empty NODE_ENV it will be 'development'.
fileUrl ?stringURL of main JS file in the root dir. Shortcut to set root in ES modules without fileURLToPath.
host ?stringIP-address to bind server. Default is 127.0.0.1.
id ?stringCustom random ID to be used in node ID.
key ?string | { pem: string }SSL key or path to it. Path could be relative from server root. It is required in production mode, because WSS is highly recommended.
logger ?Logger | LoggerOptionsLogger with custom settings.
pid ?numberProcess ID, to display in logs.
ping ?numberMilliseconds since last message to test connection by sending ping. Default is 20000.
port ?numberPort to bind server. It will create HTTP server manually to connect WebSocket server to it. Default is 31337.
redis ?stringURL to Redis for Logux Server Pro scaling.
root ?stringApplication root to load files and show errors. Default is process.cwd().
server ?HTTPServerHTTP server to serve Logux’s WebSocket and HTTP requests.
store ?LogStoreStore to save log. Will be {@link @logux/core:MemoryStore}, by default.
subprotocol ?string
supports ?string
time ?TestTimeTest time to test server.
timeout ?numberTimeout in milliseconds to disconnect connection. Default is 70000.

TokenGenerator()

Returns string | Promise<string>.

Versions

PropertyType
supportedstring
usedstring

WITH_TIME

Type: unique symbol.

WithoutTime

Type: { [WITH_TIME]: false, time: undefined, value: SyncMapTypes | SyncMapTypes[] }.

WithTime

Type: { [WITH_TIME]: true, time: number, value: SyncMapTypes | SyncMapTypes[] }.

ZeroAction

PropertyType
dstring
ivstring
type"0"

ZeroCleanAction

PropertyType
idstring
type"0/clean"

actionEvents(emitter, event, action, meta)

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

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)

Returns actions for CRDT Map.

import { defineSyncMapActions } from '@logux/actions'

const [
  createUserAction,
  changeUserAction,
  deleteUserAction,
  createdUserAction,
  changedUserAction,
  deletedUserAction
] = defineSyncMapActions('users')
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.

filterMeta(meta)

Remove all non-allowed keys from meta.

ArgumentTypeDescription
metaServerMetaMeta to remove keys.

Returns ServerMeta. Meta with removed keys.

loguxProcessed

Returns logux/processed action.

Type: ActionCreator.

loguxSubscribe

Returns logux/subscribe action.

Type: ActionCreator.

loguxSubscribed

Returns logux/subscribed action.

Type: ActionCreator.

loguxUndo(fields)

Returns logux/undo action.

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

Returns LoguxUndoAction.

loguxUnsubscribe

Returns logux/unsubscribe action.

Type: ActionCreator.

wasNot403(cb)

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

import { wasNot403 } from '@logux/server'

server.auth(({ userId, token }) => {
  return wasNot403(async () => {
    get(`/checkUser/${userId}/${token}`)
  })
})
ArgumentTypeDescription
cb() => PromiseCallback with request calls.

Returns Promise<boolean>.

zero

Returns 0 action.

Type: ActionCreator.

zeroClean

Returns 0/clean action.

Type: ActionCreator.