Extends unknown
.
Show error message to user on subscription errors in components
deep in the tree.
import { ChannelErrors } from '@logux/client/react'
export const App: FC = () => {
return <>
<SideMenu />
<ChannelErrors
NotFound={NotFoundPage}
AccessDenied={AccessDeniedPage}
Error={ServerErrorPage}
>
<Layout />
</ChannelErrors>
<>
}
Context to send Logux Client or object space to components deep in the tree.
import { ClientContext, ChannelErrors } from '@logux/client/react'
import { CrossTabClient } from '@logux/client'
let client = new CrossTabClient(…)
render(
<ClientContext.Provider value={client}>
<ChannelErrors NotFound={Page404} AccessDenied={Page403}>
<App />
</ChannelErrors>
</ClientContext.Provider>,
document.body
)
Type: ReactContext
.
Context to pass error handlers from ChannelErrors
.
Type: ReactContext
.
Hook to return user's current authentication state and ID.
import { useAuth } from '@logux/client/react'
export const UserPage = () => {
let { isAuthenticated, userId } = useAuth()
if (isAuthenticated) {
return <User id={userId} />
} else {
return <Loader />
}
}
Returns StoreValue
.
Hook to return Logux client, which you set by <ClientContext.Provider>
.
import { useClient } from '@logux/client/react'
import { User } from '../stores/user'
export const NewUserForm = () => {
let client = useClient()
let onAdd = data => {
User.create(client, data)
}
}
Returns Client
.
The way to createFilter
in React.
import { useFilter } from '@logux/client/react'
import { User } from '../stores/user'
export const Users = ({ projectId }) => {
let users = useFilter(User, { projectId })
return <div>
{users.list.map(user => <User user={user} />)}
{users.isLoading && <Loader />}
</div>
}
Returns StoreValue
. Filter store to use with map.
Create store by ID, subscribe and get store’s value.
import { useSync } from '@logux/client/react'
import { User } from '../stores/user'
export const UserPage: FC = ({ id }) => {
let user = useSync(User, id)
if (user.isLoading) {
return <Loader />
} else {
return <h1>{user.name}</h1>
}
}
Returns SyncMapValue
. Store value.
Show error message to user on subscription errors in components
deep in the tree.
<template>
<channel-errors v-slot="{ code, error }">
<layout v-if="!error" />
<error v-else-if="code === 500" />
<error-not-found v-else-if="code === 404" />
<error-access-denied v-else-if="code === 403" />
</channel-errors>
</template>
<script>
import { ChannelErrors } from '@logux/client/vue'
export default {
components: { ChannelErrors }
}
</script>
Type: Component
.
Property | Type |
---|
code | Ref<Type> |
error | Ref<Type> |
Plugin that injects Logux Client into all components within the application.
import { createApp } from 'vue'
import { loguxPlugin } from '@logux/client/vue'
import { CrossTabClient } from '@logux/client'
let client = new CrossTabClient(…)
let app = createApp(…)
app.use(loguxPlugin, client)
Returns user's current authentication state and ID.
<template>
<user v-if="isAuthenticated" :id="userId" />
<sign-in v-else />
</template>
<script>
import { useAuth } from '@logux/client/vue'
export default () => {
let { isAuthenticated, userId } = useAuth()
return { isAuthenticated, userId }
}
</script>
Argument | Type | Description |
---|
client ? | Client | Logux Client instance. |
Returns { isAuthenticated: ComputedRef, userId: ComputedRef }
.
Returns the Logux Client instance.
<script>
import { useClient } from '@logux/client/vue'
import { User } from '../stores/user'
let client = useClient()
let onAdd = data => {
User.create(client, data)
}
</script>
Returns Client
.
The way to createFilter
in Vue.
<template>
<loader v-if="users.isLoading" />
<user v-else v-for="user in users" :user="user" />
</template>
<script>
import { useFilter } from '@logux/client/vue'
import { User } from '../stores/user'
export default {
props: ['projectId'],
setup (props) {
let users = useFilter(User, { projectId: props.projectId })
return { users }
}
}
</script>
Returns ReadonlyRef<StoreValue>
. Filter store to use with map.
Create store by ID, subscribe to store changes and get store’s value.
<template>
<loader v-if="user.isLoading" />
<h1 v-else>{{ user.name }}</h1>
</template>
<script>
import { useSync } from '@logux/client/vue'
import { User } from '../stores/user'
export default {
props: ['id'],
setup (props) {
let user = useSync(User, props.id)
return { user }
}
}
</script>
Returns ReadonlyRef<SyncMapValue>
. Store value.
Extends unknown
.
Show error message to user on subscription errors in components
deep in the tree.
import { ChannelErrors } from '@logux/client/preact'
export const App: FC = () => {
return <>
<SideMenu />
<ChannelErrors
NotFound={NotFoundPage}
AccessDenied={AccessDeniedPage}
Error={ServerErrorPage}
>
<Layout />
</ChannelErrors>
<>
}
Context to send Logux Client or object space to components deep in the tree.
import { ClientContext, ChannelErrors } from '@logux/client/preact'
import { CrossTabClient } from '@logux/client'
let client = new CrossTabClient(…)
render(
<ClientContext.Provider value={client}>
<ChannelErrors NotFound={Page404} AccessDenied={Page403}>
<App />
</ChannelErrors>
</ClientContext.Provider>,
document.body
)
Type: PreactContext
.
Context to pass error handlers from ChannelErrors
.
Type: PreactContext
.
Hook to return user's current authentication state and ID.
import { useAuth } from '@logux/client/preact'
export const UserPage = () => {
let { isAuthenticated, userId } = useAuth()
if (isAuthenticated) {
return <User id={userId} />
} else {
return <Loader />
}
}
Returns StoreValue
.
Hook to return Logux client, which you set by <ClientContext.Provider>
.
import { useClient } from '@logux/client/preact'
import { User } from '../stores/user'
export const NewUserForm = () => {
let client = useClient()
let onAdd = data => {
User.create(client, data)
}
}
Returns Client
.
The way to createFilter
in React.
import { useFilter } from '@logux/client/preact'
import { User } from '../stores/user'
export const Users = ({ projectId }) => {
let users = useFilter(User, { projectId })
return <div>
{users.list.map(user => <User user={user} />)}
{users.isLoading && <Loader />}
</div>
}
Returns StoreValue
. Filter store to use with map.
Create store by ID, subscribe and get store’s value.
import { useSync } from '@logux/client/preact'
import { User } from '../stores/user'
export const UserPage: FC = ({ id }) => {
let user = useSync(User, id)
if (user.isLoading) {
return <Loader />
} else {
return <h1>{user.name}</h1>
}
}
Returns SyncMapValue
. Store value.
Base class for browser API to be extended in CrossTabClient
.
Because this class could have conflicts between different browser tab,
you should use it only if you are really sure, that application will not
be run in different tab (for instance, if you are developing a kiosk app).
import { Client } from '@logux/client'
const userId = document.querySelector('meta[name=user]').content
const token = document.querySelector('meta[name=token]').content
const client = new Client({
credentials: token,
subprotocol: '1.0.0',
server: 'wss://example.com:1337',
userId: userId
})
client.start()
Unique permanent client ID. Can be used to track this machine.
Type: string
.
Is leader tab connected to server.
Type: boolean
.
Client events log.
client.log.add(action)
Type: Log
.
Node instance to synchronize logs.
if (client.node.state === 'synchronized')
Type: ClientNode
.
Unique Logux node ID.
console.log('Client ID: ', client.nodeId)
Type: string
.
Client options.
console.log('Connecting to ' + client.options.server)
Type: ClientOptions
.
Leader tab synchronization state. It can differs
from client.node.state
(because only the leader tab keeps connection).
client.on('state', () => {
if (client.state === 'disconnected' && client.state === 'sending') {
showCloseWarning()
}
})
Type: ClientNode
.
Unique tab ID. Can be used to add an action to the specific tab.
client.log.add(action, { tab: client.tabId })
Type: string
.
Disconnect from the server, update user, and connect again
with new credentials.
onAuth(async (userId, token) => {
showLoader()
client.changeUser(userId, token)
await client.node.waitFor('synchronized')
hideLoader()
})
You need manually chang user ID in all browser tabs.
Argument | Type | Description |
---|
userId | string | The new user ID. |
token ? | string | Credentials for new user. |
Clear stored data. Removes action log from IndexedDB
if you used it.
signout.addEventListener('click', () => {
client.clean()
})
Returns Promise
. Promise when all data will be removed.
Disconnect and stop synchronization.
shutdown.addEventListener('click', () => {
client.destroy()
})
Argument | Type |
---|
event | "user" |
listener | (userId: string) => void |
Argument | Type | Description |
---|
event | "state" | The event name. |
listener | () => void | The listener function. |
Returns Unsubscribe
.
Connect to server and reconnect on any connection problem.
client.start()
Argument | Type | Description |
---|
connect ? | boolean | Start connection immediately. |
Send action to the server (by setting meta.sync
and adding to the log)
and track server processing.
showLoader()
client.sync(
{ type: 'CHANGE_NAME', name }
).then(() => {
hideLoader()
}).catch(error => {
hideLoader()
showError(error.action.reason)
})
Returns Promise<ClientMeta>
. Promise for server processing.
Add listener for adding action with specific type.
Works faster than on('add', cb)
with if
.
client.type('rename', (action, meta) => {
name = action.name
})
Argument | Type | Description |
---|
type | Action["type"] | Action’s type. |
listener | ClientActionListener | |
opts ? | { event?: "add" | "clean" | "preadd", id?: string } | |
Returns Unsubscribe
. Unbind listener from event.
Wait for specific state of the leader tab.
await client.waitFor('synchronized')
hideLoader()
Returns Promise
.
Extends Client.
Low-level browser API for Logux.
Instead of Client
, this class prevents conflicts
between Logux instances in different tabs on single browser.
import { CrossTabClient } from '@logux/client'
const userId = document.querySelector('meta[name=user]').content
const token = document.querySelector('meta[name=token]').content
const client = new CrossTabClient({
subprotocol: '1.0.0',
server: 'wss://example.com:1337',
userId,
token
})
client.start()
Unique permanent client ID. Can be used to track this machine.
Type: string
.
Is leader tab connected to server.
Type: boolean
.
Cache for localStorage detection. Can be overridden to disable leader tab
election in tests.
Type: boolean
.
Client events log.
client.log.add(action)
Type: Log
.
Node instance to synchronize logs.
if (client.node.state === 'synchronized')
Type: ClientNode
.
Unique Logux node ID.
console.log('Client ID: ', client.nodeId)
Type: string
.
Client options.
console.log('Connecting to ' + client.options.server)
Type: ClientOptions
.
Current tab role. Only leader
tab connects to server. followers
just
listen to events from leader
.
client.on('role', () => {
console.log('Tab role:', client.role)
})
Type: "follower" | "leader"
.
Leader tab synchronization state. It can differs
from client.node.state
(because only the leader tab keeps connection).
client.on('state', () => {
if (client.state === 'disconnected' && client.state === 'sending') {
showCloseWarning()
}
})
Type: ClientNode
.
Unique tab ID. Can be used to add an action to the specific tab.
client.log.add(action, { tab: client.tabId })
Type: string
.
Disconnect from the server, update user, and connect again
with new credentials.
onAuth(async (userId, token) => {
showLoader()
client.changeUser(userId, token)
await client.node.waitFor('synchronized')
hideLoader()
})
You need manually chang user ID in all browser tabs.
Argument | Type | Description |
---|
userId | string | The new user ID. |
token ? | string | Credentials for new user. |
Clear stored data. Removes action log from IndexedDB
if you used it.
signout.addEventListener('click', () => {
client.clean()
})
Returns Promise
. Promise when all data will be removed.
Disconnect and stop synchronization.
shutdown.addEventListener('click', () => {
client.destroy()
})
Argument | Type | Description |
---|
event | "state" | "role" | The event name. |
listener | () => void | The listener function. |
Argument | Type |
---|
event | "user" |
listener | (userId: string) => void |
Returns Unsubscribe
.
Connect to server and reconnect on any connection problem.
client.start()
Argument | Type | Description |
---|
connect ? | boolean | Start connection immediately. |
Send action to the server (by setting meta.sync
and adding to the log)
and track server processing.
showLoader()
client.sync(
{ type: 'CHANGE_NAME', name }
).then(() => {
hideLoader()
}).catch(error => {
hideLoader()
showError(error.action.reason)
})
Returns Promise<ClientMeta>
. Promise for server processing.
Add listener for adding action with specific type.
Works faster than on('add', cb)
with if
.
client.type('rename', (action, meta) => {
name = action.name
})
Argument | Type | Description |
---|
type | Action["type"] | Action’s type. |
listener | ClientActionListener | |
opts ? | { event?: "add" | "clean" | "preadd", id?: string } | |
Returns Unsubscribe
. Unbind listener from event.
Wait for specific state of the leader tab.
await client.waitFor('synchronized')
hideLoader()
Returns Promise
.
Extends unknown
.
IndexedDB
store for Logux log.
import { IndexedStore } from '@logux/client'
const client = new CrossTabClient({
…,
store: new IndexedStore()
})
Parameter | Type | Description |
---|
name ? | string | Database name to run multiple Logux instances on same web page. |
Database name.
Type: string
.
Highlight tabs on synchronization errors.
import { attention } from '@logux/client'
attention(client)
Argument | Type | Description |
---|
client | Client | Observed Client instance. |
Returns () => void
. Unbind listener.
Display Logux widget in browser.
import { badge, badgeEn } from '@logux/client'
import { badgeStyles } from '@logux/client/badge/styles'
badge(client, {
messages: badgeEn,
styles: {
...badgeStyles,
synchronized: { backgroundColor: 'green' }
},
position: 'top-left'
})
Argument | Type | Description |
---|
client | Client | Observed Client instance. |
opts | BadgeOptions | Widget settings. |
Returns () => void
. Unbind badge listener and remove widget from DOM.
Send create action and build store instance.
import { buildNewSyncMap } from '@logux/client'
let userStore = buildNewSyncMap(client, User, {
id: nanoid(),
login: 'test'
})
Returns Promise<SyncMapStore>
. Promise with store instance.
Change keys in the store’s value.
import { changeSyncMap } from '@logux/client'
showLoader()
await changeSyncMap(userStore, { name: 'New name' })
hideLoader()
Argument | Type | Description |
---|
store | any | Store’s instance. |
diff | Partial<Omit<SyncMapValues,"id">> | Store’s changes. |
Returns Promise
. Promise until server validation for remote classes
or saving action to the log of fully offline classes.
Change store without store instance just by store ID.
import { changeSyncMapById } from '@logux/client'
let userStore = changeSyncMapById(client, User, 'user:4hs2jd83mf', {
name: 'New name'
})
Returns Promise
. Promise until server validation for remote classes
or saving action to the log of fully offline classes.
Show confirm popup, when user close tab with non-synchronized actions.
import { confirm } from '@logux/client'
confirm(client)
Argument | Type | Description |
---|
client | Client | Observed Client instance. |
Returns () => void
. Unbind listener.
Load list of SyncMap
with simple key-value requirements.
It will look for stores in loaded cache, log (for offline maps) and will
subscribe to list from server (for remote maps).
import { createFilter } from '@logux/client'
import { User } from '../store'
let usersInProject = createFilter(client, User, { projectId })
await usersInProject.loading
console.log(usersInProject.get())
Returns FilterStore
.
Send create action to the server or to the log.
Server will create a row in database on this action. FilterStore
will update the list.
import { createSyncMap } from '@logux/client'
showLoader()
await createSyncMap(client, User, {
id: nanoid(),
login: 'test'
})
hideLoader()
Returns Promise
. Promise until server validation for remote classes
or saving action to the log of fully offline classes.
Delete store.
import { deleteSyncMap } from '@logux/client'
showLoader()
await deleteSyncMap(User)
Argument | Type | Description |
---|
store | any | Store’s instance. |
Returns Promise
. Promise until server validation for remote classes
or saving action to the log of fully offline classes.
Delete store without store instance just by store ID.
import { deleteSyncMapById } from '@logux/client'
showLoader()
await deleteSyncMapById(client, User, 'user:4hs2jd83mf')
Returns Promise
. Promise until server validation for remote classes
or saving action to the log of fully offline classes.
Encrypt actions before sending them to server.
Actions will be converted to { type: '0', d: encrypt(action) }
import { encryptActions } from '@logux/client'
encryptActions(client, localStorage.getItem('userPassword'), {
ignore: ['server/public']
})
Argument | Type | Description |
---|
client | Client | Observed Client instance. |
secret | string | CryptoKey | Password for encryption, or a CryptoKey AES key. |
opts ? | { ignore: string[] } | Encryption options -- can pass in strings
to not encrypt. |
Change favicon to show Logux synchronization status.
import { favicon } from '@logux/client'
favicon(client, {
normal: '/favicon.ico',
offline: '/offline.ico',
error: '/error.ico'
})
Argument | Type | Description |
---|
client | Client | Observed Client instance. |
links | FaviconLinks | Favicon links. |
Returns () => void
. Unbind listener.
Display Logux events in browser console.
import { log } from '@logux/client'
log(client, { ignoreActions: ['user/add'] })
Argument | Type | Description |
---|
client | Client | Observed Client instance. |
messages ? | LogMessages | Disable specific message types. |
Returns () => void
. Unbind listener.
Create temporary client instance, send an action, wait response action
from the server and destroy client.
Useful for simple actions like signin or signup.
import { request } from '@logux/client'
let action = { type: 'signin', login, password }
request(action, {
server: 'wss://example.com',
subprotocol: '1.0.0
}).then(response => {
saveToken(response.token)
}).catch(error => {
showError(error.action.reason)
})
Returns Promise<Action>
. Action of server response.
CRDT LWW Map. It can use server validation or be fully offline.
The best option for classic case with server and many clients.
Store will resolve client’s edit conflicts with last write wins strategy.
import { syncMapTemplate } from '@logux/client'
export const User = syncMapTemplate<{
login: string,
name?: string,
isAdmin: boolean
}>('users')
Argument | Type | Description |
---|
plural | string | Plural store name. It will be used in action type
and channel name. |
opts ? | { offline?: boolean, remote?: boolean } | Options to disable server validation or keep actions in log
for offline support. |
Returns SyncMapTemplate
.
Extends Client.
Virtual client to test client-side code end store extnesions.
import { TestClient } from '@logux/client'
it('connects and sends actions', async () => {
let client = new TestClient()
let user = new UserStore(client, '10')
client.server.onChannel('users/10', [
{ type: 'users/name', userId: 10, value: 'New name' }
])
await client.connect()
await delay(10)
expect(user.name).toEqual('New name')
})
Unique permanent client ID. Can be used to track this machine.
Type: string
.
Is leader tab connected to server.
Type: boolean
.
Client events log.
client.log.add(action)
Type: TestLog
.
Node instance to synchronize logs.
if (client.node.state === 'synchronized')
Type: ClientNode
.
Unique Logux node ID.
console.log('Client ID: ', client.nodeId)
Type: string
.
Client options.
console.log('Connecting to ' + client.options.server)
Type: ClientOptions
.
Connection between client and server.
Type: TestPair
.
Virtual server to test client.
expect(client.server.log.actions()).toEqual([
{ type: 'logux/subscribe', channel: 'users/10' }
])
Type: TestServer
.
Leader tab synchronization state. It can differs
from client.node.state
(because only the leader tab keeps connection).
client.on('state', () => {
if (client.state === 'disconnected' && client.state === 'sending') {
showCloseWarning()
}
})
Type: ClientNode
.
Unique tab ID. Can be used to add an action to the specific tab.
client.log.add(action, { tab: client.tabId })
Type: string
.
Disconnect from the server, update user, and connect again
with new credentials.
onAuth(async (userId, token) => {
showLoader()
client.changeUser(userId, token)
await client.node.waitFor('synchronized')
hideLoader()
})
You need manually chang user ID in all browser tabs.
Argument | Type | Description |
---|
userId | string | The new user ID. |
token ? | string | Credentials for new user. |
Clear stored data. Removes action log from IndexedDB
if you used it.
signout.addEventListener('click', () => {
client.clean()
})
Returns Promise
. Promise when all data will be removed.
Connect to virtual server.
await client.connect()
Returns Promise
. Promise until connection will be established.
Disconnect and stop synchronization.
shutdown.addEventListener('click', () => {
client.destroy()
})
Disconnect from virtual server.
client.disconnect()
Argument | Type |
---|
event | "user" |
listener | (userId: string) => void |
Argument | Type | Description |
---|
event | "state" | The event name. |
listener | () => void | The listener function. |
Returns Unsubscribe
.
Collect actions sent by client during the test
call.
let answers = await client.sent(async () => {
client.log.add({ type: 'local' })
})
expect(actions).toEqual([{ type: 'local' }])
Argument | Type | Description |
---|
test | () => void | Promise | Function, where do you expect action will be received |
Returns Promise<Action[]>
. Promise with all received actions
Connect to server and reconnect on any connection problem.
client.start()
Argument | Type | Description |
---|
connect ? | boolean | Start connection immediately. |
Does client subscribed to specific channel.
let user = new UserStore(client, '10')
await delay(10)
expect(client.subscribed('users/10')).toBe(true)
Argument | Type | Description |
---|
channel | string | Channel name. |
Returns boolean
. Does client has an active subscription.
Send action to the server (by setting meta.sync
and adding to the log)
and track server processing.
showLoader()
client.sync(
{ type: 'CHANGE_NAME', name }
).then(() => {
hideLoader()
}).catch(error => {
hideLoader()
showError(error.action.reason)
})
Returns Promise<ClientMeta>
. Promise for server processing.
Add listener for adding action with specific type.
Works faster than on('add', cb)
with if
.
client.type('rename', (action, meta) => {
name = action.name
})
Argument | Type | Description |
---|
type | Action["type"] | Action’s type. |
listener | ClientActionListener | |
opts ? | { event?: "add" | "clean" | "preadd", id?: string } | |
Returns Unsubscribe
. Unbind listener from event.
Wait for specific state of the leader tab.
await client.waitFor('synchronized')
hideLoader()
Returns Promise
.
Extends Log.
Log to be used in tests. It already has memory store, node ID,
and special test timer.
Use TestTime
to create test log.
import { TestTime } from '@logux/core'
it('tests log', () => {
const log = TestTime.getLog()
})
it('tests 2 logs', () => {
const time = new TestTime()
const log1 = time.nextLog()
const log2 = time.nextLog()
})
Unique node ID. It is used in action IDs.
Type: string
.
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[]
.
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 })
})
Argument | Type | Description |
---|
action | Action | The new action. |
meta ? | Partial<ClientMeta> | Open structure for action metadata. |
Returns Promise<false | ClientMeta>
. Promise with meta
if action was added to log or false
if action was already in log.
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 })
}
Argument | Type | Description |
---|
id | string | Action ID. |
Returns Promise<[null, null] | [Action, ClientMeta]>
. Promise with array of action and metadata.
Change action metadata. You will remove action by setting reasons: []
.
await process(action)
log.changeMeta(action, { status: 'processed' })
Argument | Type | Description |
---|
id | string | Action ID. |
diff | Partial<ClientMeta> | Object with values to change in action metadata. |
Returns Promise<boolean>
. Promise with true
if metadata was changed or false
on unknown ID.
Argument | Type | Description |
---|
callback | ActionIterator | Function will be executed on every action. |
Returns Promise
.
Return all entries (with metadata) inside log, sorted by created time.
This shortcut works only with MemoryStore
.
expect(log.action).toEqual([
[{ type: 'A' }, { id: '1 test1 0', time: 1, added: 1, reasons: ['t'] }]
])
Returns [Action, ClientMeta][]
.
Generate next unique action ID.
const id = log.generateId()
Returns string
. Unique ID for action.
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()
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')
}
})
Argument | Type | Description |
---|
event | "add" | "clean" | The event name. |
listener | ReadonlyListener | The listener function. |
Returns Unsubscribe
. Unbind listener from event.
Remove reason tag from action’s metadata and remove actions without reason
from log.
onSync(lastSent) {
log.removeReason('unsynchronized', { maxAdded: lastSent })
}
Argument | Type | Description |
---|
reason | string | The reason name. |
criteria ? | Criteria | Criteria to select action for reason removing. |
Returns Promise
. Promise when cleaning will be finished.
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()
}
Argument | Type | Description |
---|
type | string | Action’s type. |
listener | ReadonlyListener | The listener function. |
opts ? | { event?: "add" | "clean", id?: string } | |
Argument | Type |
---|
type | string |
listener | PreaddListener |
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])
})
Parameter | Type | Description |
---|
delay ? | number | Delay for connection and send events. Default is 1 . |
Delay for connection and send events to emulate real connection latency.
Type: number
.
First connection. Will be connected to right
one after connect()
.
new ClientNode('client, log1, pair.left)
Type: LocalConnection
.
Emitted events from left
connection.
await pair.left.connect()
pair.leftEvents
Type: string[][]
.
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
.
Sent messages from left
connection.
await pair.left.send(msg)
pair.leftSent
Type: Message[]
.
Second connection. Will be connected to right
one after connect()
.
new ServerNode('server, log2, pair.right)
Type: LocalConnection
.
Emitted events from right
connection.
await pair.right.connect()
pair.rightEvents
Type: string[][]
.
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
.
Sent messages from right
connection.
await pair.right.send(msg)
pair.rightSent
Type: Message[]
.
Clear all connections events and messages to test only last events.
await client.connection.connect()
pair.clear()
await client.log.add({ type: 'a' })
expect(pair.leftSent).toEqual([
['sync', …]
])
Return Promise until next event.
pair.left.send(['test'])
await pair.wait('left')
pair.leftSend
Argument | Type | Description |
---|
receiver ? | "left" | "right" | Wait for specific receiver event. |
Returns Promise<TestPair>
. Promise until next event.
Virtual server to test client.
let client = new TestClient()
client.server
All actions received from the client.
expect(client.server.log.actions()).toEqual([
{ type: 'logux/subscribe', channel: 'users/10' }
])
Type: TestLog
.
Stop to response with logux/processed
on all new action
and send logux/processed
for all received actions when test
callback will be finished.
await client.server.freezeProcessing(() => {
user.rename('Another name')
expect(user.nameIsSaving).toBe(true)
})
await delay(10)
expect(user.nameIsSaving).toBe(false)
Argument | Type | Description |
---|
test | () => Promise | Function, where server will not send logux/processed . |
Returns Promise
. Promise until test
will be finished.
Define server’s responses for specific channel.
Second call with the same channel name will override previous data.
client.server.onChannel('users/10', [
{ type: 'users/name', userId: 10, value: 'New name' }
])
let user = new UserStore(client, '10')
await delay(10)
expect(user.name).toEqual('New name')
Argument | Type | Description |
---|
channel | string | The channel name. |
response | any | Actions to send back on subscription. |
Set channels for client’s actions.
Argument | Type | Description |
---|
type | Action["type"] | Action type. |
resend | (action: Action, meta: ClientMeta) => string | string[] | Callback returns channel name. |
Send action to all connected clients.
client.server.sendAll(action)
Returns Promise
.
Response with logux/undo
instead of logux/process
on receiving
specific action.
client.server.undoAction(
{ type: 'rename', userId: '10', value: 'Bad name' }
)
user.rename('Good name')
user.rename('Bad name')
await delay(10)
expect(user.name).toEqual('Good name')
Argument | Type | Description |
---|
action | Action | Action to be undone on receiving |
reason ? | string | Optional code for reason. Default is 'error' . |
extra ? | object | Extra fields to logux/undo action. |
Response with logux/undo
instead of logux/process
on next action
from the client.
client.server.undoNext()
user.rename('Another name')
await delay(10)
expect(user.name).toEqual('Old name')
Argument | Type | Description |
---|
reason ? | string | Optional code for reason. Default is 'error' . |
extra ? | object | Extra fields to logux/undo action. |
Creates special logs for test purposes.
Real logs use real time in actions ID,
so log content will be different on every test execution.
To fix it Logux has special logs for tests with simple sequence timer.
All logs from one test should share same time. This is why you should
use log creator to share time between all logs in one test.
import { TestTime } from '@logux/core'
it('tests log', () => {
const log = TestTime.getLog()
})
it('tests 2 logs', () => {
const time = new TestTime()
const log1 = time.nextLog()
const log2 = time.nextLog()
})
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()
})
Returns TestLog
.
Last letd number in log’s nodeId
.
Type: number
.
Return next test log in same time.
it('tests 2 logs', () => {
const time = new TestTime()
const log1 = time.nextLog()
const log2 = time.nextLog()
})
Returns TestLog
.
Disable loader for filter for this builder.
import { emptyInTest, cleanStores } from '@logux/client'
beforeEach(() => {
prepareForTest(client, User, { name: 'Test user 1' })
prepareForTest(client, User, { name: 'Test user 2' })
})
afterEach(() => {
cleanStores(User)
})
Create and load stores to builder’s cache to use them in tests
or storybook.
import { prepareForTest, cleanStores, TestClient } from '@logux/client'
import { User } from '../store'
let client = new TestClient('10')
beforeEach(() => {
prepareForTest(client, User, { name: 'Test user 1' })
prepareForTest(client, User, { name: 'Test user 2' })
})
afterEach(() => {
cleanStores(User)
})
Argument | Type | Description |
---|
client | Client | TestClient instance. |
Template | SyncMapTemplateLike | Store builder. |
value | { id?: string } & Omit<object,"id"> | Store values. |
Returns MapStore<object>
. The mocked store.
Base methods for synchronization nodes. Client and server nodes
are based on this module.
Parameter | Type | Description |
---|
nodeId | string | Unique current machine name. |
log | Log | Logux log instance to be synchronized. |
connection | Connection | Connection to remote node. |
options ? | NodeOptions | Synchronization options. |
Did we finish remote node authentication.
Type: boolean
.
Is synchronization in process.
node.on('disconnect', () => {
node.connected
})
Type: boolean
.
Connection used to communicate to remote node.
Type: Connection
.
Promise for node data initial loadiging.
Type: Promise
.
Latest remote node’s log added
time, which was successfully
synchronized. It will be saves in log store.
Type: number
.
Latest current log added
time, which was successfully synchronized.
It will be saves in log store.
Type: number
.
Unique current machine name.
console.log(node.localNodeId + ' is started')
Type: string
.
Used Logux protocol.
if (tool.node.localProtocol !== 1) {
throw new Error('Unsupported Logux protocol')
}
Type: number
.
Log for synchronization.
Type: Log
.
Minimum version of Logux protocol, which is supported.
console.log(`You need Logux protocol ${node.minProtocol} or higher`)
Type: number
.
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
.
Unique name of remote machine.
It is undefined until nodes handshake.
console.log('Connected to ' + node.remoteNodeId)
Type: string
.
Remote node Logux protocol.
It is undefined until nodes handshake.
if (node.remoteProtocol >= 5) {
useNewAPI()
} else {
useOldAPI()
}
Type: number
.
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
.
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
.
Time difference between nodes.
Type: number
.
Disable throwing a error on error message and create error listener.
node.catch(error => {
console.error(error)
})
Argument | Type | Description |
---|
listener | (error: LoguxError) => void | The error listener. |
Returns Unsubscribe
. Unbind listener from event.
Shut down the connection and unsubscribe from log events.
connection.on('disconnect', () => {
server.destroy()
})
Argument | Type |
---|
event | "headers" |
listener | (headers: object) => void |
Argument | Type |
---|
event | "error" | "clientError" |
listener | (error: LoguxError) => void |
Argument | Type | Description |
---|
event | "state" | "connect" | "debug" | "headers" | Event name. |
listener | () => void | The listener function. |
Argument | Type |
---|
event | "debug" |
listener | (type: "error", data: string) => void |
Returns Unsubscribe
.
Set headers for current node.
if (navigator) {
node.setLocalHeaders({ language: navigator.language })
}
node.connection.connect()
Argument | Type | Description |
---|
headers | object | The data object will be set as headers for current node. |
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')
Argument | Type | Description |
---|
state | NodeState | The expected synchronization state value. |
Returns Promise
. Promise until specific state.
Abstract interface for connection to synchronize logs over it.
For example, WebSocket or Loopback.
Is connection is enabled.
Type: boolean
.
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.
Finish current connection.
Argument | Type | Description |
---|
reason ? | "timeout" | "error" | "destroy" | Disconnection reason. |
Argument | Type |
---|
event | "disconnect" |
listener | (reason: string) => void |
Argument | Type |
---|
event | "error" |
listener | (error: Error) => void |
Argument | Type | Description |
---|
event | "connecting" | "disconnect" | "connect" | Event name. |
listener | () => void | Event listener. |
Argument | Type |
---|
event | "message" |
listener | (msg: Message) => void |
Returns Unsubscribe
.
Send message to connection.
Argument | Type | Description |
---|
message | Message | The 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' })
Parameter | Type | Description |
---|
opts | LogOptions | Log options. |
Unique node ID. It is used in action IDs.
Type: string
.
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 })
})
Argument | Type | Description |
---|
action | Action | The new action. |
meta ? | Partial<ClientMeta> | Open structure for action metadata. |
Returns Promise<false | ClientMeta>
. Promise with meta
if action was added to log or false
if action was already in log.
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 })
}
Argument | Type | Description |
---|
id | string | Action ID. |
Returns Promise<[null, null] | [Action, ClientMeta]>
. Promise with array of action and metadata.
Change action metadata. You will remove action by setting reasons: []
.
await process(action)
log.changeMeta(action, { status: 'processed' })
Argument | Type | Description |
---|
id | string | Action ID. |
diff | Partial<ClientMeta> | Object with values to change in action metadata. |
Returns Promise<boolean>
. Promise with true
if metadata was changed or false
on unknown ID.
Argument | Type | Description |
---|
callback | ActionIterator | Function will be executed on every action. |
Returns Promise
.
Generate next unique action ID.
const id = log.generateId()
Returns string
. Unique ID for action.
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')
}
})
Argument | Type | Description |
---|
event | "add" | "clean" | The event name. |
listener | ReadonlyListener | The listener function. |
Returns Unsubscribe
. Unbind listener from event.
Remove reason tag from action’s metadata and remove actions without reason
from log.
onSync(lastSent) {
log.removeReason('unsynchronized', { maxAdded: lastSent })
}
Argument | Type | Description |
---|
reason | string | The reason name. |
criteria ? | Criteria | Criteria to select action for reason removing. |
Returns Promise
. Promise when cleaning will be finished.
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()
}
Argument | Type | Description |
---|
type | string | Action’s type. |
listener | ReadonlyListener | The listener function. |
opts ? | { event?: "add" | "clean", id?: string } | |
Argument | Type |
---|
type | string |
listener | PreaddListener |
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()
})
Add action to store. Action always will have type
property.
Returns Promise<false | ClientMeta>
. Promise with meta
for new action or false
if action with
same meta.id
was already in store.
Return action by action ID.
Argument | Type | Description |
---|
id | string | Action ID. |
Returns Promise<[Action, ClientMeta] | [null, null]>
. Promise with array of action and metadata.
Change action metadata.
Argument | Type | Description |
---|
id | string | Action ID. |
diff | Partial<ClientMeta> | Object with values to change in action metadata. |
Returns Promise<boolean>
. Promise with true
if metadata was changed or false
on unknown ID.
Remove all data from the store.
Returns Promise
. Promise when cleaning will be finished.
Return a Promise with first page. Page object has entries
property
with part of actions and next
property with function to load next page.
If it was a last page, next
property should be empty.
This tricky API is used, because log could be very big. So we need
pagination to keep them in memory.
Argument | Type | Description |
---|
opts ? | GetOptions | Query options. |
Returns Promise<LogPage>
. Promise with first page.
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.
Get added
values for latest synchronized received/sent events.
Returns Promise<LastSynced>
. Promise with added
values
Remove action from store.
Argument | Type | Description |
---|
id | string | Action ID. |
Returns Promise<false | [Action, ClientMeta]>
. Promise with entry if action was in store.
Remove reason from action’s metadata and remove actions without reasons.
Argument | Type | Description |
---|
reason | string | The reason name. |
criteria | Criteria | Criteria to select action for reason removing. |
callback | ReadonlyListener | Callback for every removed action. |
Returns Promise
. Promise when cleaning will be finished.
Set added
value for latest synchronized received or/and sent events.
Argument | Type | Description |
---|
values | Partial<LastSynced> | Object with latest sent or received values. |
Returns Promise
. Promise when values will be saved to store.
Extends Connection.
Wrapper for Connection for re-connecting it on every disconnect.
import { ClientNode, Reconnect } from '@logux/core'
const recon = new Reconnect(connection)
new ClientNode(nodeId, log, recon, options)
Fails attempts since the last connected state.
Type: number
.
Is connection is enabled.
Type: boolean
.
Are we in the middle of connecting.
Type: boolean
.
Should we re-connect connection on next connection break.
Next connect
call will set to true
.
function lastTry () {
recon.reconnecting = false
}
Type: boolean
.
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.
Finish current connection.
Argument | Type | Description |
---|
reason ? | "timeout" | "error" | "destroy" | Disconnection reason. |
Argument | Type |
---|
event | "disconnect" |
listener | (reason: string) => void |
Argument | Type |
---|
event | "error" |
listener | (error: Error) => void |
Argument | Type | Description |
---|
event | "connecting" | "disconnect" | "connect" | Event name. |
listener | () => void | Event listener. |
Argument | Type |
---|
event | "message" |
listener | (msg: Message) => void |
Returns Unsubscribe
.
Send message to connection.
Argument | Type | Description |
---|
message | Message | The message to be sent. |
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
}
Argument | Type | Description |
---|
firstMeta | string | ClientMeta | Some action’s metadata. |
secondMeta | string | ClientMeta | Other action’s metadata. |
Returns boolean
.
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)
Argument | Type | Description |
---|
id | string | Action or Node ID |
Returns IDComponents
.
Property | Type | Description |
---|
type | string | Action type name. |
Property | Type |
---|
type | string |
[extra: string] | any |
Argument | Type |
---|
nodeId | string |
token | string |
headers | { } | object |
Returns Promise<boolean>
.
Auth store. Use createAuth
to create it.
Property | Type | Description |
---|
loading | Promise | While store is loading initial state. |
Property | Type |
---|
denied | string |
disconnected | string |
error | string |
protocolError | string |
sending | string |
syncError | string |
synchronized | string |
wait | string |
Property | Type | Description |
---|
duration ? | number | Synchronized state duration. Default is 3000 . |
messages | BadgeMessages | Widget text for different states. |
position ? | "bottom-center" | "bottom-left" | "bottom-right" | "middle-center" | "middle-left" | "middle-right" | "top-center" | "top-left" | "top-right" | Widget position. Default is bottom-right . |
styles | BadgeStyles | Inline styles for different states. |
Property | Type |
---|
base | object |
connecting | object |
disconnected | object |
error | object |
icon | { disconnected: string, error: string, protocolError: string, sending: string, synchronized: string, wait: string } |
protocolError | object |
sending | object |
synchronized | object |
text | object |
wait | object |
Property | Type | Description |
---|
noAutoReason ? | boolean | Disable setting timeTravel reason. |
sync ? | boolean | This action should be synchronized with other browser tabs and server. |
tab ? | string | Action should be visible only for browser tab with the same client.tabId . |
Extends BaseNode.
Client node in synchronization pair.
Instead of server node, it initializes synchronization
and sends connect message.
import { ClientNode } from '@logux/core'
const connection = new BrowserConnection(url)
const node = new ClientNode(nodeId, log, connection)
Parameter | Type | Description |
---|
nodeId | string | Unique current machine name. |
log | Log | Logux log instance to be synchronized. |
connection | Connection | Connection to remote node. |
options ? | NodeOptions | Synchronization options. |
Did we finish remote node authentication.
Type: boolean
.
Is synchronization in process.
node.on('disconnect', () => {
node.connected
})
Type: boolean
.
Connection used to communicate to remote node.
Type: Connection
.
Promise for node data initial loadiging.
Type: Promise
.
Latest remote node’s log added
time, which was successfully
synchronized. It will be saves in log store.
Type: number
.
Latest current log added
time, which was successfully synchronized.
It will be saves in log store.
Type: number
.
Unique current machine name.
console.log(node.localNodeId + ' is started')
Type: string
.
Used Logux protocol.
if (tool.node.localProtocol !== 1) {
throw new Error('Unsupported Logux protocol')
}
Type: number
.
Log for synchronization.
Type: Log
.
Minimum version of Logux protocol, which is supported.
console.log(`You need Logux protocol ${node.minProtocol} or higher`)
Type: number
.
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
.
Unique name of remote machine.
It is undefined until nodes handshake.
console.log('Connected to ' + node.remoteNodeId)
Type: string
.
Remote node Logux protocol.
It is undefined until nodes handshake.
if (node.remoteProtocol >= 5) {
useNewAPI()
} else {
useOldAPI()
}
Type: number
.
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
.
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
.
Time difference between nodes.
Type: number
.
Disable throwing a error on error message and create error listener.
node.catch(error => {
console.error(error)
})
Argument | Type | Description |
---|
listener | (error: LoguxError) => void | The error listener. |
Returns Unsubscribe
. Unbind listener from event.
Shut down the connection and unsubscribe from log events.
connection.on('disconnect', () => {
server.destroy()
})
Argument | Type |
---|
event | "headers" |
listener | (headers: object) => void |
Argument | Type |
---|
event | "error" | "clientError" |
listener | (error: LoguxError) => void |
Argument | Type | Description |
---|
event | "state" | "connect" | "debug" | "headers" | Event name. |
listener | () => void | The listener function. |
Argument | Type |
---|
event | "debug" |
listener | (type: "error", data: string) => void |
Returns Unsubscribe
.
Set headers for current node.
if (navigator) {
node.setLocalHeaders({ language: navigator.language })
}
node.connection.connect()
Argument | Type | Description |
---|
headers | object | The data object will be set as headers for current node. |
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')
Argument | Type | Description |
---|
state | NodeState | The expected synchronization state value. |
Returns Promise
. Promise until specific state.
Property | Type | Description |
---|
allowDangerousProtocol ? | boolean | Do not show warning when using ws:// in production. |
attempts ? | number | Maximum reconnection attempts. Default is Infinity . |
maxDelay ? | number | Maximum delay between reconnections. Default is 5000 . |
minDelay ? | number | Minimum delay between reconnections. Default is 1000 . |
ping ? | number | Milliseconds since last message to test connection by sending ping.
Default is 10000 . |
prefix ? | string | Prefix for IndexedDB database to run multiple Logux instances
in the same browser. Default is logux . |
server | any | Server URL. |
store ? | LogStore | Store to save log data. Default is MemoryStore . |
subprotocol | string | Client subprotocol version in SemVer format. |
time ? | TestTime | Test time to test client. |
timeout ? | number | Timeout in milliseconds to break connection. Default is 70000 . |
token ? | any | Client credentials for authentication. |
userId | string | User ID. |
Property | Type |
---|
id | number | [number, string, number] |
time | number |
Create stores to keep client instance and update it on user ID changes.
import { createClientStore, Client, log } from '@logux/client'
import { persistentMap } from '@nanostores/persistent'
let sessionStore = persistentMap<{ userId: string }>('session:', {
userId: 'anonymous'
})
export const clientStore = createClientStore(sessionStore, session => {
let client new Client({
subprotocol: SUBPROTOCOL,
server: 'ws://example.com',
userId: session.userId
})
log(client)
return client
})
Argument | Type | Description |
---|
userIdStore | MapStore<{ userId }> | Store with object and userId key. |
builder | (value: { userId }) => Client | Callback which return client |
Argument | Type |
---|
userIdStore | MapStore<{ userId }> |
builder | (value: { userId }) => Client |
Returns Atom<Client>
. Atom store with client
Property | Type | Description |
---|
id ? | string | Remove reason only for action with id . |
maxAdded ? | number | Remove reason only for actions with lower added . |
minAdded ? | number | Remove reason only for actions with bigger added . |
olderThan ? | ClientMeta | Remove reason only older than specific action. |
youngerThan ? | ClientMeta | Remove reason only younger than specific action. |
Property | Type |
---|
[key: string] | undefined |
Property | Type | Description |
---|
error ? | string | Error favicon link. |
normal ? | string | Default favicon link. By default, it will be taken from current favicon. |
offline ? | string | Offline favicon link. |
Property | Type |
---|
[key: string] | any |
Property | Type |
---|
listChangesOnly ? | boolean |
Property | Type | Description |
---|
loading | Promise | While store is loading initial data from server or log. |
Property | Type | Description |
---|
index ? | string | Get entries with a custom index. |
order ? | "added" | "created" | Sort entries by created time or when they was added to current log. |
Action unique ID accross all nodes.
"1564508138460 380:R7BNGAP5:px3-J3oc 0"
Type: string
.
Property | Type |
---|
clientId | string |
nodeId | string |
userId | string |
Property | Type | Description |
---|
received | number | The added value of latest received event. |
sent | number | The added value of latest sent event. |
Type: { isLoading: false } & { isLoading }
.
Extends Connection.
Is connection is enabled.
Type: boolean
.
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.
Finish current connection.
Argument | Type | Description |
---|
reason ? | "timeout" | "error" | "destroy" | Disconnection reason. |
Argument | Type |
---|
event | "disconnect" |
listener | (reason: string) => void |
Argument | Type |
---|
event | "error" |
listener | (error: Error) => void |
Argument | Type | Description |
---|
event | "connecting" | "disconnect" | "connect" | Event name. |
listener | () => void | Event listener. |
Argument | Type |
---|
event | "message" |
listener | (msg: Message) => void |
Returns Unsubscribe
.
Send message to connection.
Argument | Type | Description |
---|
message | Message | The 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)
Parameter | Type | Description |
---|
delay ? | number | Delay for connection and send events. Default is 1 . |
Delay for connection and send events to emulate real connection latency.
Type: number
.
First connection. Will be connected to right
one after connect()
.
new ClientNode('client, log1, pair.left)
Type: LocalConnection
.
Second connection. Will be connected to right
one after connect()
.
new ServerNode('server, log2, pair.right)
Type: LocalConnection
.
Property | Type | Description |
---|
add ? | boolean | Disable action added messages. |
clean ? | boolean | Disable action cleaned messages. |
error ? | boolean | Disable error messages. |
ignoreActions ? | string[] | Disable action messages with specific types. |
role ? | boolean | Disable tab role messages. |
state ? | boolean | Disable connection state messages. |
user ? | boolean | Disable user ID changing. |
Property | Type | Description |
---|
nodeId | string | Unique current machine name. |
store | LogStore | Store for log. |
Every Store class should provide 8 standard methods.
Add action to store. Action always will have type
property.
Returns Promise<false | ClientMeta>
. Promise with meta
for new action or false
if action with
same meta.id
was already in store.
Return action by action ID.
Argument | Type | Description |
---|
id | string | Action ID. |
Returns Promise<[Action, ClientMeta] | [null, null]>
. Promise with array of action and metadata.
Change action metadata.
Argument | Type | Description |
---|
id | string | Action ID. |
diff | Partial<ClientMeta> | Object with values to change in action metadata. |
Returns Promise<boolean>
. Promise with true
if metadata was changed or false
on unknown ID.
Remove all data from the store.
Returns Promise
. Promise when cleaning will be finished.
Return a Promise with first page. Page object has entries
property
with part of actions and next
property with function to load next page.
If it was a last page, next
property should be empty.
This tricky API is used, because log could be very big. So we need
pagination to keep them in memory.
Argument | Type | Description |
---|
opts ? | GetOptions | Query options. |
Returns Promise<LogPage>
. Promise with first page.
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.
Get added
values for latest synchronized received/sent events.
Returns Promise<LastSynced>
. Promise with added
values
Remove action from store.
Argument | Type | Description |
---|
id | string | Action ID. |
Returns Promise<false | [Action, ClientMeta]>
. Promise with entry if action was in store.
Remove reason from action’s metadata and remove actions without reasons.
Argument | Type | Description |
---|
reason | string | The reason name. |
criteria | Criteria | Criteria to select action for reason removing. |
callback | ReadonlyListener | Callback for every removed action. |
Returns Promise
. Promise when cleaning will be finished.
Set added
value for latest synchronized received or/and sent events.
Argument | Type | Description |
---|
values | Partial<LastSynced> | Object with latest sent or received values. |
Returns Promise
. Promise when values will be saved to store.
Extends Error
.
Logux error in logs synchronization.
if (error.name === 'LoguxError') {
console.log('Server throws: ' + error.description)
}
Return a error description by it code.
Returns string
.
Human-readable error description.
console.log('Server throws: ' + error.description)
Type: string
.
Full text of error to print in debug message.
Type: string
.
Always equal to LoguxError
. The best way to check error class.
if (error.name === 'LoguxError') {
Type: "LoguxError"
.
Error options depends on error type.
if (error.type === 'timeout') {
console.error('A timeout was reached (' + error.options + ' ms)')
}
Type: LoguxErrorOptions[keyof LoguxErrorOptions]
.
Was error received from remote client.
Type: boolean
.
Calls which cause the error.
Type: string
.
The error code.
if (error.type === 'timeout') {
fixNetwork()
}
Type: keyof LoguxErrorOptions
.
Property | Type |
---|
bruteforce | void |
timeout | number |
unknown-message | string |
wrong-credentials | void |
wrong-format | string |
wrong-protocol | Versions |
wrong-subprotocol | Versions |
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()
},
…
})
Type: "LoguxNotFoundError"
.
Property | Type |
---|
id | string |
type | "logux/processed" |
Property | Type |
---|
channel | string |
creating ? | true |
filter ? | { [key: string]: boolean | number | string } |
since ? | { id: string, time: number } |
type | "logux/subscribe" |
Property | Type |
---|
channel | string |
type | "logux/subscribed" |
Property | Type |
---|
action | Action |
id | string |
reason | string |
type | "logux/undo" |
Extends Error
.
Error on logux/undo
action from the server.
try {
client.sync(action)
} catch (e) {
if (e.name === 'LoguxUndoError') {
console.log(e.action.action.type ' was undid')
}
}
Server logux/undo
action. It has origin actions (which was undid)
in action.action
.
console.log(error.action.action.type ' was undid')
Type: LoguxUndoAction
.
The better way to check error, than instanceof
.
if (error.name === 'LoguxUndoError') {
Type: "LoguxUndoError"
.
Property | Type |
---|
channel | string |
filter ? | { [key: string]: boolean | number | string } |
type | "logux/unsubscribe" |
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]
.
Property | Type | Description |
---|
added | number | Sequence number of action in current log. Log fills it. |
id | string | Action unique ID. Log sets it automatically. |
indexes ? | string[] | Indexes for action quick extraction. |
keepLast ? | string | Set value to reasons and this reason from old action. |
reasons | string[] | Why action should be kept in log. Action without reasons will be removed. |
subprotocol ? | string | Set code as reason and remove this reasons from previous actions. |
time | number | Action created time in current node time. Milliseconds since UNIX epoch. |
[extra: string] | any | |
Property | Type | Description |
---|
auth ? | Authenticator | Function to check client credentials. |
fixTime ? | boolean | Detect difference between client and server and fix time
in synchronized actions. |
onReceive ? | ActionFilter | Function to filter or change actions coming from remote node’s
before put it to current log. |
onSend ? | ActionFilter | Function to filter or change actions before sending to remote node’s. |
ping ? | number | Milliseconds since last message to test connection by sending ping. |
subprotocol ? | string | Application subprotocol version in SemVer format. |
timeout ? | number | Timeout in milliseconds to wait answer before disconnect. |
token ? | string | TokenGenerator | Client credentials. For example, access token. |
Type: "connecting" | "disconnected" | "sending" | "synchronized"
.
Property | Type | Description |
---|
attempts ? | number | Maximum reconnecting attempts. |
maxDelay ? | number | Maximum delay between re-connecting. |
minDelay ? | number | Minimum delay between re-connecting. |
Property | Type | Description |
---|
allowDangerousProtocol ? | boolean | Do not show warning when using ws:// in production. |
attempts ? | number | Maximum reconnection attempts. Default is Infinity . |
maxDelay ? | number | Maximum delay between reconnections. Default is 5000 . |
minDelay ? | number | Minimum delay between reconnections. Default is 1000 . |
ping ? | number | Milliseconds since last message to test connection by sending ping.
Default is 10000 . |
prefix ? | string | Prefix for IndexedDB database to run multiple Logux instances
in the same browser. Default is logux . |
server | any | Server URL. |
store ? | LogStore | Store to save log data. Default is MemoryStore . |
subprotocol | string | Client subprotocol version in SemVer format. |
time ? | TestTime | Test time to test client. |
timeout ? | number | Timeout in milliseconds to break connection. Default is 70000 . |
token ? | any | Client credentials for authentication. |
userId ? | string | |
Argument | Type |
---|
current | "connecting" | "connectingAfterWait" | "denied" | "disconnected" | "error" | "protocolError" | "syncError" | "synchronized" | "synchronizedAfterWait" | "wait" |
details | { action: Action, meta: ClientMeta } | { error: Error } |
Property | Type | Description |
---|
duration ? | number | Synchronized state duration. Default is 3000 . |
Property | Type |
---|
fields | Partial<Omit<SyncMapValues,"id">> |
id | string |
type | string |
Property | Type |
---|
fields | Partial<Omit<SyncMapValues,"id">> |
id | string |
type | string |
Property | Type |
---|
id | string |
type | string |
Property | Type |
---|
id | string |
type | string |
Property | Type | Description |
---|
client | Client | Logux Client instance. |
createdAt ? | ClientMeta | Meta from create action if the store was created locally. |
deleted ? | true | Mark that store was deleted. |
loading | Promise | While store is loading initial data from server or log. |
offline | boolean | Does store keep data in the log after store is destroyed. |
plural | string | Name of map class. |
remote | boolean | Does store use server to load and save data. |
Type: boolean | null | number | string | undefined
.
Property | Type |
---|
headers ? | object |
server ? | TestServer |
subprotocol ? | string |
Property | Type | Description |
---|
nodeId ? | string | Unique log name. |
store ? | LogStore | Store for log. Will use MemoryStore by default. |
Returns string | Promise<string>
.
Property | Type |
---|
supported | string |
used | string |
Extends Connection.
Logux connection for browser WebSocket.
import { WsConnection } from '@logux/core'
const connection = new WsConnection('wss://logux.example.com/')
const node = new ClientNode(nodeId, log, connection, opts)
Parameter | Type | Description |
---|
url | string | WebSocket server URL. |
Class ? | any | |
opts ? | any | Extra option for WebSocket constructor. |
Is connection is enabled.
Type: boolean
.
WebSocket instance.
Type: WebSocket
.
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.
Finish current connection.
Argument | Type | Description |
---|
reason ? | "timeout" | "error" | "destroy" | Disconnection reason. |
Argument | Type |
---|
event | "disconnect" |
listener | (reason: string) => void |
Argument | Type |
---|
event | "error" |
listener | (error: Error) => void |
Argument | Type | Description |
---|
event | "connecting" | "disconnect" | "connect" | Event name. |
listener | () => void | Event listener. |
Argument | Type |
---|
event | "message" |
listener | (msg: Message) => void |
Returns Unsubscribe
.
Send message to connection.
Argument | Type | Description |
---|
message | Message | The message to be sent. |
Property | Type |
---|
d | string |
iv | string |
type | "0" |
Property | Type |
---|
id | string |
type | "0/clean" |
Create store with user’s authentication state.
import { createAuth } from '@logux/client'
let auth = createAuth(client)
await auth.loading
console.log(auth.get())
Argument | Type | Description |
---|
client | Client | Logux Client. |
Returns AuthStore
.
Returns actions for CRDT Map.
import { defineSyncMapActions } from '@logux/actions'
const [
createUserAction,
changeUserAction,
deleteUserAction,
createdUserAction,
changedUserAction,
deletedUserAction
] = defineSyncMapActions('users')
Returns [ActionCreator, ActionCreator, ActionCreator, ActionCreator, ActionCreator, ActionCreator]
.
Pass all common tests for Logux store to callback.
import { eachStoreCheck } from '@logux/core'
eachStoreCheck((desc, creator) => {
it(desc, creator(() => new CustomStore()))
})
Argument | Type | Description |
---|
test | (name: string, testCreator: (storeCreator: () => LogStore) => () => void) => void | Callback to create tests in your test framework. |
Change store’s value type to value with isLoaded: false
.
If store is still loading, this function will trow an error.
Use it for tests written on TypeScript.
import { ensureLoaded } from '@logux/client'
expect(ensureLoaded($currentUser)).toEqual({ id: 1, name: 'User' })
Returns LoadedSyncMapValue
.
Return store’s value if store is loaded or wait until store will be loaded
and return its value.
Returns undefined
on 404.
import { loadValue } from '@logux/client'
let user = loadValue($currentUser)
Returns Promise<LoadedValue | undefined>
.
Returns logux/undo
action.
Argument | Type |
---|
fields | { action: Action, id: string, reason: string } |
Returns LoguxUndoAction
.
Low-level function to show Logux synchronization status with your custom UI.
It is used in badge
widget.
import { status } from '@logux/client'
status(client, current => {
updateUI(current)
})
Returns () => void
. Unbind listener.
Track for logux/processed
or logux/undo
answer from server
for the cases when Client#sync
can’t be used.
client.type('pay', (action, meta) => {
track(client, id).then(() => {
console.log('paid')
}).catch(() => {
console.log('unpaid')
})
})
Argument | Type | Description |
---|
client | any | Logux Client. |
id | ID | Action ID. |
Returns Promise
. Promise when action was proccessed.