Skip to content

Starter's Guide

Trevor Sears edited this page Jan 30, 2020 · 12 revisions

This guide will serve as a short and sweet introduction to the CommandSocket framework, including the use of both a server and client.

Table of Contents

Before Getting Started

Before we get started, we need to go over a couple of important key concepts of the CommandSocket framework.

Commands

First off, we have commands. Commands are the most important piece of the puzzle, and the Command interface is going to be the one that you will probably be implementing the most. A CommandSocket Command is a structure for defining executable code segments that have well defined inputs and outputs.

Every Command implementation has an #execute function as a part of its definition. The interface's #execute member is defined as follows:

execute(params: C["params"], context: CommandSocket): Promise<C["return"]>;

Let's clarify a couple things about this function signature...

First off, the params: C["params"] and Promise<C["return"]> part. Don't worry too much about exactly what the type of the generic type C is. All you need to know for now is that the params parameter is of a type that can be defined for each individual Command implementation, and the same goes for the return type of the function, although said return type does always need to be a Promise. That said, the generic type of the Promise can be specified.

Commands also have a simple #getName method to implement that should simply return the full command identifier.

getName(): N;

The N generic type can be specified as a generic argument to the Command class just to provide some additional type-checking power, but can also be easily omitted for N to default to the type string.


Command Registries

Command registries are dynamic collections of commands that serve to inform each CommandSocket what commands are available to it. Command registries are formalized in the CommandRegistry class, and has such methods as #addCommands, #hasCommand, and #getCommand which are mostly self-explanatory.

let commandSocketOrServer: CommandSocket | CommandSocketServer = /* init */;

commandSocketOrServer.getCommandRegistry().addCommands(new MyCommand());

// We can now call MyCommand on this CommandSocket or CommandSocketServer!

Command Sets & Command Structures

Command sets are pretty self-explanatory -- they are sets of commands for CommandSockets to utilize. Command sets are formalized in the CommandSet interface. That interface looks something like the following:

interface CommandSet {
    [commandName: string]: CommandStructure;
}

In case you're not familiar with TypeScript's indexable types, what this interface is specifying is that for any objects that implement this interface, that object's members must all have string (not number) identifiers, and must each be of type CommandStructure.

Now, command structures are somewhat less self-explanatory, but equally as simple. Command structures are used to specify the parameter type(s) and return type for a given Command. They are formalized in the CommandStructure interface, which looks like:

interface CommandStructure {
    readonly params: any;
    readonly return: any;
}

These two interfaces, CommandSet and CommandStructure are used in combination to specify to other developers using the CommandSocket framework (in particular, those developers hoping to use the CommandSocket API you are publishing!) what commands are available to them, and what the inputs and outputs of those commands should look like.

As an example, here's what a simple command set might look like for a CommandSocket that performs certain math and logic operations:

let myCommandSet: CommandSet = {
    "my-command-set sum": {
        params: number[],
        return: number
    },
    "my-command-set xor": {
        params: [boolean[], boolean[]],
        return: boolean[]
    },
    "my-command-set stats": {
        params: number[],
        return: {
            mean: number,
            median: number,
            mode: number,
            stdDev: number
        }
    }
}

If you're wondering why the command names/identifiers have all been prefixed with my-command-set, this is because of the importance of namespacing, but we'll return to that later.

Please note that these classes have nothing to do with internally informing the CommandSocket framework as to what commands actually exist, and what those commands' parameter types and return types are. These classes are merely meant as contracts between API designers and API users that can be directly used with TypeScript's generics system to securely typecheck the commands that are being called between CommandSockets. It is your duty as the API developer to keep your API's command set up-to-date with the actual commands that you are registering with your CommandSockets.

Creating the Server

OK, it's time to create the CommandSocket server! To do so, all we have to do it create a new instance of CommandSocketServer from the @command-socket/server package.

The constructor for the CommandSocketServer class takes one argument: the port on which the server should be started.

// Import the CommandSocket server package:
import { CommandSocketServer } from "@command-socket/server";

// Create a new instance of the CommandSocketServer class, starting on port 3849:
let server: CommandSocketServer = new CommandSocketServer(3849);

Now, we can also optionally specify the LCS (local command set) and RCS (remote command set) of our CommandSocketServer as generic arguments upon initialization. Both of these generic arguments must implement the CommandSet interface.

The LCS (local command set) contains information about the commands that are* available locally. This is to say that the commands that are specified in the LCS can be executed by the remotely connected CommandSocket on the local CommandSocket.

The RCS (remote command set) is the opposite, and therefore contains information about the commands that are* available on the remote CommandSocket

* Please read: should be. This behavior is more closely covered in the section on Command Sets & Command Structures.

If we wish to use generics, we can do so as follows:

// Initialize the CommandSocketServer with generic arguments.
let server: CommandSocketServer<MyLCS, MyRCS> = new CommandSocketServer(3849);

In case you missed the intro to command sets, you can find that above, under Command Sets & Command Structures.

A Quick Note:

Now, when I said that:

The constructor for the CommandSocketServer class takes one argument...

I lied by omission - the CommandSocketServer class actually takes two arguments, the second parameter being a pre-initialized CommandRegistry. In this guide we haven't actually covered how to do this yet though, but we will (after learning how to initialize the client (which also actually has the same second parameter))! If you'd rather skip to the section on creating and adding CommandRegistries, you can find that here: LINK

Creating the Client

Now that we have our server ready, we can go ahead and create our CommandSocket client. There are clients available for the two big environments: NodeJS (@command-socket/node-client) and the browser (@command-socket/browser-client). But don't worry, both of these clients work in the exact same way, expose the same methods, and are overall interchangeable so long as they are used in their supported environment.

// Import the CommandSocket server package:
import { CommandSocket as CommandSocketClient } from "@command-socket/browser-client";

const PORT: number = 3849;
const HOST: string = "localhost";
const WS_ADDRESS: string = "ws://" + HOST + ":" + PORT;

// Create a new instance of the CommandSocketServer class, starting on port 3849:
let client: CommandSocketClient = new CommandSocketClient(WS_ADDRESS);

Note that we can also specify a local and remote Command Set like we can with a CommandSocket server.

// Initialize the CommandSocketClient with generic arguments.
let client: CommandSocketClient<MyLCS, MyRCS> = new CommandSocketClient(WS_ADDRESS);

The CommandSocketClient will connect immediately on initialization.

Creating & Utilizing Command Registries

OK, now that