Efficient real-time state syncing
Live Replica provides magic like state syncing, allowing client replicas to subscribe to an entire hierarchical data structures or a subset (using jsonpath like syntax) on the server memory.
Upon successful subscription, the client replica receives a complete snapshot and then kept in sync with the server state in real-time. For updates only the delta required for the subscribed part to be up-to-date is sent over the transport.
(documentation is still work in progress)
- Real-time and efficient state syncing using a diff algorithm
- Powerful observability features on both client and server
- Bidirectional syncing (client replica's with write access can also update the server state)
- RPC (Remote Procedure Call) support whenever the server state contains functions
- Multiple server and transport implementations
- WebSocket and Socket.io for browser and node.js
- Worker postMessage for WebWorker / SharedWorker (server) and main thread syncing inside the browser
- IPC for state syncing between node.js processes in a cluster configuration
- Offers both OOP and functional style apis
- Data mutations can be done either explicitly via api calls or simply manipulating javascript proxies
- Server side access control via a middlewares
- First party LitElement / LitHtml integration
npm install live-replica
import { LiveReplica } from '@live-replica/live-replica';
import ws from 'ws';
const server = new LiveReplica.WebSocketServer(ws);
server.listen(8080);
server.set('foo', { bar: 'baz' });
import { LiveReplica } from '@live-replica/live-replica';
const client = new LiveReplica.WebSocketClient('ws://localhost:8080');
client.connect();
const replica = new LiveReplica.Replica();
await replica.connect(client, 'foo');
Live Replica offers a functional style api as an alternative to the OOP style or even mixing the 2 styles together.
It works by utilizing javascript proxies that can be manipulated just like a regular object. This offers a more intuitive way to work and manipulate the state and is more suitable for functional programming.
Obtaining a proxy can be done by either accessing the data
property a replica or
by using the Replica.create
factory function to create proxy to a new replica (implicit replica creation is done behind the scenes)
import { LiveReplica } from '@live-replica/live-replica';
// obtaining a proxy from an existing replica
const replica = new LiveReplica.Replica();
const dataProxy = replica.data;
// obtaining a proxy from a new replica
const dataProxy = LiveReplica.create();
If the proxy is obtained from an existing replica,
connecting to a server can be done by calling the replica.connect()
method on the replica,
however, If the proxy is obtained from Replica.create
and don't have access to the replica you can use the connect()
function to achieve the same result.
example:
await connect(dataProxy, socket, 'foo');
// read
console.log(dataProxy.foo) // returns { bar: 'baz' }
For transactional mutations it follows the lodash style of functions such as merge, set, get supplying the proxy as the first argument and the path as the second argument
const dataProxy = LiveReplica.create({
foo: {
bar: 'baz'
}
});
get(dataProxy, 'foo.bar'); // returns 'baz'
set(dataProxy, 'foo.bar', 'qux'); // sets 'qux' to 'foo.bar'
merge(dataProxy, 'foo', { bar: 'qux' }); // merges { bar: 'qux' } to 'foo'
Live Replica offers 3 options for lit integration that allows you to use the live-replica
- ReactiveController - Enables you to bind a replica to a LitElement life cycle to update the element on state changes
- @observed State / Property decorator - Enables you to bind a replica or any object as an observed @property or @state of a LitElement, this uses the reactive controller under the hood,
- Live Directive - This is a lit-html AsyncDirective that enables you to bind a specific replica part by path to a template part which asynchronously updates the template part on state changes without re-rendering the entire template
Enables you to bind a replica or any object as an observed @property or @state of a LitElement, this uses the reactive controller under the hood,
import { LitElement, html } from 'lit-element';
import { LiveReplica, connect } from '@live-replica/live-replica';
import { observed } from '@live-replica/lit';
class MyElement extends LitElement {
// this will have the same effect as the LiveReplicaController example below, it will update the element on state changes and unwatch on disconnectedCallback
@observed() @state() myState = LiveReplica.create();
disconnectReplica: () => void;
connectedCallback() {
super.connectedCallback?.();
this.disconnectReplica?.(); // in case of re-connecting to the domm
this.disconnectReplica = connect(this.myState, socket, 'server.path');
}
disconnectedCallback() {
this.disconnectReplica?.();
super.disconnectedCallback?.();
}
render() {
return html`<div>${myState?.some?.path?.inside}</div>`;
}
}
If you are not using typescript or needs more control over the watch mechanism you can use LiveReplicaController which is a ReactiveController that enables you to bind a replica to a LitElement life cycle to update the element on state changes
import { LitElement, html } from 'lit-element';
import { LiveReplica } from '@live-replica/live-replica';
import { LiveReplicaController } from '@live-replica/lit';
class MyElement extends LitElement {
constructor() {
super();
const client = new LiveReplica.WebSocketClient('ws://localhost:8080');
client.connect();
const replica = new LiveReplica.Replica();
replica.connect(client, 'foo');
const lrController = new LiveReplicaController(this);
// updates element on replica changes and unwatch on disconnectedCallback
lrController.watch(replica);
// updates element only when 'some.path.inside' changes and unwatch on disconnectedCallback
lrController.watch(replica, 'some.path.inside');
// optionally provide a watch callback to be called on state changes
lrController.watch(replica, 'some.path', (diff) => {
if (!diff?.inside) {
return false; // returning false will prevent the element from updating
}
});
}
render() {
return html`
<live-replica .replica=${this.replica}></live-replica>
`;
}
}
Apache 2.0 - see LICENSE