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

chizu

v0.2.46

Published

<div align="center"> <img src="/media/logo-v2.png" width="475" />

Readme

Checks

Strongly typed React framework using generators and efficiently updated views alongside the publish-subscribe pattern.

View Live Demo →

Contents

  1. Benefits
  2. Getting started

For advanced topics, see the recipes directory.

Benefits

  • Event-driven architecture superset of React.
  • Views only re-render when the model changes.
  • Built-in optimistic updates via Immertation.
  • No stale closures – context.data stays current after await.
  • No need to lift state – siblings communicate via events.
  • Reduces context proliferation – events replace many contexts.
  • No need to memoize callbacks – handlers are stable via useEffectEvent.
  • Clear separation between business logic and markup.
  • Complements Feature Slice Design architecture.
  • Strongly typed dispatches, models, payloads, etc.
  • Built-in request cancellation with AbortController.
  • Granular async state tracking per model field.
  • Declarative lifecycle hooks without useEffect.
  • Centralised error handling via the Error component.
  • React Native compatible – uses eventemitter3 for cross-platform pub/sub.

Getting started

We dispatch the Actions.Name event upon clicking the "Sign in" button and within useNameActions we subscribe to that same event so that when it's triggered it updates the model with the payload – in the React component we render model.name. The With helper binds the action's payload directly to a model property.

import { useActions, Action, With } from "chizu";

type Model = {
  name: string | null;
};

const model: Model = {
  name: null,
};

export class Actions {
  static Name = Action<string>("Name");
}

export default function useNameActions() {
  const actions = useActions<Model, typeof Actions>(model);

  actions.useAction(Actions.Name, With("name"));

  return actions;
}
export default function Profile(): React.ReactElement {
  const [model, actions] = useNameActions();

  return (
    <>
      <p>Hey {model.name}</p>

      <button onClick={() => actions.dispatch(Actions.Name, randomName())}>
        Sign in
      </button>
    </>
  );
}

When you need to do more than just assign the payload – such as making an API request – expand useAction to a full function. It can be synchronous, asynchronous, or even a generator:

actions.useAction(Actions.Name, async (context) => {
  context.actions.produce((draft) => {
    draft.model.name = context.actions.annotate(Op.Update, null);
  });

  const name = await fetch(api.user());

  context.actions.produce((draft) => {
    draft.model.name = name;
  });
});

Notice we're using annotate which you can read more about in the Immertation documentation. Nevertheless once the request is finished we update the model again with the name fetched from the response and update our React component again.

If you need to access external reactive values (like props or useState from parent components) that always reflect the latest value even after await operations, pass a data callback to useActions:

const actions = useActions<Model, typeof Actions, { query: string }>(
  model,
  () => ({ query: props.query }),
);

actions.useAction(Actions.Search, async (context) => {
  await fetch("/search");
  // context.data.query is always the latest value
  console.log(context.data.query);
});

For more details, see the referential equality recipe.

Each action should be responsible for managing its own data – in this case our Profile action handles fetching the user but other components may want to consume it – for that we should use a distributed action:

class DistributedActions {
  static Name = Action<string>("Name", Distribution.Broadcast);
}

class Actions extends DistributedActions {
  static Profile = Action<string>("Profile");
}
actions.useAction(Actions.Profile, async (context) => {
  context.actions.produce((draft) => {
    draft.model.name = context.actions.annotate(Op.Update, null);
  });

  const name = await fetch(api.user());

  context.actions.produce((draft) => {
    draft.model.name = name;
  });

  context.actions.dispatch(Actions.Name, name);
});

Once we have the distributed action if we simply want to read the name when it's updated we can use consume:

export default function Subscriptions(): React.ReactElement {
  return (
    <>
      Manage your subscriptions for your{" "}
      {actions.consume(Actions.Name, (name) => name.value)} account.
    </>
  );
}

However if we want to listen for it and perform another operation in our local component we can do that via useAction:

actions.useAction(Actions.Name, async (context, name) => {
  const friends = await fetch(api.friends(name));

  context.actions.produce((draft) => {
    draft.model.friends = friends;
  });
});

For targeted event delivery, use channeled actions. Define a channel type as the second generic argument and call the action with a channel object – handlers fire when the dispatch channel matches:

class Actions {
  // Second generic arg defines the channel type
  static UserUpdated = Action<User, { UserId: number }>("UserUpdated");
}

// Subscribe to updates for a specific user
actions.useAction(
  Actions.UserUpdated({ UserId: props.userId }),
  (context, user) => {
    // Only fires when dispatched with matching UserId
  },
);

// Subscribe to all admin user updates
actions.useAction(
  Actions.UserUpdated({ Role: Role.Admin }),
  (context, user) => {
    // Fires for {Role: Role.Admin}, {Role: Role.Admin, UserId: 5}, etc.
  },
);

// Dispatch to specific user
actions.dispatch(Actions.UserUpdated({ UserId: user.id }), user);

// Dispatch to all admin handlers
actions.dispatch(Actions.UserUpdated({ Role: Role.Admin }), user);

// Dispatch to plain action - ALL handlers fire (plain + all channeled)
actions.dispatch(Actions.UserUpdated, user);

Channel values support non-nullable primitives: string, number, boolean, or symbol. By convention, use uppercase keys like {UserId: 4} to distinguish channel keys from payload properties.