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'letuserId=document.querySelector('meta[name=user]').contentlettoken=document.querySelector('meta[name=token]').contentconstclient=newCrossTabClient({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'letdb=openDb(sqlocalDriver('app.sqlite'))letcrdt=createCrdtDatabase(client,db,{migrating(done){// Show “Migrating database” loader until done promise},asyncrepeat(){// 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 }`letuser=crdt.table('user',{age: optional(number()),createdAt: bigint({default: ()=>Date.now()}),name: string()},['name',['age','createdAt DESC']])awaitcrdt.readyletid=awaituser.create({name: 'Ann'})awaituser.update(id,{age: 30})let$adults=user.select`WHERE "age" >= ${18} ORDER BY "name"`// Arrays create batch actions, applied in a single SQL queryletids=awaituser.create([{name: 'Ben'},{name: 'Cat'}])awaituser.update(ids,{age: 20})awaituser.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'letuserRenamed=defineAction<{id: stringname: stringtype: 'user/renamed'}>('user/renamed')letrenameUser=crdt.action(userRenamed,async(tx,action,meta)=>{// change() keeps per-field last write wins, unlike your own UPDATEawaituser.change(tx,action.id,{name: action.name},meta)})awaitrenameUser({ id,name: 'New'})The same database can keep the Logux log itself:
import{CrossTabClient}from'@logux/client'import{SqlLogStore}from'@logux/client/db'constclient=newCrossTabClient({
…,store: newSqlLogStore(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']})