Engine overview

@dirtytalk/engine is a zero-dependency dirty-tracking core: a Space algebra, pluggable schedulers and a DirtyChannel that coalesces marks.

View .md Edit on GitHub

@dirtytalk/engine computes “what changed” once at the source, then tells only the subscribers that care, once per scheduling window. It has zero dependencies and ships no concrete region type: you supply the Space.

When to use it

  • Several consumers react to the same mutations, and each cares about a different part of the data (rectangles on a canvas, paths in a state tree, keys in a store).
  • You want many mutations in one tick to produce a single notification per subscriber.
  • You want to choose when notifications run (immediately, per microtask, per animation frame, or on demand) without changing producer or consumer code.

It is not a reactive graph. There are no computed values, effects, selectors or diffing helpers; producing a region from a mutation is your job. See Concepts.

Install

pnpm add @dirtytalk/engine

Building blocks

PieceRolePage
Space<Region>Algebra describing a region: empty, isEmpty, union, intersectsCustom spaces
SchedulerDecides when a flush runsSchedulers
DirtyChannel<Region>Accumulates marks, notifies interested subscribersDirtyChannel

Minimal example

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

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

const channel = new DirtyChannel(KeySpace, new MicrotaskScheduler());

const unsubscribe = channel.subscribe(
  () => new Set(['users', 'session']),
  (dirty) => console.log('changed:', [...dirty]),
);

channel.mark(new Set(['users']));
channel.mark(new Set(['session', 'theme']));
// After the current microtask queue drains, one call:
// changed: [ 'users', 'session', 'theme' ]

channel.mark(new Set(['theme']));
// No callback: 'theme' is not in the subscriber's interest.

unsubscribe();
channel.dispose();

Both mark calls land in the same window, so the subscriber is called once with the union of the marks. The callback receives the whole dirty region, not only the part that intersects its interest.

Where to go next