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

observite-js

v1.0.0

Published

A lightweight global state management library with automatic memory cleanup and dynamic state containers.

Readme


Overview

Observite is a lightweight state management library built around three core primitives:

  • Observables — Units of shared state (sync or async) that notify subscribers when they change
  • Selectors — Pure functions that derive new state from Observables or other Selectors
  • Observers — Subscribe to Observables and Selectors, reacting when values update

These primitives form a data-flow graph: state flows from Observables through Selectors and into your application via Observers.

Observite offers:

  • Flexible subscriptions — Subscribe to state changes conditionally, in any order, and even within loops or dynamic contexts
  • Controlled memory management — Create state dynamically and configure exactly when it gets released
  • Framework-agnostic design — Works in React, vanilla JS, or any JavaScript environment
  • First-class async support — Built-in Promise handling with pending/resolved/rejected states

Installation

npm install observite-js

Note: This package was previously published as observite, but that name was taken. The official package is now observite-js.


Core Concepts

Observables

Observables are units of state. They're updatable and subscribable: when an observable is updated, each subscriber is notified with the new value.

Create your observables in a freely accessible place, like a global context or a module:

const fontSize = new Observable<number>({
  default: 14,
});

Subscribe to them in your application code. For example, in React it might look something like this:

const {observe} = useObserver();
return <FontSlider
  value={observe(fontSize)} // Subscribe to fontSize
  onChange={fontSize.set} // Update fontSize
/>;

Selectors

Selectors are used to calculate derived data that is based on other state.

const fontSizeLabel = new Selector<string>(({ observe }) => {
  // Every time fontSize changes, fontSizeLabel updates
  const size = observe(fontSize);
  return `${size}px`;
});

They are subscribable just like observables. You can use them anywhere you'd use an observable:

const {observe} = useObserver();
return <FontSlider
  label={observe(fontSizeLabel)}
  value={observe(fontSize)}
  onChange={fontSize.set}
/>;

Observers

Observers are the bridge between your state and your application. They subscribe to state changes and notify your code when updates occur.

Usually you will wrap observer creation based on your framework or environment. For example, in React you might use useObserver().

Example without a framework:

const {observe} = new SyncObserver();

// On changes, subscriptions are reset so that you can subscribe to different state.
// Each time, you should observe the state you want to use until the next change.
const render = () => {
  const label = observe(fontSizeLabel);
  document.querySelector('#font-size-label').textContent = label;
};

// Set up onChange and subscribe for the first time
observer.setOnChange(render);
render(); // label: "14px"

fontSize.set(16); // label: "16px"
fontSize.set(18); // label: "18px"

Dynamic Subscriptions

Observite allows fully dynamic subscriptions. On each update, you can change what you're subscribed to freely—conditionally, in loops, or in any order.

Conditional subscriptions:

const detailsSelector = new Selector<Details | null>(({ observe }) => {
  const showDetails = observe(showDetailsState);

  // Only subscribe to details when needed
  if (showDetails) {
    return observe(expensiveDetailsState);
  }
  return null;
});

Subscriptions in loops:

const totalSelector = new Selector<number>(({ observeKey }) => {
  const itemIds = ['item-1', 'item-2', 'item-3'];

  // Subscribe to a dynamic list of items
  let total = 0;
  for (const id of itemIds) {
    const item = observeKey(itemPriceMap, id);
    total += item?.price ?? 0;
  }
  return total;
});

Changing subscription sets:

const dataSelector = new Selector<Data>(({ observe }) => {
  const mode = observe(modeState);

  // Different subscriptions based on current mode
  switch (mode) {
    case 'simple':
      return observe(simpleDataState);
    case 'detailed':
      return observe(simpleDataState) + observe(extraDataState);
    case 'full':
      return observe(simpleDataState) + observe(extraDataState) + observe(metaDataState);
  }
});

This flexibility means your selectors can efficiently subscribe to exactly what they need based on the current state, automatically unsubscribing from unused dependencies when conditions change.

Async Observables

Observite has first-class support for asynchronous state. Use AsyncObservable and AsyncSelector for data that comes from promises:

