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

@statekit/connector

v0.5.4-dev

Published

Downloads

1,218

Readme

@statekit/connector

Field tracking and shared runtime for view-library adapters.

@statekit/react already uses this package. Use it directly when building an adapter for another framework, renderer, or platform. It is not another state manager, and most application code won't import it.

Work in progress.

What it does

The connector records which machine fields a view reads. After an event, the adapter compares only those fields and can skip an unrelated render.

view reads data.user.name
            |
            v
connector records user.name
            |
            v
machine publishes new data
            |
            v
did user.name change? -- no --> skip render
            |
           yes
            |
            v
         render

Install

npm i @statekit/core @statekit/connector

Track data reads

import { createMachine } from "@statekit/core"
import { createMachineProxy, SubscriptionManager } from "@statekit/connector"

const machine = createMachine({
   data: {
      user: { name: "Ada" },
      theme: "light",
   },
   events: {
      rename: ({ draft }, name: string) => {
         draft.user.name = name
      },
      setTheme: ({ draft }, theme: string) => {
         draft.theme = theme
      },
   },
})

const machineRef = { current: machine }
const subscriptions = new SubscriptionManager()
let readsState = false

const proxyRef = createMachineProxy(
   subscriptions,
   machineRef,
   () => void (readsState = true)
)

proxyRef.current.data.user.name

let before = machine.data

machine.event.setTheme("dark")
subscriptions.checkIsSubscribedChanged(before, machine.data) // false

before = machine.data

machine.event.rename("Grace")
subscriptions.checkIsSubscribedChanged(before, machine.data) // true

Only user.name was read through the proxy. Changing theme does not affect that consumer; changing user.name does.

Track state reads

Reading the proxied state calls the third argument passed to createMachineProxy:

proxyRef.current.state
readsState // true

The adapter can then compare the previous and current machine state alongside data changes.

Objects, arrays, and selectors

Nested objects and arrays keep their normal behavior. Reads through Object.keys, Object.values, object spread, iteration, and array methods are tracked too.

Selectors called through proxyRef.current.select run against the proxied machine, so they record the fields they read. A consumer that only calls select.isPositive() changes when count changes, not when an unrelated label does.

Date, Map, Set, RegExp, Promise, and similar built-ins are returned without a proxy, because their methods need the real object. Reading one tracks its path, not its contents.

How changes are compared

Each tracked path is compared with Object.is. A tracked value replaced by an equal copy, such as a new Date with the same time, counts as a change. A value mutated in place behind the same reference does not. Keep data that views read plain.

The data root is always an object or array; core guarantees it. Values inside it can become null, a primitive, or a different shape between snapshots, and the comparison handles that.

Shared runtime

Adapters share one StateKit runtime per JavaScript realm. Read it, or create it if it does not exist yet:

import { getStatekit, initStatekit } from "@statekit/connector"

const statekit = getStatekit() ?? initStatekit()

It is stored on globalThis, so the same calls work in browsers, Node, and workers. Don't hide a private runtime inside a framework context; adapters that do can't work together. Field tracking itself does not require this runtime.

getIsBrowser, getIsNode, and getIsWebWorker are exported for code that depends on the environment.

Building an adapter

The core machine is the source of truth. Core owns the data, events, selectors, states, transitions, and async behavior. The adapter watches the machine and asks its framework to update at the right time. It does not copy data into a second store or recreate core behavior.

One tracker per consumer

Give every independently rendered consumer its own SubscriptionManager and proxy. If one consumer reads user.name and another reads cart.total, a shared tracker wakes both for both changes.

Wake the framework

When the machine notifies, compare what this consumer last saw with the machine now:

const dataChanged = subscriptions.checkIsSubscribedChanged(
   previousData,
   machineRef.current.data
)

const stateChanged =
   readsState && !Object.is(previousState, machineRef.current.state)

previousData = machineRef.current.data
previousState = machineRef.current.state

if (dataChanged || stateChanged) notifyFrameworkConsumer()

Save the snapshots after every notification, not only when you render. Otherwise the previous value falls behind and later comparisons are wrong.

notifyFrameworkConsumer is the framework-specific part: update a signal, invalidate a view, or schedule a render with the mechanism the framework recommends.

Fit the framework's lifecycle

  • Keep the proxy and subscription manager stable across renders.
  • Remove every listener on unmount or disposal.
  • Survive development remounts and strict modes without duplicate listeners.
  • Run the machine's initializer once, in the browser, at the first client-side use.

Hand back the core API

Users should get the same machine back, with its types intact: data, event, select, state, STATE, setState, setData, and subscribeEvents. Add the ergonomics your framework needs around it, such as hooks, signals, or plugins, without renaming or reinterpreting it.

Share machines between adapters

A page can mix frameworks. When two adapters use the same machine, an event called from either side, or from plain JavaScript, must update both.

React consumer ──calls increment──┐
                                  v
                          sharedCounter
                                  |
                    update fans out to adapters
                          ┌───────┴───────┐
                          v               v
                   React consumer   other consumer
  • Keep one machine, not one copy per adapter.
  • Call core events directly instead of wrapping them in framework events.
  • Don't bundle a private copy of core.
  • Mounting or unmounting one adapter must not disconnect another.

Before you ship an adapter

Test at least:

  1. one consumer and one primitive field;
  2. nested objects and arrays;
  3. object keys being added and removed;
  4. selectors;
  5. state-only updates, and consumers that never read state skipping them;
  6. sync events and every async phase: start, resolve, and reject;
  7. two consumers with separate dependencies;
  8. mount, unmount, and remount, including strict mode;
  9. server rendering and hydration;
  10. an event called outside the framework;
  11. two adapters sharing one machine, with updates in both directions.

For the last test, a small reference adapter with its own lifecycle is enough.

@statekit/react is the reference implementation. Borrow its plumbing, not its React-shaped API.

Do

  • Keep one SubscriptionManager for each consumer.
  • Read from proxyRef.current while collecting fields.
  • Keep machineRef.current pointed at the latest machine.
  • Compare the previous data snapshot with the latest one after every notification.
  • Share the global runtime from getStatekit() ?? initStatekit().

Don't

  • Don't use this package directly in a normal React app; use @statekit/react.
  • Don't share one SubscriptionManager between unrelated consumers.
  • Don't copy machine data into a framework store.
  • Don't turn field paths into strings; symbols and array indexes must keep their identity.
  • Don't save an old proxyRef.current value and expect it to follow a replaced machine ref.

Development

From the node directory:

pnpm --filter @statekit/connector test -- --runInBand
pnpm --filter @statekit/connector build