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

@mantaq/sugar

v1.0.0

Published

Userland sugar for mantaq actors — matches, effect helpers, transition utilities.

Readme

@mantaq/sugar

Convenience helpers for @mantaq/core actors. State/event batching, pattern matching, effect utilities, and dynamic child management.

Install

npm install @mantaq/sugar
# or
pnpm add @mantaq/sugar
# or
yarn add @mantaq/sugar

Quick Start

import { states, events, matches, withTimeout } from "@mantaq/sugar";
import { Actor, VirtualClock } from "@mantaq/core";

const s = states("idle", "loading", "success", "error");
const e = events("fetch", "resolve", "fail");

const clock = new VirtualClock();

const machine = new Actor({
  inputs: [e.fetch],
  internal: [e.resolve, e.fail],
  context: {},
  states: [s.idle, s.loading, s.success, s.error],
  initial: s.idle,
  clock,
  setup: (m) => {
    m.on(s.idle, e.fetch, () => ({ state: s.loading }));
    m.effect(s.loading, {
      name: "startFetchTimeout",
      fn: (input) => withTimeout(2000, input, () => e.resolve.create()),
    });
    m.on(s.loading, e.resolve, () => ({ state: s.success }));
    m.on(s.loading, e.fail, () => ({ state: s.error }));
  },
});

matches(machine, "idle"); // true
machine.send(e.fetch.create());
matches(machine, "loading"); // true
clock.advance(2000);
matches(machine, "success"); // true

Patterns

Type-safe state groups with states()

Use states() to create a record of StateRefs with full type safety. The returned keys match the input strings exactly.

import { states } from "@mantaq/sugar";

const s = states("idle", "loading", "success", "error");

s.idle.name; // "idle"
s.success.final(); // StateRef with isFinal = true

const actor = new Actor({
  states: [s.idle, s.loading, s.success, s.error],
  initial: s.idle,
  // ...
});

Anti-pattern: Creating states manually without states() and losing type safety:

import { state } from "@mantaq/core";

const idle = state("idle")();
const loading = state("loading")();
const success = state("success")();
const error = state("error")();
// No single record — easy to miss one, no autocomplete, no compile check

states() guarantees all names exist as typed keys. Manual creation requires you to keep references in sync yourself.

Type-safe event groups with events()

Same as states() but for events. Creates a typed record with .create(), .is(), and other EventRef methods.

import { events } from "@mantaq/sugar";

const e = events("fetch", "resolve", "fail");

e.fetch.create(); // { type: "fetch" }
e.resolve.is(emittedEvent); // boolean

const actor = new Actor({
  inputs: [e.fetch],
  internal: [e.resolve],
  // ...
});

Anti-pattern: Scattering event refs across files without a shared group:

import { event } from "@mantaq/core";

const fetchEvent = event("fetch")();
const resolveEvent = event("resolve")();
const failEvent = event("fail")();
// No single source of truth — naming drift, typos not caught

events() creates a single typed object. Any typo in a key is a compile error.

Pattern matching with matches()

matches() uses dot-separated paths. The format alternates between state names and region keys: "state.regionKey.state".

import { matches } from "@mantaq/sugar";

// flat
matches(actor, "idle"); // true if in idle

// hierarchical — state, region key, nested state
matches(actor, "connected.default.active");

// parallel — match any region
matches(actor, "player.playing");

Anti-pattern: Using wrong path format:

// WRONG — region key must come after parent state
matches(actor, "active.connected.default");

// WRONG — trailing dot always returns false
matches(actor, "idle.");

// WRONG — empty string returns false
matches(actor, "");

Path format: "parentState.regionKey.childState". The region key is the key you passed to the actor's regions option, not the nested state's name.

Using tag() for state grouping

tag() groups multiple StateRefs and tests if a snapshot matches any of them. Useful for UI that needs to branch on state categories.

import { tag } from "@mantaq/sugar";

const s = states("idle", "loading", "success", "error");
const busy = tag(s.idle, s.loading);
const terminal = tag(s.success, s.error);

// In UI
if (busy.has(snapshot)) {
  showSpinner();
} else if (terminal.has(snapshot)) {
  showResult();
}

Anti-pattern: Testing individual states manually with matches():

if (matches(actor, "idle") || matches(actor, "loading")) {
  showSpinner();
}
// Breaks when hierarchy changes, verbose, error-prone

tag() searches recursively through parallel regions and nested hierarchies. Works regardless of depth.

ActorMap patterns

ActorMap manages dynamic children by string key. Always use ensure() to avoid duplicate spawns.

import { ActorMap, isIn } from "@mantaq/sugar";

const map = new ActorMap(parentActor);

// Safe spawn — no-op if key exists
map.ensure("child1", () => createChild());

// Send events
map.send("child1", someEvent);

// Snapshot a child
const snap = map.snapshot("child1");
if (snap && isIn(snap, "active")) {
  // child is active
}

// Kill when done
map.kill("child1");

Anti-pattern: Using spawn() without checking for duplicates:

map.spawn("child1", () => createChild());
map.spawn("child1", () => createChild()); // warns, aborts the previous child, replaces

ensure() is idempotent. Safe to call repeatedly. spawn() always replaces.

Effect utilities: withPromise vs manual handling

Always use withPromise() for promise-to-event bridging. It handles abort signals automatically.

import { withPromise } from "@mantaq/sugar";

// Correct — abort-aware
withPromise(fetchData(), input.signal, input.emit, {
  success: (data) => ({ type: "loaded", payload: data }),
  error: (err) => ({ type: "loadFailed", payload: String(err) }),
});

