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

@dirtytalk/engine

v0.2.1

Published

A reactive dirty-tracking and notification engine. Zero deps, pluggable space, pluggable scheduler.

Readme

@dirtytalk/engine

A reactive dirty-tracking and notification engine. Zero deps, pluggable space, pluggable scheduler.

[!WARNING] BlaC v2 is in pre-release (beta). While in beta, breaking API changes may ship in patch releases without a major version bump. Pin an exact version and check the changelog before upgrading. Strict semver resumes once v2 is officially out of beta.

Why this exists

Both insomni (a WebGPU renderer) and blac (a state container library) solve the same problem in different domains: after a mutation, what changed, who cares, and when do we tell them? Today both answer this per-consumer — insomni repaints the entire canvas because it has no rect-level damage info; blac walks state N times for N consumers.

The shared move is to compute "what changed" once at the source, in a format every subscriber can intersect cheaply. Insomni's format is a union of damage rects. Blac's format is a set of interned path IDs. Both are the same algebra: a region with empty, union, and intersects. This package is that algebra plus the scheduling glue.

What's in the box

  • Space<Region> interface — the algebra a consuming library must implement to describe its notion of "what changed" (e.g., rect unions, path sets).
  • Scheduler interface + four built-in implementations (SyncScheduler, ManualScheduler, MicrotaskScheduler, RAFScheduler) — controls when a flush runs.
  • DirtyChannel<Region> — the main event: accumulates marks within a scheduler window, then delivers to interested subscribers in one flush.

Install

pnpm add @dirtytalk/engine

Quick example

import { DirtyChannel, SyncScheduler } from '@dirtytalk/engine';
import type { Space } from '@dirtytalk/engine';

// A trivial Space where Region = Set<string>.
const StringSetSpace: Space<Set<string>> = {
  empty: () => new Set(),
  isEmpty: (r) => r.size === 0,
  union: (a, b) => new Set([...a, ...b]),
  intersects: (interest, dirty) => {
    for (const k of interest) {
      if (dirty.has(k)) return true;
    }
    return false;
  },
};

const channel = new DirtyChannel(StringSetSpace, new SyncScheduler());

// Subscribe with an interest thunk (re-evaluated each flush).
const unsub = channel.subscribe(
  () => new Set(['users', 'session']),
  (dirty) => {
    console.log('dirty keys:', [...dirty]);
  },
);

channel.mark(new Set(['users']));
// => "dirty keys: [ 'users' ]"  (SyncScheduler flushes immediately)

channel.mark(new Set(['theme']));
// => no output — 'theme' doesn't intersect our interest

unsub();

The Space<Region> interface

interface Space<Region> {
  empty(): Region;
  isEmpty(r: Region): boolean;
  union(a: Region, b: Region): Region; // accumulate dirty marks
  intersects(interest: Region, dirty: Region): boolean; // delivery predicate
}

Contracts:

  • union(empty(), r) is equivalent to r.
  • intersects(empty(), _) returns false.
  • Both operations must be pure — no side effects, stable output for stable inputs.

This package provides no concrete Space implementations. Those live in the consuming libraries. Insomni's RectSpace and blac's PathSetSpace are the motivating examples.

Scheduler interface + provided implementations

interface Scheduler {
  request(flush: () => void): void; // call flush at most once per scheduling window
  cancel?(): void; // optional teardown
}

request must be idempotent within a window: ten calls before the first flush produce one flush, not ten.

| Scheduler | When it flushes | Intended use | | -------------------- | ---------------------------------------------------------------- | ------------------------------ | | SyncScheduler | Immediately on request | Tests, sync emit compatibility | | ManualScheduler | When .pump() is called | Tests, replay, SSR | | MicrotaskScheduler | End of current microtask queue | blac (default) | | RAFScheduler | Next requestAnimationFrame (falls back to setTimeout(_, 16)) | insomni |

DirtyChannel<Region>

class DirtyChannel<Region> {
  constructor(space: Space<Region>, scheduler: Scheduler);
  mark(r: Region): void;
  subscribe(interest: () => Region, cb: (dirty: Region) => void): () => void;
}

mark accumulates into an internal dirty accumulator and requests a flush. subscribe registers an interest thunk and a callback; both are evaluated at flush time, not at subscribe time.

const unsub = channel.subscribe(
  () => myNode.bounds(), // re-evaluated every flush
  (dirty) => myNode.repaint(dirty),
);

Returns an unsubscribe function. Safe to call at any time, including from inside a callback.

Behaviour notes

  • Marks coalesce. Many mark calls within the same scheduler window produce one flush. Space.union is called per mark; the result is a single accumulated region.
  • Interest is a thunk. Re-evaluated on every flush, so subscribers can move, resize, or reconfigure freely between flushes. If you snapshot at subscribe time, you miss updates.
  • Re-entrant marks defer. Calling mark from inside a subscriber callback accumulates into the next flush. No infinite loops; bounded work per flush tick.
  • Error isolation. If a subscriber callback throws, the error is collected and the flush continues to completion. A single error is re-thrown as-is; multiple errors are wrapped in an AggregateError.
  • Subscribe/unsubscribe during flush is safe. New subscribers see the next flush, not the current one. Unsubscribed callbacks are skipped immediately, even mid-flush.

What it is not

  • No auto-tracked computed values. No computed(() => a.value + b.value) with hidden dependency graphs. Build derived values above this layer.
  • No effect system with cleanups. subscribe returns an unsubscribe; that's the cleanup.
  • No selector/memoization helpers. Consumer-side concern (React's useMemo, blac's per-consumer tracker).
  • No diffing utilities. Producing a Region from a mutation is the consuming library's job. The engine only unions and intersects.
  • No glitch-free guarantees across a dependency graph. There is no dependency graph at this layer.
  • Not coupled to any framework. React, the DOM, and the GPU are not referenced here.

License

MIT