Introduction
dirtytalk is a set of dirty-tracking primitives that answer what changed, who cares, and when to tell them, for any domain.
dirtytalk is a small monorepo of dirty-tracking primitives. After a mutation, it computes what changed once, tells only the consumers that care, and does so at a moment you choose.
What dirtytalk does
After any mutation, three questions need answers:
- What changed? A
Spacedefines what a region is in your domain (a set of keys, a set of paths, a list of rectangles). Every mark is a region, and marks within one window are unioned into a single dirty region. - Who cares? Each subscriber supplies an interest thunk that returns a region. On flush, only subscribers whose interest intersects the dirty region are called.
- When do we tell them? A
Schedulerdecides when the flush runs: immediately, at the end of the microtask, on the next animation frame, or when you callpump().
See Concepts for the full model.
Packages
| Package | Role | Docs |
|---|---|---|
@dirtytalk/engine | The algebra and the glue: Space<Region>, Scheduler and its four implementations, and DirtyChannel. | Engine |
@dirtytalk/structural | A Space over interned state paths (PathSet), plus a state container, render tracking and a React hook. | Structural |
@dirtytalk/spatial | A Space over damage rectangles (Damage[]), plus a scene graph, a damage-driven renderer and pointer routing. | Spatial |
structural and spatial are built on engine. Neither re-implements scheduling or subscription.
Which package do I need
- You need to coalesce marks and notify interested subscribers for your own kind of region: use
@dirtytalk/engineand implement aSpace. See Custom spaces. - You have plain object state and want consumers (such as React components) to re-run only when the paths they read change: use
@dirtytalk/structural. - You draw to a canvas or similar surface and want to repaint only the rectangles that changed: use
@dirtytalk/spatial. - You only need the scheduling behavior (sync, microtask, animation frame, manual): use
@dirtytalk/enginealone.
Example
A channel over a set of string keys, using @dirtytalk/engine only. Marks in one microtask coalesce into a single flush, and subscribers whose interest does not intersect the dirty region are skipped.
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());
channel.subscribe(
() => new Set(['users']),
(dirty) => console.log('users view:', [...dirty]),
);
channel.subscribe(
() => new Set(['theme']),
(dirty) => console.log('theme view:', [...dirty]),
);
channel.mark(new Set(['users']));
channel.mark(new Set(['session']));
await Promise.resolve();
// users view: [ 'users', 'session' ]
// (theme view is not called: its interest did not intersect)
Next steps
- Installation: install each package, entry points, and versioning.
- Concepts: the model shared by all three packages.
- Interactive chapters: engine, structural, spatial.