const userDataState = new AsyncSelector<UserData>(async ({ observe }) => {
  const userId = observe(currentUserIdState);
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
});

Async selectors automatically track their loading state. When the promise is pending, observers can choose how to handle it:

  • AsyncObserver — Returns the promise for async/await handling
// Waits until userData is loaded before logging
const data = await observe(userData);
console.log(`Hello, ${data.name}!`);
  • ComponentObserver — Throws the promise for React Suspense integration
function UserProfile() {
  const { observe } = useObserver(); // wraps ComponentObserver

  // Throws promise to Suspense if still loading
  const userData = observe(userDataState);

  return <div>Hello, {userData.name}!</div>;
}
  • observeState — Returns the raw state object with status, result, and error
function UserProfile() {
  const { observeState } = useObserver();

  const result = observeState(userDataState);

  if (result.status === Status.Pending) {
    return <Loading />;
  }
  if (result.status === Status.Rejected) {
    return <Error error={result.error} />;
  }
  return <div>Hello, {result.result.name}!</div>;
}

Keyed Collections

For dynamic data where you need multiple instances of state keyed by an identifier, use ObservableMap and SelectorMap:

const fieldValidation = new SelectorMap<string, string | null>(
  ({ observe }, fieldName) => {
    const formData = observe(formState);
    const value = formData[fieldName];
    return validateField(fieldName, value);
  }
);

// Access data by key
const emailValidation = observeKey(fieldValidation, 'email');

Memory Management

When no observers are watching an observable or selector, Observite can automatically clean up that entry instantly, never, or after a configurable delay.

const cachedData = new Selector<ExpensiveData>(
  ({ observe }) => fetchData(observe(inputState)),
  {
    releaseDelay: 30000, // Keep cached for 30 seconds after last observer leaves
    onRelease: (data) => {
      console.log('Cleaning up cached data');
    },
  }
);

API Reference

Observables

| Class | Description | |-------|-------------| | Observable<T> | Basic synchronous state container | | AsyncObservable<T> | State container for Promises | | ObservableMap<K, V> | Keyed collection of sync observables | | AsyncObservableMap<K, V> | Keyed collection of async observables | | ObservableAnimation<T> | Animated transitions between values |

Observable Options

new Observable<T>({
  default?: T,                          // Initial value
  debugID?: string,                     // Unique identifier for debugging
  onChanged?: (value: T) => void,       // Callback on value change
  releaseDelay?: number | ReleaseDelay, // Cleanup delay in ms
  onRelease?: (value: T) => void,       // Cleanup callback
  comparisonMethod?: ComparisonMethod,  // Equality check method
});

Observable Methods

observable.set(value)       // Update the value
observable.peek()           // Get current value without subscribing
observable.isInitialized()  // Check if value has been set
observable.destroy()        // Clean up the observable

Selectors

| Class | Description | |-------|-------------| | Selector<T> | Derived synchronous state | | AsyncSelector<T> | Derived async state | | SelectorMap<K, V> | Keyed collection of sync selectors | | AsyncSelectorMap<K, V> | Keyed collection of async selectors |

Selector Methods

selector.peek()           // Get current value without subscribing
selector.peekSafe()       // Get current value if initialized, otherwise null
selector.destroy()        // Clean up the selector

Observers

| Class | Description | |-------|-------------| | SyncObserver | For synchronous-only state | | AsyncObserver | Returns promises for pending async state | | ComponentObserver | Throws promises for React Suspense |

Observer Methods

observer.setOnChange(callback)       // Set change notification callback
observer.observe(observable)         // Subscribe and get current value
observer.observeKey(map, key)        // Subscribe to a keyed entry
observer.observeState(observable)    // Subscribe and get raw state with status
observer.observeKeyState(map, key)   // Subscribe to keyed entry raw state
observer.destroy()                   // Unsubscribe from all observables
observer.reset()                     // Reset without destroying

Contributing

Development of Observite happens in the open on GitHub, and we are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving Observite.

License

Observite is MIT licensed.