New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@computerwwwizards/observers

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@computerwwwizards/observers

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

latest
npmnpm
Version
1.3.1
Version published
Maintainers
1
Created
Source

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

FAQs

Package last updated on 01 Dec 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts