Skip to content

jslno/tswsrpc

Repository files navigation

Forks Stargazers Issues MIT License Github Release NPM Version


tswsrpc

TypeSafe WebSocket events.


Report Bug · Request Feature


Table of Contents
  1. Features
  2. Installation
  3. Basic Usage
  4. Additional Usage
  5. Roadmap
  6. Dependencies
  7. License

Features

  • Easy Event Subscription & Emission
  • Bi-directional Communication
  • Schema Validation
    • works with zod, yup and many other validation libraries.
  • Middleware Support
  • Clean & modular structure

Installation

If not done already, install a schema validator. This example uses Zod but you can use any validator using the StandardSchema. You can find the list of featured compatible libraries here.

npm install zod

Add tswsrpc to your project:

npm install tswsrpc@latest

Basic Usage

1. Register Events

Before setting up the server and client, define the events they will handle. The event registry ensures structured communication by defining valid message types.

server.ts

import { tswsrpc } from "tswsrpc";
import { z } from "zod";

export const serverEvents = tswsrpc.$eventRegistry({
  message: tswsrpc.$event({
    type: z.string(),
  }),
});

client.ts

import { client } from "tswsrpc/client";

export const clientEvents = client.$eventRegistry({
  "nested.ping": client.$event({}),
});

2. Create Server Instance

The WebSocket server listens for incoming connections and handles registered events.

server.ts

import type { clientEvents } from "./client"; // Import as type

const server = await tswsrpc<typeof clientEvents>()({
  server: {
    port: 8000,
  },
  events: serverEvents,
});

server.on.message((ctx) => {
  console.log(ctx.data);
});

server.emit.nested.ping();

3. Create Client Instance

The WebSocket client connects to the server and listens for events.

client.ts

import type { serverEvents } from "./server"; // Import as type

const socket = client<typeof serverEvents>()({
  address: "ws://localhost:8000",
  events: clientEvents,
});

socket.on.nested.ping(() => {
  console.log("PING");
  socket.emit.message("PONG");
});

This setup establishes a bidirectional messaging system with minimal configuration.

  1. The server emits a nested.ping event.

  2. The client receives the event, logs "PING", and responds with message.

  3. The server receives the message event and logs "PONG".

Additional Usage

Middlewares

Middlewares allow you to preprocess, validate, and modify incoming event data before execution. You can use a single middleware or an array of middlewares for tasks like authentication and data transformation.

In the example below, the middleware validates an access token before processing the event:

export const events = tswsrpc.$eventRegistry({
  // or client.$eventRegistry
  message: tswsrpc.$event({
    // or client.$event
    type: z.object({
      accessToken: z.string(),
    }),
    use: async (ctx, _error) => {
      // Or an array of middlewares
      const isValid = await validateAccessToken(ctx.data?.accessToken);
      if (!isValid) {
        return ctx.error("UNAUTHORIZED");
        // or throw ctx.error("UNAUTHORIZED");
      }

      return; // or return modified data.
    },
  }),
});

Error Handling

server.on.message((ctx, error) => {
  if (error) {
    // Handle error
  }

  const isValid = await validateAccessToken(ctx.data?.accessToken);
  if (!isValid) {
    return ctx.error("UNAUTHORIZED");
    // or throw ctx.error("UNAUTHORIZED");
  }
});

Roadmap

Dependencies

License

Distributed under the MIT License. See LICENSE.md for more information.