TinyBase logoTinyBase

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
TypeDescription
webSocketServerWebSocketServer

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 Persister to synchronize with the clients on a given path (or a two-item array of Persister and callback that lets you handle data after persistence has started).

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.

returnsWsServer

A reference to the new WsServer object.

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) => {
  console.log('Client address: ' + request.socket.remoteAddress);
});
const server = createWsServer(webSocketServer);

// On a client:
const synchronizer = await createWsSynchronizer(
  createMergeableStore(),
  new WebSocket('ws://localhost:8047'),
);
// -> 'Client address: ::1'

synchronizer.destroy();
server.destroy();

Since

v5.0.0