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

avosignals

v1.0.16

Published

A lightweight signaling library for web components and modern web applications with Lit integration.

Readme

avosignals

A lightweight, type-safe reactive state management library for TypeScript. It features automatic dependency tracking, efficient updates, and first-class support for Lit components.

Features

  • ⚡️ Fine-Grained Reactivity: Updates only what needs to change.

  • 🛡️ Circular Dependency Protection: Detects and prevents infinite loops during computation.

  • 🧹Automatic Garbage Collection: Uses WeakRef for internal computed subscriptions to prevent memory leaks in derived state graphs.

  • 🔒 Safety Guardrails: Prevents state mutations during reactive evaluations to ensure data consistency.

  • 🔥 Lit Integration: Includes a specific controller (SignalWatcher) to make Lit components reactive automatically.

Instalation

Node / NPM

npm install avosignals

Browser / Esm.sh

<script type="module">
    import { Signal, Computed, effect } from "https://esm.sh/avosignals"
</script>

Core Concepts

1. Signals

Signals are the atoms of state. They hold a value and notify subscribers when that value changes.

import { Signal } from 'avosignals';

const count = new Signal(0, 'count');

console.log(count.get()); // 0

count.set(1);
count.update(c => c + 1); // 2

The value property

Similar having the update() for convenience avosignals also has a concise value property for reading or writing the current signal value. Accessing value inside a reactive context tracks the dependency just like get():

const count = new Signal(0);

console.log(count.value); // 0
console.log(count.get()); // 0

count.value += 1; //count.set(count.get() + 1);

console.log(count.value); // 1
console.log(count.get()); // 1

2. Computed

Computed values are derived signals. They depend on other signals and re-evaluate only when their dependencies change. They are lazy—they only recalculate when read.

import { Signal, Computed } from 'avosignals';

const count = new Signal(1);
const double = new Computed(() => count.get() * 2, 'double');

console.log(double.get()); // 2

count.set(10);
console.log(double.get()); // 20

3. Effects

Effects are side effects that run automatically whenever the signals they access change. Useful for logging, manual DOM manipulation, or syncing with external APIs.

import { Signal, effect } from 'avosignals';

const count = new Signal(0);

const dispose = effect(() => {
    console.log(`The count is now ${count.get()}`);
    
    // Optional cleanup function (runs before next execution or on dispose)
    return () => console.log('Cleaning up...');
});

count.set(1); 
// Logs: "The count is now 1"

dispose();
// Logs: 'Cleaning up...'

4. Manual Subscription

If you need to listen to changes without creating an automatic effect (for example, to integrate with a legacy API or one-off logic), you can subscribe directly to any Signal or Computed.

Note: Unlike effect, manual subscriptions do not track dependencies automatically; they only fire when the specific signal you subscribed to changes.

import { Signal } from 'avosignals';

const theme = new Signal('light');

// Returns an unsubscribe function
const unsubscribe = theme.subscribe(() => {
    console.log(`Theme changed to: ${theme.get()}`);
});

theme.set('dark'); // Logs: "Theme changed to: dark"

// Stop listening
unsubscribe();

Usage with Lit

avosignals was built with Lit in mind. The SignalWatcher class hooks into the Lit lifecycle to automatically track signals accessed during render. Its core design is to allow for production ready signals which can be easily replaced with Lit's official signals once TC39 signals becomes mainstream and production ready.

The SignalWatcher Controller

You do not need to manually subscribe to signals. simply add the controller, and any signal read inside render() will trigger a component update when it changes.

import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { Signal, SignalWatcher } from 'avosignals';

// Shared state
const counter = new Signal(0);

@customElement('my-counter')
export class MyCounter extends LitElement {
    // 1. Register the watcher
    private watcher = new SignalWatcher(this);

    render() {
        // 2. Access signals directly. 
        // The component now auto-subscribes to 'counter'.
        return html`
            <p>Count: ${counter.get()}</p>
            <button @click=${() => counter.update(c => c + 1)}>
                Increment
            </button>
        `;
    }
}

Usage with Vanilla Web Components

You can easily use avosignals with standard HTML Web Components. Since vanilla components don't have a built-in reactive render cycle, the best pattern is to use an effect inside connectedCallback to update the DOM, and clean it up in disconnectedCallback.

import { Signal, effect } from 'avosignals';

const count = new Signal(0);

class VanillaCounter extends HTMLElement {
    private dispose?: () => void;
    private label = document.createElement('span');
    private button = document.createElement('button');

    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        
        this.button.textContent = 'Increment';
        this.button.onclick = () => count.update(c => c + 1);
        
        // Initial layout
        this.shadowRoot?.append(this.label, this.button);
    }

    connectedCallback() {
        // Use 'effect' to bind the signal state to the DOM text.
        // This runs immediately and whenever 'count' changes.
        this.dispose = effect(() => {
            this.label.textContent = `Current count: ${count.get()} `;
        });
    }

    disconnectedCallback() {
        // ⚠️ Important: Always clean up effects when the element 
        // is removed from the DOM to prevent memory leaks.
        if (this.dispose) this.dispose();
    }
}

customElements.define('vanilla-counter', VanillaCounter);

Advanced Architecture

Memory Management (WeakRefs)

Unlike many other signal libraries, Computed nodes in avosignals hold weak references to their subscribers where possible. This means if you create a derived signal but stop referencing it in your application, JavaScript's Garbage Collector can clean it up, even if the source signal is still active. This prevents the common "detached listener" memory leaks found in observer patterns.

Cycle Detection

avosignals maintains a stack of active consumers. If a computed value attempts to read itself during its own evaluation (A -> B -> A), the library throws a descriptive error helping you identify the cycle immediately.

Read/Write Consistency

To ensure unidirectional data flow, avosignals forbids writing to a Signal while a Computed value is currently being evaluated. This prevents side-effects from occurring during the "read" phase of your application loop.

API Reference

Signal<T>

  • constructor(initial: T, name?: string)
  • get(): T: Returns current value and tracks dependency.
  • set(value: T): Updates value and notifies listeners.
  • update(fn: (prev: T) => T): Convenience method for updating based on previous value.

Computed<T>

  • constructor(fn: () => T, name?: string)
  • get(): T: Evaluates (if dirty) and returns the value.

effect

  • effect(fn: () => void | cleanupFn): Runs immediately and tracks dependencies. Returns a dispose function.

Demos

See demos here

License

MIT