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

trisgeist

v0.1.2

Published

small simple global state management library. class-based stores, selector subscriptions, derived state, redux devtools. inspired by flutter's `ChangeNotifier` and riverpod.

Readme

trisgeist

small simple global state management library. class-based stores, selector subscriptions, derived state, redux devtools. inspired by flutter's ChangeNotifier and riverpod.

install

bun add trisgeist
# or
npm install trisgeist

basic usage

extend Notifier, override build() to return your initial state, expose actions as methods.

import { Notifier } from "trisgeist";

type CounterState = { count: number };

class CounterNotifier extends Notifier<CounterState> {
  override build() {
    return { count: 0 };
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }
}

export const counterNotifier = new CounterNotifier();

then in your component:

import { useWatch } from "trisgeist/react";
import { counterNotifier } from "./counterNotifier";

export function Counter() {
  const { count } = useWatch(counterNotifier, (o) => o.state);

  return (
    <button onClick={() => counterNotifier.increment()}>
      clicked {count} times
    </button>
  );
}

the selector (o) => o.state.count means the component only re-renders when count changes, not on any unrelated state update.

note: useWatch lives at trisgeist/react rather than the root export. this keeps the door open for other framework bindings (e.g. trisgeist/vue) without name collisions.

the rules

  • always override build() to return your initial state. don't also assign state = {...} as a class field — class fields run after the constructor body, so it'll silently overwrite whatever build() returned.
  • state is settable, but every direct assignment must be followed by notifyListeners() (or use setState/scheduleNotify, which call it for you). if you set state directly and don't notify, subscribers won't update.

derived stores

use watch() inside build() to make a store that recomputes when other stores change — same idea as riverpod's ref.watch.

class FilteredEventsNotifier extends Notifier<Event[]> {
  override build() {
    const events = this.watch(eventsNotifier, (o) => o.state);
    const filters = this.watch(filtersNotifier, (o) => o.state);
    return events.filter((e) =>
      filters.category === "all" || e.category === filters.category
    );
  }
}

export const filteredEventsNotifier = new FilteredEventsNotifier();

when filtersNotifier or eventsNotifier updates, build() reruns automatically. no wiring needed.

note: dependencies declared with watch() must be static — call watch() on the same stores, in the same order, every time build() runs. only the first call (during construction) actually registers subscriptions, so conditional watch() calls won't be tracked correctly afterward.

debounced updates

if you're getting a burst of updates (websocket events, stream data) and don't want a re-render for each one, assign this.state directly (no merge) and call scheduleNotify() instead of setState:

class MessagesNotifier extends Notifier<Message[]> {
  override debounceDuration = 50; // default is 100ms

  override build() {
    return [] as Message[];
  }

  onMessage(msg: Message) {
    this.state = [...this.state, msg];
    this.scheduleNotify(); // batches into one re-render once the burst settles
  }
}

scheduleNotify() resets a timer on each call and only fires notifyListeners() once things settle. unlike setState, it does not merge — set this.state to the full new value yourself first.

reading from other stores

stores are just singletons, import and read from them anywhere:

class CartNotifier extends Notifier<CartState> {
  checkout() {
    const { currency } = settingsNotifier.state;
    this.setState({ total: calculateTotal(this.state.items, currency) });
  }
}

which update method to use

| method | when | |---|---| | setState(partial) | shallow merge + notify immediately — use this for almost everything | | this.state = ... + notifyListeners() | full state replacement, then notify | | this.state = ... + scheduleNotify() | full replacement, debounced notify, for burst updates |

devtools

connects to redux devtools automatically if you have the extension installed. each store shows up as its own instance in the dropdown. time travel works on source stores — derived ones recompute naturally.

no config needed, just install the extension.

react native

works fine, useSyncExternalStore is react core not web-specific.

api

Notifier<TState>trisgeist

| member | description | |---|---| | state | current state, settable — direct assignment requires a notifyListeners()/scheduleNotify() call after | | build() | must override. return initial state here, call watch() to declare deps | | setState(partial) | shallow merge + notify | | notifyListeners() | manually flush listeners | | scheduleNotify() | debounced flush | | debounceDuration | debounce ms, default 100, override per store | | watch(store, selector) | declare a static dep inside build(), reruns build when it changes | | subscribe(listener, selector) | raw subscription, returns unsub fn |

useWatch(store, selector?)trisgeist/react

import { useWatch } from "trisgeist/react";

const state = useWatch(myNotifier); // whole state, re-renders on any change
const count = useWatch(myNotifier, (o) => o.state.count); // only re-renders when count changes