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

@timberland/emitters

v0.0.4

Published

Lightweight reactivity library for vanilla JS

Readme

Timberland - Emitters

The emitters package is a very simple and straight-forward implementation of the Observer Pattern. It also takes inspiration from several niceties found in RxJs and Signals, such as:

  • Explicit subscribe and unsubscribe methods (RxJs)
  • until method (RxJs)
  • Computed reactive values (Signals)

Besides:

  • Make any value reactive. It can be anything from a string to a WeakMap, implemented with no extra code
  • Create events that are not value dependent. If you don't need your reactivity to be attached to any specific value, just use the Emitters for triggering events
  • Less than 500b minified + gzipped

Project status

This package is pretty new and we don't expect a crazy wild adoption. The API and the implementation are fairly simple, but please be aware that bugs might appear. If you find anything strange, please let us know by opening an issue.

Even though it's under active development, the API is stable and it's very unlikely to change. However, until we don't hit a v1, we cannot ensure that the API will remain intact. There are still a lot of work to do and we will try our best to not change the usage. But if a fix requires changing the API in order to keep the bundle size small, we should be open to minor adjustments.

Installation

With a package manager

pnpm add @timberland/emitters
...
import { Emitter, ComputedEmitter } from "@timberland/emitters"

With a CDN

<!-- ESM -->
<script type="module">
    import { Emitter, ComputedEmitter } from "https://unpkg.com/@timberland/emitters/dist/emitters.esm.js"
</script>

<!-- IIFE -->
<script src="https://unpkg.com/@timberland/emitters/dist/emitters.iife.js"></script>
<script>
    // Stored under the emitters global name so we don't pollute the global scope
    const { Emitter, ComputedEmitter } = window.emitters 
</script>

[!CAUTION] These examples should be used for development only. If you plan to use the CDN for production, pin a specific version. For instance: https://unpkg.com/@timberland/[email protected]/dist/emitters.esm.js. Check the releases section for getting the latest version.

Reference (API/Usage)

new Emitter(optionalInitialValue)

The Emitter constructor creates a new Emitter instance with an optional initial value:

const $count = new Emitter(0)

const $map = new Emitter(new Map())

const $statelessEvent = new Emitter()

Emitter.value

This will return the current value of the Emitter (if any). Feel free to access it anywhere in your code, as it won't register or trigger any side effects:

$count.value // -> 0

$map.value // -> Map instance

$statelessEvent.value // -> null

Emitter#subscribe

It accepts a callback that will be triggered whenever the next or trigger methods are invoked. The callback will receive the current value of the Emitter or any message provided with the trigger method.

$count.subscribe((count) => console.log(count))

$map.subscribe((map) => console.log(map.get('awesomeKey')))

$statelessEvent.subscribe((optionalMessage) => console.log(optionalMessage ?? 'No message provided'))

Subscriptions are lazy by default, meaning that they will not run when first declared. If you need to run it as soon as declared, you can pass an object with the lazy property set as false as a second argument:

$count.subscribe((count) => doSomething(), { lazy: false })

In any case, it will return a Subscription object. More on it later.

Emitter#next

This method will be responsible for mutating the value and triggering all subscriptions. You can either provide a new value directly or through a callback (recommended for complex datatypes):

$count.next(2) // -> will set the value to 2 and trigger all subscriptions

$map.next((prev) => prev.set('hello', 'world')) // -> will mutate the value and then trigger the subscriptions

$statelessEvent.next('now we have value') // -> not recommended, but possible

new ComputedEmitter(callback, [dependencies])

The ComputedEmitter constructor returns an Emitter-like object with only a subscribe method. Its value will be computed based on the callback's return value and the Emitters contained within the dependencies array:

const $double = new ComputedEmitter(() => $count.value * 2, [ $count ])

Behind the scenes, it is creating an internal Emitter whose value is being updated everytime the value of any of its dependencies change:

$double.subscribe((value) => console.log(value))

$count.next(2) // -> will log 4

Subscription

Any subscribe method will return a Subscription object that we can use to clear the subscription itself. The object is exactly the same in both Emitter and ComputedEmitter.

Subscription#unsubscribe

This will clear the subscription on-demand:

const $count = new Emitter(0)
const subscription = $count.subscribe((count) => console.log(count))

$count.next( count.value + 1 ) // -> will log 1
subscription.unsubscribe()
$count.next( count.value + 1 ) // -> won't log anything

Subscription#until

Heavily inspired by the takeUntil method from RxJs. Useful when we want the subscription to take place only until a certain condition is met:

const $count = new Emitter(0)
const subscription = $count.subscribe((count) => console.log(count))

subscription.until((value) => value > 3)

$count.next( count.value + 1 ) // -> will log 1
$count.next( count.value + 1 ) // -> will log 2
$count.next( count.value + 1 ) // -> will log 3
$count.next( count.value + 1 ) // -> won't log anything

Subscription#trigger(optionalMessage)

This will trigger the callback without mutating the value of the Emitter. Useful for when we have a stateless Emitter. Optionally, it takes a message that will be passed on to the callback:

const $onMessage = new Emitter()
const messageEvent = $onMessage.subscribe((msg) => console.log(msg ?? 'no message provided'))

messageEvent.trigger('Hello world') // will log 'Hello world'
messageEvent.trigger() // will log 'no message provided'

License

MIT