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

@hatimcodes/bolt

v0.1.0

Published

Path-indexed external store for React.

Readme

⚡ Bolt

npm version bundle size types stars

A small, fast state store for React that you talk to with paths, not selectors. ~4.3 kB gzip, no boilerplate, no providers-in-providers, and it only re-renders the components that actually care.

const [name, setName] = useStore("user.profile.name"); // read and write one path
setName("Ada");                                        // no spreads

Don't disregard it because it's tiny. Bolt keeps state outside React and talks to it through useSyncExternalStore, so it plays nicely with concurrent rendering, never tears, and won't wake a component that didn't subscribe to what changed. Writes are immutable under the hood — you just write the path.

You can try the live comparison playground: bun run dev → open /stresstest.

npm install @hatimcodes/bolt react
# or: bun add @hatimcodes/bolt react

First, create a store

createBolt<State>() hands you a Provider and a few hooks, all typed to your state shape. No actions to declare, no reducers — set is generic.

import { createBolt } from "@hatimcodes/bolt";

type State = {
  user: { profile: { name: string } };
  count: number;
};

const { Provider, useStore, useSet } = createBolt<State>();

Then read a path, and that's it!

Subscribe to a path and your component re-renders only when that path changes. Nothing else does.

function Counter() {
  const [count, setCount] = useStore("count");
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}

function Name() {
  const [name, setName] = useStore("user.profile.name");
  return <input value={name} onChange={(e) => setName(e.target.value)} />;
}

function App() {
  return (
    <Provider state={{ user: { profile: { name: "Grace" } }, count: 0 }}>
      <Counter />
      <Name />
    </Provider>
  );
}

Click the button and Name stays put — it only subscribes to user.profile.name. No selectors to write, no useMemo, no equality helpers.

Why bolt over zustand?

  • No selectors. You subscribe to a path, so the scope is right by construction. No more re-rendering every tick because a selector returned a fresh object (and no reaching for shallow).
  • No manual spreads. set("user.profile.name", "Ada") instead of set((s) => ({ user: { ...s.user, profile: { ...s.user.profile, name } } })).
  • Writes don't fan out. A write wakes only the touched path and its parents — not every subscriber in the app. It stays fast as the store grows (see below).
  • Nothing to declare. No actions, no slices — set works on any path.

Why bolt over Context?

  • Re-renders only the components that read the changed path, not the whole tree.
  • No "split your context to avoid re-renders" gymnastics.
  • Read and write from anywhere under one <Provider>.

Reading and writing

Paths are dot strings or arrays — arrays are handy for dynamic keys. set takes a value or an updater function.

const [itemName, setItemName] = useStore("items.0.name");
const [sameItemName] = useStore(["items", 0, "name"]); // same subscription

setItemName("Pear");                        // bound setter from useStore(path)
set("user.profile.name", "Ada");            // value
set("count", (previous) => previous + 1);   // updater
set(["cells", cellId], (n) => n + 1);       // dynamic key
set("", nextWholeState);                    // replace the whole store

useStore();                                 // no path = whole store

Paths and set values are type-checked from your state, up to 6 levels deep.

API

| Member | What it does | | --- | --- | | Provider | ({ state, children }) — owns one store. state is read once on mount. | | useStore(path) | Subscribe to a path and return [value, setValue]. The setter is already bound to that path. | | useStore() | Subscribe to the whole store and return its value. | | useSet() | Returns the typed set(path, valueOrUpdater). | | useApi() | Imperative { get, set, getState, subscribe } — no re-render. |

Need it without React (tests, vanilla code)? createBoltStore(initialState) gives you the same get / set / subscribe.

It stays fast as the store grows

Same mounted grid of N cells, each cell leaf-subscribed to its own value (a Zustand selector vs a Bolt path), driven by the same 10,000 single-cell updates. Both re-render the same cells — so this is pure store-dispatch cost, not wasted renders. Chrome, Apple Silicon, React 19. Lower is better.

| Cells | Zustand dispatch | Bolt dispatch | Zustand → paint | Bolt → paint | Bolt faster | | ---: | ---: | ---: | ---: | ---: | ---: | | 128 | 165 ms | 79 ms | 209 ms | 100 ms | 52% | | 512 | 882 ms | 364 ms | 1056 ms | 413 ms | 61% | | 1024 | 2055 ms | 462 ms | 2386 ms | 565 ms | 76% | | 2048 | 4045 ms | 876 ms | 4612 ms | 1066 ms | 77% |

The gap widens with size: Zustand re-runs every selector on every write (O(subscribers)), while Bolt only wakes the touched path's parents (O(depth)). At 2048 cells Bolt dispatches the same updates ~4.6× faster. Run it yourself with bun run dev/stresstest (numbers vary by machine; the trend is the point).

How it works

State lives in a closure outside React. Each useStore(path) registers a useSyncExternalStore subscription keyed by that path and returns a setter already bound to the same path. When you write a.b.c.d, Bolt notifies the listeners for a.b.c.d and its prefixesa.b.c, a.b, a, and the root — and nobody else. So a parent watching a.b updates when anything beneath it changes, while a sibling on a.x never hears about it. Nested writes go through Mutative, which gives you mutation-style writes while keeping state immutable.

When not to reach for bolt

Pick Zustand or Jotai if you want a mature, widely-audited library with a middleware ecosystem (persist, devtools, immer), transient updates, or derived/computed state. Bolt is young, has no middleware, and caps typed paths at 6 levels deep. It's deliberately small.

Build

npm run build:lib   # writes ESM, CJS, and .d.ts to dist/

Release

npm login
npm run publish:from-main

Update the package version on main first. The release script resets publish-npm from main, checks npm auth, verifies the tarball, and publishes @hatimcodes/bolt publicly.