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

@med1802/scoped-observer

v1.1.4

Published

A lightweight, type-safe, and **zero-dependency** event system for frontend applications. Built on native `EventTarget` API with support for **hierarchical scopes**, **last event replay**.

Readme

🔄 Scoped Observer

A lightweight, type-safe, and zero-dependency event system for frontend applications. Built on native EventTarget API with support for hierarchical scopes, last event replay.


🚀 Features

  • Hierarchical scopes — Define scopes once, navigate through them
  • Last event replay — Late subscribers automatically receive the last dispatched payload
  • Zero dependencies — Built on native JavaScript APIs
  • TypeScript native — Full type safety out of the box
  • Immutable structure — Scopes are defined once and cannot be modified at runtime
  • Simple API — Just dispatch and subscribe, nothing more

📦 Installation

npm i @med1802/scoped-observer

⚙️ Quick Start

Define your scope hierarchy once at initialization:

import { createScopedObserver } from "@med1802/scoped-observer";

const observer = createScopedObserver([
  {
    scope: "app",
    subScopes: [
      {
        scope: "dashboard",
        subScopes: [{ scope: "widgets" }, { scope: "settings" }],
      },
      {
        scope: "profile",
      },
    ],
  },
]);

📤 Dispatching Events

// Dispatch to a specific scope
observer.dispatch({
  scope: "app:dashboard:widgets",
  eventName: "widgetUpdated",
  payload: { id: 1, name: "Chart" },
});

// Dispatch to root scope (scope is optional)
observer.dispatch({
  eventName: "appInitialized",
  payload: { version: "1.0.0" },
});

📥 Subscribing to Events

// Subscribe to a scoped event
const unsubscribe = observer.subscribe({
  scope: "app:dashboard:widgets",
  eventName: "widgetUpdated",
  callback: ({ payload, eventName, scope }) => {
    console.log("Widget updated:", payload);
    console.log("Event:", eventName);
    console.log("Scope:", scope);
  },
});

// Unsubscribe when done
unsubscribe();

🎯 Last Event Replay

If you subscribe after an event has been dispatched, the callback will be invoked immediately with the last payload:

// 1. Dispatch first
observer.dispatch({
  scope: "app:profile",
  eventName: "userLoaded",
  payload: { id: 1, name: "John" },
});

// 2. Subscribe later
observer.subscribe({
  scope: "app:profile",
  eventName: "userLoaded",
  callback: ({ payload }) => {
    // This will fire immediately with { id: 1, name: 'John' }
    console.log("User:", payload);
  },
});

This is perfect for state synchronization where components mount after data has loaded.


🧭 Scope Navigation

Scopes are separated by : and form a hierarchical structure:

  • app → root level scope
  • app:dashboard → nested scope
  • app:dashboard:widgets → deeply nested scope

If scope is omitted or empty, the root scope is used.


📚 API Reference

createScopedObserver(scopes?)

Creates a new scoped observer instance.

Parameters:

  • scopes (optional): Array of ScopeNode objects defining the hierarchy

Returns: Observer instance with dispatch and subscribe methods


observer.dispatch(options)

Dispatches an event to a specific scope.

Options:

  • scope? (string): Target scope path (e.g., "app:dashboard")
  • eventName (string): Name of the event
  • payload? (any): Data to send with the event

observer.subscribe(options)

Subscribes to events on a specific scope.

Options:

  • scope? (string): Target scope path
  • eventName (string): Name of the event to listen for
  • callback (function): Handler receiving { payload, eventName, scope }

Returns: Unsubscribe function


🏗️ Architecture

  • EventScope — Each scope is an EventTarget instance
  • Recursive hierarchy — Scopes are built as a tree structure at initialization
  • lastEventPayloads — Each scope maintains a map of the last payload per event
  • Immutable — Once created, the scope structure cannot be modified

📄 License

MIT