Anti-pattern: Manual .then()/.catch() without abort handling:

fetchData()
  .then((data) => {
    input.emit({ type: "loaded", payload: data }); // fires even if actor destroyed
  })
  .catch((err) => {
    input.emit({ type: "loadFailed", payload: String(err) }); // same problem
  });

withPromise checks signal.aborted before each emit. Manual chains fire events into destroyed actors.

Helpers

matches(actor, pattern)

Dot-notation state pattern matching against an actor's snapshot. Works with flat, hierarchical, and parallel states.

import { matches } from "@mantaq/sugar";

// flat
matches(actor, "idle"); // true/false

// hierarchical — region path
matches(actor, "connected.default.active");

// parallel — any region matches
matches(actor, "player.playback.playing");

Pattern format: "state.regionKey.state" or just "state" for flat. Empty string or trailing dots return false.

ActorMap

Dynamic child actor registry. Spawns, sends to, kills, and snapshots children by string key. Optionally wires child output to a parent actor.

import { ActorMap, isIn } from "@mantaq/sugar";

const map = new ActorMap(); // or new ActorMap(parentActor)

map.spawn("child1", () => childActor);
map.spawn("child2", () => otherActor);

map.send("child1", someEvent);
map.kill("child2");
map.keys(); // ["child1"]

const snap = map.snapshot("child1"); // Snapshot | undefined
if (snap && isIn(snap, "active")) {
  // child is active
}

ensure(key, factory). Spawn only if key missing:

map.ensure("child1", () => childActor); // no-op if exists

Typing children: ActorMap is untyped by design. Access children through your own typed references or use matches() / snapshot() for type-safe checks.

Parent wiring: When constructed with a parent actor, child outputs are automatically forwarded to the parent:

const map = new ActorMap(parentActor);
map.spawn("child", () => childActor);
// child emits → parent receives

broadcast(map, event)

Send an event to every key in an ActorMap (or any SendableMap):

import { broadcast } from "@mantaq/sugar";

broadcast(map, { type: "ping" });

Works with ActorMap or any object implementing { keys(): string[]; send(key, event): void }.

onOutput(actor, handler)

Route an actor's emitted outputs to a handler. regions auto-wire child outputs into the parent; ActorMap children do not. This is the public wrapper for that wiring seam, no internal import needed:

import { ActorMap, onOutput } from "@mantaq/sugar";

const requests = new ActorMap(
  (id) => {
    const child = createHandler(id);
    onOutput(child, (e) => {
      if (result.is(e)) parent.send(e);
    });
    return child;
  },
  { autoReap: true },
);

The is() guard narrows the output to the receiver's declared input.

tag(...stateRefs)

Group multiple StateRefs and test if a snapshot matches any of them. Useful for UI state grouping.

import { tag } from "@mantaq/sugar";
import { state } from "@mantaq/core";

const idle = state("idle")();
const loading = state("loading")();
const busy = tag(idle, loading);

busy.has(snapshot); // true if snapshot is idle or loading (any depth)

Searches recursively through parallel regions and nested hierarchies.

states(...names)

Batch-create StateRefs as a typed record:

import { states } from "@mantaq/sugar";

const s = states("idle", "loading", "success");
s.idle.name; // "idle"
s.success.final(); // StateRef with isFinal = true

events(...names)

Batch-create EventRefs as a typed record:

import { events } from "@mantaq/sugar";

const e = events("click", "submit");
e.click.create(); // { type: "click" }
e.click.is(emittedEvent); // boolean

withPromise(promise, signal, emit, events)

Bridge a promise into actor events. Emits success on resolve, error on reject. Respects AbortSignal. Skips emit if aborted before settlement.

import { withPromise } from "@mantaq/sugar";

withPromise(fetchData(), input.signal, input.emit, {
  success: (data) => ({ type: "loaded", payload: data }),
  error: (err) => ({ type: "loadFailed", payload: String(err) }),
});

withTimeout(ms, input, eventFn)

Schedule a timeout event through the actor's clock. Aborts cleanly if the actor is destroyed before timeout fires.

import { withTimeout } from "@mantaq/sugar";

withTimeout(5000, input, () => ({ type: "timeout", payload: { reason: "exceeded" } }));

Migration from Core

Starting with @mantaq/core? Here's what sugar adds.

| Core | Sugar | Benefit | | --------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------- | | state("idle")() × N | states("idle", "loading") | Single call. Typed record. No missed names. | | event("FETCH")() × N | events("FETCH", "RESOLVE") | Single call. Typed record. Shared source of truth. | | snapshot().path.includes("active") | matches(actor, "active") | Works on actor directly. Dot-notation for hierarchies: "idle.region.a". | | Manual .then()/.catch() + abort check | withPromise(promise, signal, emit, events) | Auto abort-aware. No forgotten signal.aborted guard. | | Group states by variable naming | tag(stateA, stateB).has(snapshot) | Recursive matching. Works through parallel regions. |

Core alone:

import { state, event } from "@mantaq/core";

const idle = state("idle")();
const loading = state("loading")();
const success = state("success")();
const failEvent = event("FAIL")();

actor.snapshot().path.includes("idle"); // flat name only

With sugar:

import { states, events, matches, tag } from "@mantaq/sugar";

const s = states("idle", "loading", "success");
const e = events("FAIL");

matches(actor, "idle"); // actor, not snapshot
matches(actor, "connected.default.active"); // hierarchical

const busy = tag(s.idle, s.loading);
busy.has(actor.snapshot()); // recursive

No migration needed. Sugar wraps core. Use both side by side.

License

MIT