Logux is a new way to connect client and server. Instead of sending HTTP requests (e.g., AJAX and GraphQL) it synchronizes log of operations between client, server, and other clients.
- Guide, recipes, and API
- Issues and roadmap
- Projects inside Logux ecosystem
This repository contains Logux base components to build web client:
CrossTabClientandClientto create web client for Logux.IndexedStoreto store Logux log inIndexedDB.SqlLogStoreto store Logux log in SQLite or PGlite.badge()widget to show Logux synchronization status in UI.status()to write own UI to show Logux synchronization status in UI.attention(),confirm(),favicon()to improve UX in Logux web app.log()to print Logux synchronization status to browser DevTools.
Check demo page for widget UI.
Logux Client is built by Evil Martians, an American design and engineering consultancy for developer tools, AI, and cybersecurity startups.
npm install @logux/core @logux/client nanostoresSee documentation for Logux API.
import { CrossTabClient, badge, badgeEn, log } from '@logux/client'
import { badgeStyles } from '@logux/client/badge/styles'
let userId = document.querySelector('meta[name=user]').content
let token = document.querySelector('meta[name=token]').content
const client = new CrossTabClient({
subprotocol: 1,
server: 'wss://example.com:1337',
userId,
token
})
badge(client, { messages: badgeEn, styles: badgeStyles })
log(client)
client.start()@logux/client/db keeps CRDT tables in a local SQL database
(like SQLite in the browser) filled from Logux log. All changes are
synchronized as Logux actions, and edit conflicts are resolved with
per-field last write wins.
It needs Nano Stores SQL database:
npm install @nanostores/sqlimport { openDb } from '@nanostores/sql'
import { sqlocalDriver } from '@nanostores/sql/sqlocal'
import {
bigint,
createCrdtDatabase,
number,
optional,
string
} from '@logux/client/db'
let db = openDb(sqlocalDriver('app.sqlite'))
let crdt = createCrdtDatabase(client, db, {
migrating(done) {
// Show “Migrating database” loader until done promise
},
async repeat() {
// Ask server to the full client log
// Remove if you store the whole lo locally
}
})
// The third argument defines indexes: a column, an array of columns,
// `{ columns, unique }`, or the whole `CREATE INDEX` statement in `{ sql }`
let user = crdt.table(
'user',
{
age: optional(number()),
createdAt: bigint({ default: () => Date.now() }),
name: string()
},
['name', ['age', 'createdAt DESC']]
)
await crdt.ready
let id = await user.create({ name: 'Ann' })
await user.update(id, { age: 30 })
let $adults = user.select`WHERE "age" >= ${18} ORDER BY "name"`
// Arrays create batch actions, applied in a single SQL query
let ids = await user.create([{ name: 'Ben' }, { name: 'Cat' }])
await user.update(ids, { age: 20 })
await user.delete(ids)Custom actions are applied to the database by your callback. Action types
in the callback come from defineAction():
import { defineAction } from '@logux/actions'
let userRenamed = defineAction<{
id: string
name: string
type: 'user/renamed'
}>('user/renamed')
let renameUser = crdt.action(userRenamed, async (tx, action, meta) => {
// change() keeps per-field last write wins, unlike your own UPDATE
await user.change(tx, action.id, { name: action.name }, meta)
})
await renameUser({ id, name: 'New' })The same database can keep the Logux log itself:
import { CrossTabClient } from '@logux/client'
import { SqlLogStore } from '@logux/client/db'
const client = new CrossTabClient({
…,
store: new SqlLogStore(db)
})encryptActions() encrypts actions before sending them to the server,
so the server can’t read users’ data. Pass a password or an AES
CryptoKey and list action types to be kept unencrypted.
import { encryptActions } from '@logux/client'
encryptActions(client, localStorage.getItem('userPassword'), {
ignore: ['server/public']
})