createWsServer
The createWsServer
function creates a WsServer
that facilitates synchronization between clients that are using WsSynchronizer
instances.
createWsServer<PathPersister>(
webSocketServer: WebSocketServer,
createPersisterForPath?: (pathId: string) => undefined | PathPersister | [PathPersister, (store: MergeableStore) => void] | Promise<PathPersister> | Promise<[PathPersister, (store: MergeableStore) => void]>,
onIgnoredError?: (error: any) => void,
): WsServer
Type | Description | |
---|---|---|
webSocketServer | WebSocketServer | A WebSocketServer object from your server environment. |
createPersisterForPath? | (pathId: string) => undefined | PathPersister | [PathPersister, (store: MergeableStore) => void] | Promise<PathPersister> | Promise<[PathPersister, (store: MergeableStore) => void]> | An optional function that will create a |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the server would otherwise ignore when trying to sync data. This is suitable for debugging issues in a development environment. |
returns | WsServer | A reference to the new |
This should be run in a server environment, and you must pass in a configured WebSocketServer object in order to create it.
If you want your server to persist data itself, you can use the optional second parameter of this function, which allows you to create a Persister
for a new path - whenever a new path is accessed by a client. This Persister
will only exist when there are active clients on that particular path. The creation callback can be asynchronous.
You are responsible for creating a MergeableStore
to pass to this Persister
, but starting and stopping its automatic saving and loading is taken care of by the WsServer
. As a result, the server MergeableStore
will be kept in sync with the clients on that path, and in turn with whatever persistence layer you have configured. See the example below.
It is not safe to add or manipulate data in the MergeableStore
during the createPersisterForPath
function, since changes will probably be overwritten when the Persister
starts. If you wish to modify data - or upgrade a schema, for example - you can have that function instead return an array containing the Persister
and a callback that takes the MergeableStore
. That callback will get called after the Persister
has started, and is an appropriate place to manipulate data in a way that will be transmitted to clients. Again, see the example below.
Examples
This example creates a WsServer
that synchronizes two clients on a shared path.
import {WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
// Server
const server = createWsServer(new WebSocketServer({port: 8047}));
// Client 1
const clientStore1 = createMergeableStore();
clientStore1.setCell('pets', 'fido', 'species', 'dog');
const synchronizer1 = await createWsSynchronizer(
clientStore1,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer1.startSync();
// ...
// Client 2
const clientStore2 = createMergeableStore();
clientStore2.setCell('pets', 'felix', 'species', 'cat');
const synchronizer2 = await createWsSynchronizer(
clientStore2,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer2.startSync();
// ...
console.log(clientStore1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(clientStore2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
server.destroy();
This longer example creates a WsServer
that persists a MergeableStore
to file that is synchronized with two clients on a shared path. Later, when a third client connects, it picks up the data the previous two were using.
import {WebSocketServer} from 'ws';
import {createFilePersister} from 'tinybase/persisters/persister-file';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {rmSync} from 'fs';
// Server
const server = createWsServer(
new WebSocketServer({port: 8047}),
(pathId) =>
createFilePersister(
createMergeableStore(),
pathId.replace(/[^a-zA-Z0-9]/g, '-') + '.json',
),
);
// Client 1
const clientStore1 = createMergeableStore();
clientStore1.setCell('pets', 'fido', 'species', 'dog');
const synchronizer1 = await createWsSynchronizer(
clientStore1,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer1.startSync();
// ...
// Client 2
const clientStore2 = createMergeableStore();
clientStore2.setCell('pets', 'felix', 'species', 'cat');
const synchronizer2 = await createWsSynchronizer(
clientStore2,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer2.startSync();
// ...
console.log(clientStore1.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
console.log(clientStore2.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer1.destroy();
synchronizer2.destroy();
// ...
// Client 3 connects later
const clientStore3 = createMergeableStore();
const synchronizer3 = await createWsSynchronizer(
clientStore3,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer3.startSync();
// ...
console.log(clientStore3.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
synchronizer3.destroy();
server.destroy();
// Remove file for the purposes of this demo.
rmSync('petShop.json');
This example creates a WsServer
that persists a MergeableStore
to file that is synchronized with two clients on a shared path, but also which updates its data once synchronization has started.
import {WebSocketServer} from 'ws';
import {createFilePersister} from 'tinybase/persisters/persister-file';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
import {rmSync} from 'fs';
// Server
const server = createWsServer(
new WebSocketServer({port: 8047}),
(pathId) => [
createFilePersister(
createMergeableStore(),
pathId.replace(/[^a-zA-Z0-9]/g, '-') + '.json',
),
(store) => store.setValue('pathId', pathId),
],
);
const clientStore = createMergeableStore();
clientStore.setCell('pets', 'fido', 'species', 'dog');
const synchronizer = await createWsSynchronizer(
clientStore,
new WebSocket('ws://localhost:8047/petShop'),
);
await synchronizer.startSync();
// ...
console.log(clientStore.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {"pathId": "petShop"}]
synchronizer.destroy();
server.destroy();
// Remove file for the purposes of this demo.
rmSync('petShop.json');
This example creates a WsServer
with a custom listener that displays information about the address of the client that connects to it.
import {WebSocket, WebSocketServer} from 'ws';
import {createMergeableStore} from 'tinybase';
import {createWsServer} from 'tinybase/synchronizers/synchronizer-ws-server';
import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';
// On the server:
const webSocketServer = new WebSocketServer({port: 8047});
webSocketServer.on('connection', (_, request) => {
const clientAddress = request.socket.remoteAddress;
if (clientAddress == '::1' || clientAddress == '::ffff:127.0.0.1') {
console.log('Local client connected');
}
});
const server = createWsServer(webSocketServer);
// On a client:
const synchronizer = await createWsSynchronizer(
createMergeableStore(),
new WebSocket('ws://localhost:8047'),
);
// -> 'Local client connected'
synchronizer.destroy();
server.destroy();
Since
v5.0.0