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

@computerwwwizards/observers

v1.3.1

Published

A minimal TypeScript library for creating observable state containers with subscription capabilities.

Readme

Observable Store

A minimal TypeScript library for creating observable state containers with subscription capabilities.

Core Philosophy

  • Minimal API: Only the essential methods needed for state management
  • Client Responsibility: Subscribers handle their own equality checks and error handling
  • Mutation Support: Values can be directly mutated if desired
  • Framework Agnostic: Works with any UI framework or vanilla JS

Core Components

ObservableStore<T>

A basic store that holds a value of type T and notifies subscribers when that value changes.

DerivedStore<T, S>

A store that derives its value from other observable stores, automatically updating when any source store changes.

ChildObservableStore<C, P>

A store that is linked to a parent store, with its own value that can be updated independently or derived from the parent's value.

API Reference

ObservableStore

class ObservableStore<T> {
  constructor(initialValue: T)
  
  // Get the current value
  getValue(): T
  
  // Subscribe to changes
  subscribe(listener: (currentValue: T) => void, emitCurrent = false): this
  
  // Subscribe with a cleanup function
  subscribeWithCleanup(listener: (currentValue: T) => void, emitCurrent = false): () => void
  
  // Unsubscribe a listener
  unsubscribe(listener: (currentValue: T) => void): this
  
  // Update the store's value
  update(updater: T | ((prev: T) => T)): this
}

DerivedStore

class DerivedStore<T, S extends Record<string, any>> extends ObservableStore<T> {
  constructor(
    sources: SourcesRecord<S>,
    deriveFn: (sourceValues: SourcesValues<S>, prevValue?: T) => T
  )
  
  // Get all source stores
  getSources(): SourcesRecord<S>
  
  // Get the current values of all source stores
  getSourceValues(): SourcesValues<S>
  
  // Clean up subscriptions
  dispose(): this
  
  // Override to support both direct updates and updates with source values
  update(updater: T | ((prev: T, sourceValues?: SourcesValues<S>) => T)): this
}

ChildObservableStore

class ChildObservableStore<C, P = C> extends ObservableStore<C> {
  constructor(
    initialValue: C,
    parent: IObservableStore<P>,
    onParentUpdate: (parentValue: P, childPrev: C) => C
  )
  
  // Change how parent updates affect this store
  setOnParentUpdate(onParentUpdate: (parentValue: P, childPrev: C) => C): this
  
  // Update with access to parent value
  update(updater: C | ((childPrev: C, parentValue: P) => C)): this
  
  // Clean up subscription to parent
  dispose(): this
}

Usage Examples

Basic Store

const counter = new ObservableStore(0);

// Subscribe to changes
counter.subscribe(value => console.log(`Counter: ${value}`));

// Update the value
counter.update(prev => prev + 1); // Logs: "Counter: 1"
counter.update(5);                // Logs: "Counter: 5"

// You can also mutate the value directly if needed
counter.update(prev => {
  prev.someProperty = newValue; // Direct mutation
  return prev; // Return same reference
});

Derived Store

const userStore = new ObservableStore({ name: 'John', age: 30 });
const settingsStore = new ObservableStore({ theme: 'dark', fontSize: 14 });

const appState = new DerivedStore(
  { user: userStore, settings: settingsStore },
  ({ user, settings }) => ({
    userName: user.name,
    userAge: user.age,
    isDarkMode: settings.theme === 'dark',
    fontSize: settings.fontSize,
  })
);

appState.subscribe(state => console.log(state));
userStore.update(prev => ({ ...prev, name: 'Jane' }));
// Logs: { userName: 'Jane', userAge: 30, isDarkMode: true, fontSize: 14 }

With React's useSyncExternalStore

function useStore<T>(store: IObservableStore<T>) {
  return useSyncExternalStore(
    callback => store.subscribeWithCleanup(callback),
    () => store.getValue()
  );
}

function Counter() {
  const count = useStore(counterStore);
  return <div>{count}</div>;
}

Implementation Notes

Error Handling

The library doesn't catch errors from subscribers. If any subscriber throws, it will break the notification chain. This is an intentional design choice to keep the primitives minimal:

// Client code responsibility example
store.subscribe(value => {
  try {
    // Handle the update safely
    processSafely(value);
  } catch (error) {
    // Handle errors locally
    console.error('Error processing update:', error);
  }
});

Equality Checking

The library doesn't perform equality checks on values. Subscribers are responsible for determining if an update is meaningful:

let previousValue = null;
store.subscribe(value => {
  // Client-side equality check
  if (JSON.stringify(value) !== JSON.stringify(previousValue)) {
    // Only process meaningful changes
    doSomething(value);
    previousValue = JSON.parse(JSON.stringify(value));
  }
});

Future Improvements

  • Updater Configuration: Plan to allow passing configuration objects to update methods, enabling custom error handling and edge case management per update.
  • Enhanced Mutable Operations: Plan to add more utilities for direct mutation, batch mutation, and fine-grained control over how state is changed and emitted.
  • Plugin System: Add support for plugins that can intercept, filter, or transform values before they reach subscribers. Plugins may also filter which callbacks/subscribers are executed, allowing for advanced control over notification logic (e.g., only notify certain subscribers based on value or context). This enables features like logging, filtering, or custom processing without changing client code. We are also planning to evaluate rough points between RxJS and this library, and create plugins to add stream support (such as filtering, mapping, and reactive pipelines) if needed.
  • Lazy Subscribers: Support subscribers that can be created ahead of time and work with a fallback value if no emitter is available. This enables more flexible state sharing and decoupling between emitters and subscribers, useful for multi-framework or async scenarios.
  • Api: naming is somewhat weird, like update maybe is a bad decision and a better one is emit. Maybe also separate concerns and create observables and observer separated and a event bus to agregate models and emisions

License

MIT