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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@snigo.dev/channel

v0.0.6

Published

Channel pub/sub proxy decorator

Readme

Channel

Easy to use pub/sub proxy decorator

Usage

In the terminal:


% npm install @snigo.dev/channel

Then in the module:


// JavaScript modules
import { withChannel } from '@snigo.dev/channel';

// Implement "Hello world" of reactive programming:
class Counter {
  constructor() {
    this.count = 0;
  }

  increment() {
    this.count += 1;
  }
}

const Counter$ = withChannel(Counter);

const myCounter = new Counter$();

// If you ever tried rxjs, this will be familiar:
const subscription = myCounter.subscribe({
  count: (value) => {
    console.log(`The count is: ${value}`);
  }
});

myCounter.increment(); // Logs: "The count is: 1"
myCounter.count = 2; // Logs: "The count is: 2"

subscription.unsubscribe('count');

myCounter.increment(); // Logs nothing

myCounter.count; // 3

Motivation

Making pub/sub a bit more accessible and a bit more straight-forward: you have an object or instance and you subscribe to any of properties, whenever property changes your callback is invoked. I want something that I can explain to a beginner who just started learing JavaScript few weeks ago, something to be the same complexity as setTimeout or Element.addEventListener.

Another particular reason - decentralizing centralized state. Imagine we store our app state in centralized manner, let's say React context. What if we'll never change it's value, but will allow any component or subtree to subscribe to parts of the state and manage their state locally? This way, in my opinion, we'll still have centralized state nicely stored while not having top-to-bottom re-renders and let components be a bit more independent.


// React

function useCalculator() {
  // Singleton
  const calculator = new Calculator$();
  const [value, __setValue] = useState(calculator.value);

  useEffect(() => {
    const subscription = calculator.subscribe({
      value: (newValue) {
        __setValue(newValue);
      }
    });
  });

  return () => {
    subscription.unsubscribe('value');
  };

  return [value, calculator];
}

// Interactive component
const calculator = new Calculator$();

return <button type="button" onClick={() => calculator.eval('2 + 2 * 20')}>=</button>;

// Display component

const [value] = useCalculator();

return <div>{value}</div>;

On the surface this might seem like still a lot of coupling inside useCalculator() function, but for larger interfaces this hook will always stay the same and will act as a "driver" for the interface and at the moment when frontend library will change it's API or you will decide to switch to another library, the only thing you will need to rewrite is this "driver" function without worrying about anything else.

API

withChannel(wrappedClass)

Class decorator, extends given wrapped class and returns new decorated class whose instances would be proxy channels.

| Parameter | Type | Default value | Notes | |---------------|-----------|-------------------|--------------------------------| | wrappedClass| Function| | Class to be decorated |


import { withChannel } from 'snigo.dev/channel';

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const Person$ = withChannel(Person);
const person = new Person$('Alice', 23);

person.subscribe({
  'age': (value, target) => {
    console.log(`Happy ${value} Birthday, ${target.name}!`);
  }
});

person.age = 24;
// => Logs "Happy 24 Birthday, Alice!"

Usage with typescript

You can use withChannel as class decorators in typescript. More info here: https://www.typescriptlang.org/docs/handbook/decorators.html


import { withChannel } from 'snigo.dev/channel';

@withChannel
class Person {
  public name: string;

  public age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

const person = new Person('Alice', 23);

person.subscribe({
  'age': (value, target) => {
    console.log(`Happy ${value} Birthday, ${target.name}!`);
  }
});

person.age = 24;
// => Logs "Happy 24 Birthday, Alice!"

channel(object)

Proxyfies given object returning proxy channel instance of the object.

| Parameter | Type | Default value | Notes | |---------------|-----------|-------------------|--------------------------------| | object | Object | | Object to be proxyfied |


import { channel } from 'snigo.dev/channel';

const obj = { name: 'Bob', age: 34 };
const bobChannel = channel(obj);

bobChannel.subscribe({
  'age': (value, target) => {
    console.log(`Happy ${value} Birthday, ${target.name}!`);
  }
});

bobChannel.age = 35;
// => Logs "Happy 35 Birthday, Bob!"

Channel

Proxy channel class.

Channel.prototype.subscribe(subscriptionDescriptor)

Subscribes to a particular property of proxy channel.

| Parameter | Type | Default value | Notes | |-------------------------|----------------------|-------------------|--------------------------------| | subscriptionDescriptor| SubscriptionTopic | | Subscription descriptor object |

subscriptionDescriptor object is an object with keys represent properties to subscribe to and values callback function that will be called each time those properties will be changed. SubscriptionCallback will be called with two arguments:

  • value: new value of the property
  • target: the proxy channel instance

*Note: the target in callback function will still have previous value of a property that has been changed:


import { channel } from 'snigo.dev/channel';

const stateChannel = channel({ count: 4 });

const subscriptionCallback = (value, target) => {
  console.log(`Value has been changed from ${target.count} to ${value}`);
};

const subscriptionDescriptor = {
  count: subscriptionCallback,
};

const sub = stateChannel.subscribe(subscriptionDescriptor);

stateChannel.count = 20;
// => Logs "Value has been changed from 4 to 20"

Returns SubscriptionReceipt allowing to unsubscribe from properties:


// If no argument provided will unsubscribe from all properties
sub.unsubscribe();

stateChannel.count = 22;
// => Logs nothing

Channel.prototype.unsubscribe(id, [key])

Allows channel proxy to unsubscribe certain subscribers by id. Rarely will be needed.

| Parameter | Type | Default value | Notes | |----------------|--------------|-------------------|--------------------------------------------| | id | number | | Subscription id | | key | string | | Optional property name to unsubscribe from |

If key isn't provided will unsubscribe subscription with given id from all properties.


Channel.prototype.push(key, value)

Pushes new value for given key. It will have the same result if value is simply reassigned with the only difference that .push() will return proxy channel instance back allowing chaining.

| Parameter | Type | Default value | Notes | |----------------|--------------|-------------------|----------------------------------------| | key | string | | Property to be updated | | value | any | | New value for given property |


import { channel } from 'snigo.dev/channel';

const taskChannel = channel({ tasks: [] });
const addTask = (channel, task) => channel.push('tasks', [...channel.tasks, task]);