đź“– Guide Documents
Features
Worker Protocol

Outline

TGrid supports Worker/SharedWorker protocols.

With TGrid, you can easily develop Worker programs under the RPC (Remote Procedure Call) concept.

TGrid considers Worker as a 1: 1 dedicated server, and SharedWorker as a 1: N multi-client acceptable server running on the local. Therefore, the interfaces of Worker and SharedWorker in the TGrid are similar with WebSocket components. In such reason, if you're developing a complicate WebSocket system, you can simulate it in the local environment by using Worker/SharedWorker components.

Worker

Available in both Browser/NodeJS.

You can utilize RPC (Remote Procedure Call) even in the Worker.

WorkerConnector

examples/worker/src/client.ts
import { Driver, WorkerConnector } from "tgrid";
 
import { ICalcConfig } from "./interfaces/ICalcConfig";
import { ICalcEvent } from "./interfaces/ICalcEvent";
import { ICalcEventListener } from "./interfaces/ICalcEventListener";
import { ICompositeCalculator } from "./interfaces/ICompositeCalculator";
 
const EXTENSION = __filename.endsWith(".ts") ? "ts" : "js";
 
export const workerClientMain = async () => {
  const stack: ICalcEvent[] = [];
  const listener: ICalcEventListener = {
    on: (evt: ICalcEvent) => stack.push(evt),
  };
  const connector: WorkerConnector<
    ICalcConfig,
    ICalcEventListener,
    ICompositeCalculator
  > = new WorkerConnector(
    { precision: 2 }, // header
    listener, // provider for remote server
    "process",
  );
  await connector.connect(`${__dirname}/server.${EXTENSION}`);
 
  const remote: Driver<ICompositeCalculator> = connector.getDriver();
  console.log(
    await remote.plus(10, 20), // returns 30
    await remote.multiplies(3, 4), // returns 12
    await remote.divides(5, 3), // returns 1.67
    await remote.scientific.sqrt(2), // returns 1.41
    await remote.statistics.mean(1, 3, 9), // returns 4.33
  );
 
  await connector.close();
  console.log(stack);
};
Terminal
$ npm start
30 12 1.67 1.41 4.33
[
  { type: 'plus', input: [ 10, 20 ], output: 30 },
  { type: 'multiplies', input: [ 3, 4 ], output: 12 },
  { type: 'divides', input: [ 5, 3 ], output: 1.67 },
  { type: 'sqrt', input: [ 2 ], output: 1.41 },
  { type: 'mean', input: [ 1, 3, 9 ], output: 4.33 }
]

Worker Connector.

The WorkerConnector is a communicator class, which creates an Worker instance, and interacts with it through RPC (Remote Procedure Call). In other words, WorkerConnector considers the Worker instance as a remote server accepting only one client; WorkerServer.

You can create the Worker instance and communicate with it by WorkerConnector.connect() or WorkerConnector.compile() method. The WorkerConnector.connect() method just opens an existing JS (or TS) file, and the WorkerConnector.compile() method writes a temporary JS (TS) file and connects to it. Anyway, the Worker instanced program must open the WorkerServer.

By the way, don't forget closing the worker to clean up the resources. If the closing be performed by WorkerServer, you can wait the worker server closing through the WorkerConnector.wait() method.

Also, when declaring this WorkerConnector type, you've to define three generic arguments; Header, Provider and Remote. Those generic arguments must be same with the ones defined in the target WorkerServer class (Provider and Remote must be reversed).

For reference, the first Header type represents an initial data from the remote client after the connection. I recommend utilize it as an activation tool for security enhancement. The second generic argument Provider represents a provider from client to server, and the other Remote means a provider from the remote server to client.

  • Above example case:
    • Header: ICalcConfig type
    • Provider: Client is providing ICalcEventListener to the server
    • Remote: Server is providing ISimpleCalculator to the client
đź’»

Demonstration

You can run it on Playground Website (opens in a new tab), or local machine.

Terminal
git clone https://github.com/samchon/tgrid.example.worker
npm install
npm start

WorkerServer

examples/worker/src/server.ts
import { Driver, WorkerServer } from "tgrid";
 
import { ICalcConfig } from "./interfaces/ICalcConfig";
import { ICalcEventListener } from "./interfaces/ICalcEventListener";
import { CompositeCalculator } from "./providers/CompositeCalculator";
 
const main = async () => {
  const server: WorkerServer<
    ICalcConfig,
    CompositeCalculator,
    ICalcEventListener
  > = new WorkerServer();
 
  const header: ICalcConfig = await server.getHeader();
  const listener: Driver<ICalcEventListener> = server.getDriver();
  const provider: CompositeCalculator = new CompositeCalculator(
    header,
    listener,
  );
  await server.open(provider);
};
main().catch((exp) => {
  console.error(exp);
  process.exit(-1);
});

