npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

shared-message

v1.0.2

Published

Send JSON messages between server and client with type safety while maintaining DRY code.

Downloads

3

Readme

Shared Message

shared-message is built in TypeScript and provides means of building shared components used to build and validate JSON messages sent bi-directionally between server and client.

While designed to work with WebSockets, this library is transport agnostic.

Two main problems solved:

  1. Minimises code duplication by creating a single source of truth for each message. This is achieved by creating "message builders". Message builders are used to build and validate messages.
  2. Creates trust in code by enabling the attachment of handlers to message builders in a typed way. Handlers have typed knowledge of data they are expected to receive.

Install

npm install --save shared-message

Type definitions are bundled and do not require a separate install.

Overview of a message

A message is a JSON object consisting of a _name and any other data you define.

_name is a string that uniquely identifies the type of message. This is used by routers to correctly route a message to the correct handler.

{
    "_name": "NAME",
    "any": "abc",
    "other": 123,
    "data": true
}

Usage

Message Builders

To create your first message, begin by creating an interface to represent the data it will carry.

interface ExampleInterface {
    name: string;
    age: number;
}

A message builder can now be created by extending AbstractMessageBuilder with your interface as a supplied generic type parameter.

Implement getMessageName by returning a globally unique string that identifies your message. Implement isMessage to validate the message interface.

import { AbstractMessageBuilder } from 'shared-message';

class ExampleMessageBuilder extends AbstractMessageBuilder<ExampleInterface> {
    public static shared = new ExampleMessageBuilder();

    public getMessageName(): string {
        return 'EXAMPLE';
    }

    public isMessage(messageData: unknown): messageData is ExampleInterface {
        if (!(messageData instanceof Object)) {
            return false;
        }

        if (typeof (messageData as ExampleInterface).name !== 'string') {
            return false;
        }

        if (typeof (messageData as ExampleInterface).age !== 'number') {
            return false;
        }

        return true;
    }
}

Note: Although not necessary, because message builders do not require multiple instantiations, ExampleMessageBuilder has been given a static property called shared to act as a singleton.

Messages can now be built using your message builder's build method. These messages can be serialised and sent to a server or client.

ExampleMessageBuilder.shared.build({
    name: 'Bob',
    age: 40,
});

// {
//     _name: "EXAMPLE",
//     name: "Bob",
//     age: 40,
// }

Handlers

A handler handles application logic for a particular received message. A handler must conform to the MessageHandler interface. This ensures that it knows what data to expect.

As well as receiving the message data, your handlers may also need other data or components. This is solved by creating a context interface, here you can define everything your handlers need such as a user ID for requests to be authenticated.

interface Context {
    user_id: string;
}

Your handler can now be created. MessageHandler requires your message and context interfaces. If you do not require a context, undefined can be used instead.

import { MessageHandler } from 'shared-message';

const handler: MessageHandler<ExampleInterface, Context> = (message, context) => {
    // Business logic.
};

Router

Now that your message builders and handlers have been created, we can associate them with a router.

import { MessageHandler, MessageRouter } from 'shared-message';

const router = new MessageRouter<Context>();
const exampleMessageBuilder = new ExampleMessageBuilder();

const handler: MessageHandler<ExampleInterface, Context> = (message, context) => {
    // Business logic.
};

// Attach a handler.
router.on(exampleMessageBuilder, handler);

// Attach a handler directly using an anonymous function.
router.on(exampleMessageBuilder, (message, context) => {
    // Business logic.
});

The router can now handle incoming messages using the handleMessage method. This needs to be used by your code that handles incoming data.

let message;

try {
    message = JSON.parse(incommingDataString);
} catch (error) {
    // handle
}

router.handleMesage(message, {
    user_id: 'abc',
});