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

actzs

v0.0.3

Published

class-based reactive UI library with fine-grained dependency tracking and no VDOM

Readme

actzs

A tiny, class-based reactive UI library with explicit mutation tracking, full call-stack attribution, and a fine-grained DOM layer — no virtual DOM, no diffing.

npm install actzs
import { Ref, Computed, h, mount, setRef, runNamed } from 'actzs'

const count = new Ref(0)
const doubled = new Computed(() => count.value * 2)

function App() {
  function increment() {
    setRef(count, count.value + 1)
  }
  return h('div', {}, [
    h('p', {}, ['Count: ', count]),
    h('p', {}, ['Doubled: ', doubled]),
    h('button', { onClick: () => runNamed(increment) }, '+1'),
  ])
}

mount(h(App), document.getElementById('app'))

Why actzs?

Most reactive UI frameworks use a virtual DOM and diffing. actzs takes a different approach:

  • Fine-grained reactivity — every reactive read is tracked; when a value changes, only the exact DOM nodes / effects that depend on it re-run.
  • Full traceability — every mutation records its caller stack. Open the console and ask "who changed this, and why?"
  • No VDOM, no diffingidom binds effects directly to real DOM nodes. Zero wasted comparisons.
  • Zero dependencies — no framework, no build step required. Just ESM imports.

Core API

| Concept | Type | Import | |---|---|---| | Primitive ref | Ref | import { Ref } from 'actzs' | | Reactive object/array/Map | Reactive | import { Reactive } from 'actzs' | | Derived value | Computed | import { Computed } from 'actzs' | | Side effect | Effect.run / watch | import { Effect, watch } from 'actzs' | | Set ref value | setRef(ref, value) | import { setRef } from 'actzs' | | Set reactive property | setReactive(proxy, key, value) | import { setReactive } from 'actzs' | | Call reactive method | setReactiveMethod(proxy, method, ...args) | import { setReactiveMethod } from 'actzs' | | Named call context | runNamed(fn, ...args) | import { runNamed } from 'actzs' | | Named callback wrapper | named(fn) | import { named } from 'actzs' | | Async context capture | captureContext() | import { captureContext } from 'actzs' | | VNode factory | h(tag, props?, children?) | import { h } from 'actzs' | | Mount to DOM | mount(vnode, container) | import { mount } from 'actzs' | | Reactive mount | reactiveMount(fn, container) | import { reactiveMount } from 'actzs' | | Lifecycle hooks | onMount(fn) / onDestroy(fn) | import { onMount, onDestroy } from 'actzs' | | Reactive list | runList(parent, items) | import { runList } from 'actzs' | | Graph inspector | Graph | import { Graph } from 'actzs' |

How it works

Reactive data

const score = new Ref(0)                    // primitive
const player = new Reactive({ x: 0, y: 0 }) // object / array / Map
const doubled = new Computed(() => score.value * 2) // derived
  • Read with .value (Ref/Computed) or direct property access (Reactive).
  • Direct assignment throws — always use setters.

Mutations

setRef(score, 10)
setReactive(player, 'x', 5)
setReactiveMethod(items, 'push', { id: 3 })

Every mutation records who triggered it via the call context stack.

Named call context

Wrap mutating functions so the graph knows the caller:

function addScore(points) {
  setRef(score, score.value + points)
}

runNamed(addScore, 10)                  // graph: by ['addScore']
button.onclick = () => runNamed(addScore, 10)

// Async: capture the context before scheduling
function startTimer() {
  const ctx = captureContext()
  setInterval(ctx(() => setRef(t, t.value - 1)), 1000)
}
runNamed(startTimer)                    // graph: by ['startTimer']

DOM with idom

Pass Refs directly to h() — they stay reactive:

h('p', {}, ['Score: ', score])       // reactive text
h('div', { style: { opacity: opacityRef } })
h('img', { src: imageUrlRef })

No VDOM diffing. Each Ref-bound prop/text spins up its own Effect that updates the real DOM node directly.

Debugging

const g = new Graph()
window.g = g
window.score = score

g.dump()                    // all effects + recent mutations
g.lookup(score, 'value')    // what depends on score.value

Coding with actzs via LLM

actzs is designed to be LLM-friendly. The full project rules are in:

  • GUIDE.md — complete developer guide including an LLM prompt template (section 10) that you can copy-paste when asking an LLM to write actzs code.
  • AGENTS.md — machine-readable rules that tools like opencode use to enforce actzs conventions.

Key rules the LLM prompt enforces:

  • Every mutable variable must be Ref or Reactive — no let.
  • Every mutation uses a setter — no direct assignment.
  • Every mutating function is wrapped in runNamed / named / captureContext.
  • Every text/attribute in h() is a Ref or Computed — no string literals.
  • Everything is exposed on window for graph debugging.

Games built with actzs

| Game | Stack | |---|---| | Flappy Bird | Canvas 2D, physics, collision | | Helix Fall | Three.js tower descent | | Helix Fall 2 | Three.js neon tower + combos | | Cut the Rope | Verlet physics, swipe cutting | | Red Ball 4 | Platformer, physics enemies | | Angry Birds | Slingshot projectile, destructible blocks | | Category Sort | 3D sorting with Three.js | | Jigsaw Solitaire | Tile matching puzzle | | Jump n Roll | Endless runner |

All games expose window.g — open DevTools and type g.dump() to see the full reactivity graph.

License

MIT