Worker Server.

The WorkerServer is a class representing a Worker server which communicate with client (WorkerConnector), through the RPC (Remote Procedure Call).

Unlike other servers, WorkerServer can accept only one client (WorkerConnector), because the Worker is dependent on its parent instance (web page, node or parent worker). Thus, WorkerServer does not have any acceptor and communicates with client (its parent) directly.

To start communication with the client, call the WorkerServer.open() (opens in a new tab) method with Provider instance. After your business, don't forget closing this Worker instance. If the termination is performed by the WorkerConnector, you can wait the closing signal through the WorkerServer.join() (opens in a new tab) method.

Also, when declaring this WorkerServer type, you've to define three generic arguments; Header, Provider and Remote. Those generic arguments must be same with the ones defined in the target WorkerConnector class (Provider and Remote must be reversed).

For reference, the first Header type represents an initial data from the client after the connection. I recommend utilize it as an activation tool for security enhancement. The second generic argument Provider represents a provider from server to client, and the other Remote means a provider from the client to server.

  • Above example case:
    • Header: ICalcConfig type
    • Provider: Server is providing CompositeCalculator to the client
    • Remote: Client is providing ICalcEventListener to the server

Shared Worker

⚠️

Available only in the Web Browser.

In the Web Browser, you also can perform RPC (Remote Procedure Call) in the SharedWorker.

Also, as SharedWorker can accept multiple clients, TGrid considers it as a local server running on the web browser, and its interfaces are similar with WebSocket components.

SharedWorkerServer

examples/shared-worker/src/server.ts
import { Driver, SharedWorkerServer } from "tgrid";
 
import { ICalcConfig } from "./interfaces/ICalcConfig";
import { ICalcEventListener } from "./interfaces/ICalcEventListener";
import { CompositeCalculator } from "./providers/CompositeCalculator";
 
const main = async () => {
  let pool: number = 0;
  const server: SharedWorkerServer<
    ICalcConfig,
    CompositeCalculator,
    ICalcEventListener
  > = new SharedWorkerServer();
  await server.open(async (acceptor) => {
    // LIST UP PROPERTIES
    const config: ICalcConfig = acceptor.header;
    const listener: Driver<ICalcEventListener> = acceptor.getDriver();
 
    // ACCEPT OR REJECT THE CONNECTION
    if (pool >= 8) {
      await acceptor.reject("Too much connections.");
    } else {
      await acceptor.accept(new CompositeCalculator(config, listener));
      ++pool;
      await acceptor.join();
      --pool;
    }
  });
};
main().catch(console.error);

Shared Worker Server.

The SharedWorkerServer is a class representing a server in SharedWorker environment. Clients connecting to the SharedWorkerServer would communicate with this server through SharedWorkerAcceptor instances using RPC (Remote Procedure Call) concept.

To open the server, call the SharedWorkerServer.open() method with your callback function which would be called whenever a SharedWorkerAcceptor has been newly created by a new client's connection.

Also, when declaring this SharedWorkerServer type, you have to define three generic arguments; Header, Provider and Remote. Those generic arguments would be propagated to the SharedWorkerAcceptor, so that SharedWorkerAcceptor would have the same generic arguments, too.

For reference, the first Header type represents an initial data from the remote client after the connection. I recommend utilize it as an activation tool for security enhancement. The second generic argument Provider represents a provider from server to client, and the other Remote means a provider from the remote client to server.

  • Above example case:
    • Header: ICalcConfig type
    • Provider: Server is providing CompositeCalculator to the client
    • Remote: Client is providing ICalcEventListener to the server

SharedWorkerAcceptor

examples/shared-worker/src/server.ts
import { Driver, SharedWorkerAcceptor, SharedWorkerServer } from "tgrid";
 
import { ICalcConfig } from "./interfaces/ICalcConfig";
import { ICalcEventListener } from "./interfaces/ICalcEventListener";
import { CompositeCalculator } from "./providers/CompositeCalculator";
 
const main = async () => {
  let pool: number = 0;
  const server: SharedWorkerServer<
    ICalcConfig,
    CompositeCalculator,
    ICalcEventListener
  > = new SharedWorkerServer();
  await server.open(
    async (
      acceptor: SharedWorkerAcceptor<
        ICalcConfig,
        CompositeCalculator,
        ICalcEventListener
      >,
    ) => {
      // LIST UP PROPERTIES
      const config: ICalcConfig = acceptor.header;
      const listener: Driver<ICalcEventListener> = acceptor.getDriver();
 
      // ACCEPT OR REJECT THE CONNECTION
      if (pool >= 8) {
        await acceptor.reject("Too much connections.");
      } else {
        await acceptor.accept(new CompositeCalculator(config, listener));
        ++pool;
        await acceptor.join();
        --pool;
      }
    },
  );
};
main().catch(console.error);

