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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@streamerson/emitter

v0.0.16

Published

![Version](https://img.shields.io/badge/version-1.0.0-blue.svg) ![License](https://img.shields.io/badge/license-MIT-green.svg) ![TypeScript](https://img.shields.io/badge/TypeScript-4.5%2B-blue)

Downloads

33

Readme

🔭 ObservableObject

Version License TypeScript

A TypeScript library that provides an observable state object with event-based subscriptions to changes at any nested path within the object, using eventemitter3.

🌟 Features

  • 🔍 Deep Subscription: Subscribe to changes on any lodash-like path within your state object.
  • 🔄 Partial and Full Updates: Update the state object entirely or partially with ease.
  • 🪶 Lightweight: Built with minimal dependencies for efficient performance.
  • 📘 TypeScript Support: Fully typed for better developer experience.
  • 🚀 High Performance: Leverages eventemitter3 for fast event handling.

📦 Installation

Install the package via npm:

npm install @streamerson/emitter

Or using yarn:

yarn add @streamerson/emitter

🚀 Quick Start

import { ObservableObject } from '@streamerson/emitter';

// Create an observable object
const state = new ObservableObject({
  user: { name: 'Alice', age: 30 },
  isLoggedIn: false
});

// Subscribe to changes
state.subscribe('user.name', (newValue, oldValue) => {
  console.log(`Name changed: ${oldValue} -> ${newValue}`);
});

// Update the state via JSON Text
state.update('{ "user": { "name": "Bob" } }');

// Update the state via JSON:
state.update({ user: { name: 'Bob' } });

📘 Usage Guide

Importing the Module

import { ObservableObject } from '@streamerson/emitter';

Creating an Observable Object

Initialize your state with any nested structure of primitives, arrays, or dictionaries:

const state = new ObservableObject({
  user: {
    name: 'Alice',
    age: 30,
    hobbies: ['reading', 'hiking']
  },
  settings: {
    theme: 'dark',
    notifications: true
  },
  isLoggedIn: false
});

Text versus JSON API

The update methods accept text and JSON. Text is probably the more blessed path because:

  • It guarantees no circularity errors, which will be thrown on objects with circular references
  • To guarantee the above, we text-serialize and reparse all incoming objects.
    • This has negative performance implications for large objects, so updating from text is safer
    • This lets you avoid serializing messages if they are safe to merge into client states

Subscribing to Changes

Subscribe to changes on any path using lodash-like syntax:

// Subscribe to changes on 'user.name'
state.subscribe('user.name', (newValue, oldValue) => {
  console.log(`User name changed from ${oldValue} to ${newValue}`);
});

// Subscribe to changes in the entire user object
state.subscribe('user', (newUser, oldUser) => {
  console.log('User object changed:', newUser);
});

// Subscribe to changes in an array
state.subscribe('user.hobbies', (newHobbies, oldHobbies) => {
  console.log('Hobbies updated:', newHobbies);
});

// Subscribe to any change in the state
state.subscribe('*', (newState) => {
  console.log('State changed:', newState);
});


// Subscribe to some change in the state, with exclusions
state.subscribe('*', (newState) => {
  console.log('State changed:', newState);
}, { exclude: ['some.path'] });

Updating the State

You can update the state partially or entirely:

// Partial update
state.update({
  user: {
    name: 'Bob',
    hobbies: [...state.get('user.hobbies'), 'swimming']
  }
});

// Full update
state.set({
  user: {
    name: 'Charlie',
    age: 25,
    hobbies: ['coding']
  },
  settings: {
    theme: 'light',
    notifications: false
  },
  isLoggedIn: true
});

Deleting the state:

To delete records from the state, you should use null, not undefined. Namely, this is because not all platforms handle the serialization of undefined the same and many will strip it from the JSON of a sent record.

If you send undefined, for compatibility, we will entirely ignore it. null however will remove the object from the tracked state.

state.update({ abc: 123 })
state.update({ abc: undefined })
state.get('abc') === 123 // true
state.update({ abc: null })
state.get('abc') === undefined // true

Retrieving State Values

Use the get method to retrieve values from the state:

const userName = state.get('user.name');
console.log('Current user name:', userName);

const userHobbies = state.get('user.hobbies');
console.log('User hobbies:', userHobbies);

🛠 API Reference

ObservableObject

Constructor

new ObservableObject<T>(initialState
:
T
)
  • initialState: The initial state object.

Methods

| Method | Description | |------------------------------------------------------------------------------------|--------------------------------------------| | subscribe(path: string, listener: (newValue: any, oldValue?: any) => void): void | Subscribes to changes at a specific path. | | update(partialState: Partial<T>): void | Partially updates the state object. | | set(newState: T): void | Replaces the entire state object. | | get(path: string): any | Retrieves the value at the specified path. |

EventEmitter Methods

ObservableObject extends EventEmitter from eventemitter3, providing these additional methods:

| Method | Description | |-----------------------------------------------------------|-------------------------------------------------| | on(event: string \| symbol, listener: Function): this | Adds a listener for the event. | | off(event: string \| symbol, listener: Function): this | Removes a listener for the event. | | once(event: string \| symbol, listener: Function): this | Adds a one-time listener for the event. | | emit(event: string \| symbol, ...args: any[]): boolean | Triggers all listeners for the specified event. |

🧪 Testing

The library is tested using Node.js native testing modules (node:test). To run the tests:

npm run test

Or, if you prefer to run specific test files:

tsx --test ./tests/emitter.happy.test.ts
tsx --test ./tests/emitter.sad.test.ts

TODO:

  • I'd like to get AJV (or the like) in as a schematized option for verified state data
  • Needs some more tests
  • Other Stuff

🤝 Contributing

Contributions are welcome and appreciated! Here's how you can contribute:

  1. Fork & PR

Please ensure your code adheres to the existing style and all tests pass before submitting a PR.

🙏 Acknowledgements