Shared Worker Acceptor.

The SharedWorkerAcceptor is a communicator class interacting with the SharedWorkerConnector through RFC (Remote Function Call), created by the SharedWorkerServer class whenever a client connects to the SharedWorker instance.

When a remote client connects to the SharedWorkerServer, so that a new SharedWorkerAcceptor instance being created, you can determine whether to accept the client's connection or not, reading the SharedWorkerAcceptor.header property. If you've decided to accept the connection, call the SharedWorkerAcceptor.accept() method with Provider instance. Otherwise, reject it thorugh the SharedWorkerAcceptor.reject() method.

After accepting the connection, don't forget to closing the connection after your business has been completed to clean up the resources. Otherwise the closing must be performed by the remote client, you can wait the remote client's closing signal by the SharedWorkerAcceptor.join() method.

Also, when declaring this SharedWorkerAcceptor type, you have to define three generic arguments; Header, Provider and Remote. Those generic arguments must be same with the ones defined in the SharedWorkerServer class.

For reference, the first Header type represents an initial data from the remote client after the connection. I recommend utilize it as an activation tool for security enhancement. The second generic argument Provider represents a provider from server to client, and the other Remote means a provider from the remote client to server.

SharedWorkerConnector

examples/shared-worker/src/client.ts
import { Driver, SharedWorkerConnector } from "tgrid";
 
import { ICalcConfig } from "./interfaces/ICalcConfig";
import { ICalcEvent } from "./interfaces/ICalcEvent";
import { ICalcEventListener } from "./interfaces/ICalcEventListener";
import { ICompositeCalculator } from "./interfaces/ICompositeCalculator";
 
const main = async () => {
  const stack: ICalcEvent[] = [];
  const listener: ICalcEventListener = {
    on: (evt: ICalcEvent) => stack.push(evt),
  };
  const connector: SharedWorkerConnector<
    ICalcConfig,
    ICalcEventListener,
    ICompositeCalculator
  > = new SharedWorkerConnector(
    { precision: 2 }, // header
    listener, // provider for remote server
  );
  await connector.connect(`./server.js`);
 
  const remote: Driver<ICompositeCalculator> = connector.getDriver();
  console.log(
    await remote.plus(10, 20), // returns 30
    await remote.multiplies(3, 4), // returns 12
    await remote.divides(5, 3), // returns 1.67
    await remote.scientific.sqrt(2), // returns 1.41
    await remote.statistics.mean(1, 3, 9), // returns 4.33
  );
 
  await connector.close();
  for (const evt of stack) console.log(JSON.stringify(evt));
};
main().catch(console.error);
console
30 12 1.67 1.41 4.33
{"type":"plus","input":[10,20],"output":30}
{"type":"multiplies","input":[3,4],"output":12}
{"type":"divides","input":[5,3],"output":1.67}
{"type":"sqrt","input":[2],"output":1.41}
{"type":"mean","input":[1,3,9],"output":4.33}

Shared Worker Connector.

The SharedWorkerConnector is a communicator class which connects to an SharedWorker instance, and interacts with it through RFC (Remote Function Call) concept.

You can connect to the SharedWorkerServer using SharedWorkerConnector.connect() method. The interaction would be started if the server accepts your connection by calling the SharedWorkerAcceptor.accept() method. If the remote server rejects your connection through SharedWorkerAcceptor.reject() method, the exception would be thrown.

After the connection, don't forget to closing the connection, if your business logics have been completed, to clean up the resources. Otherwise, the closing must be performed by the remote shared worker server, you can wait the remote server's closing signal through the SharedWorkerConnector.join() method.

Also, when declaring this SharedWorkerConnector type, you've to define three generic arguments; Header, Provider and Remote. Those generic arguments must be same with the ones defined in the target SharedWorkerServer and SharedWorkerAcceptor classes (Provider and Remote must be reversed).

For reference, the first Header type represents an initial data from the remote client after the connection. I recommend utilize it as an activation tool for security enhancement. The second generic argument Provider represents a provider from client to server, and the other Remote means a provider from the remote server to client.

  • Above example case:
    • Header: ICalcConfig type
    • Provider: Client is providing ICalcEventListener to the server
    • Remote: Server is providing ISimpleCalculator to the client
đź’»

Demonstration

You can run it on your local machine.

Terminal
git clone https://github.com/samchon/tgrid.example.shared-worker
npm install
npm run build
npm start