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

@suidrmz/expectree

v0.2.0

Published

Type-safe expectation trees for modeling branching workflows, tutorials, exams, and incident runbooks in TypeScript and React.

Readme

Expectree

Expectree is a TypeScript library for managing complex, branching state as expectation trees. You define expectations for how a scenario should unfold, and Expectree helps you run them step-by-step, inspect the state, and compare what actually happened to what you expected.

Use Cases

  • Interactive / branching tutorials – model multiple paths a learner can take (e.g., Vim tutorials, product tours).
  • Adaptive exams & assessments – unlock follow-up expectations based on answers and mastery.
  • Incident runbooks & operational workflows – encode your runbooks as trees, visualize progress, and record history.
  • Complex workflows & wizards – represent multi-step flows with shortcuts, optional branches, and guard conditions.

If you build something with Expectree that doesn’t fit these boxes, I’d love to hear about it — see the “Real‑world Use Cases” section below.

Install

npm i @suidrmz/expectree

First Scenario

import { node, TreeBuilder } from '@suidrmz/expectree';

// Vim tutorial: either perform the three manual edits or use the substitution macro
const moveCursor = node('vim.cursor.line5', { type: 'vim', goal: 'position' });
const deleteFoo = node('vim.delete.foo', { type: 'vim', goal: 'edit' });
const deleteBar = node('vim.delete.bar', { type: 'vim', goal: 'edit' });
const substitution = node('vim.substitute.foo-bar', {
  type: 'vim',
  goal: 'shortcut',
});

const tree = new TreeBuilder()
  .group(builder =>
    builder
      .addExpectation(moveCursor)
      .and()
      .group(b => b.addExpectation(deleteFoo).and().addExpectation(deleteBar))
  )
  .or()
  .addExpectation(substitution)
  .build();

// Learner takes the manual path
moveCursor.fulfill();
deleteFoo.fulfill();
deleteBar.fulfill();

console.log(tree.status); // "PASSED"
console.log(tree.diffs); // Snapshot of what changed for playback/analytics

Core Concepts

Expectations (Nodes)

Nodes represent observable goals. They can be fulfilled, rejected, or pending. Attach metadata (type, tags, group) to power selectors, analytics, and author tooling.

Composition

Combine expectations with boolean operators:

  • and() – all expectations inside the group must pass
  • or() – any branch can satisfy the scenario
  • not() – invert an expectation (use sparingly for clarity)
  • group() – nest structure to express prerequisites and optional paths

State & Snapshots

Trees maintain immutable state. Each change produces a new snapshot and diff list, enabling time travel, playback, and deterministic debugging.

Reactivity

Subscribe via tree.subscribe, hook into React with useExpectationTree, or call tree.visualize to render ASCII/other representations for instructor consoles.

Scenario Patterns

  • Linear with escape hatches – sequential steps with alternative shortcuts (tutorials)
  • Fan-in quests – multiple parallel tasks that converge (CLI or onboarding flows)
  • Guarded runbooks – nested approvals and verification gates (operations)
  • Adaptive exams – evaluation nodes unlock follow-up expectations based on mastery

The tests in tests/unit showcase these patterns in detail.

React Example: Incident Runbook

Here’s a minimal React component that wires an incident runbook tree into your UI:

import { useMemo } from 'react';
import { node, TreeBuilder, useExpectationTree } from '@suidrmz/expectree';

function createRunbookTree() {
  const acknowledge = node('runbook.ack', { type: 'runbook', stage: 'ack' });
  const rollback = node('runbook.rollback', {
    type: 'runbook',
    stage: 'mitigate',
  });
  const verify = node('runbook.verify', { type: 'runbook', stage: 'verify' });
  const escalate = node('runbook.escalate', {
    type: 'runbook',
    stage: 'fallback',
  });

  return new TreeBuilder()
    .addExpectation(acknowledge)
    .and()
    .group(b => b.addExpectation(rollback).or().addExpectation(escalate))
    .and()
    .addExpectation(verify)
    .build();
}

export function IncidentRunbookPanel() {
  const tree = useMemo(() => createRunbookTree(), []);
  const { status, snapshot, diffs } = useExpectationTree(tree);

  return (
    <section>
      <h2>Runbook status: {status}</h2>
      <pre>{tree.visualize('ascii')}</pre>
      <small>
        Steps taken: {snapshot.completedCount} / {snapshot.totalCount}
      </small>
      <details>
        <summary>Last changes</summary>
        <pre>{JSON.stringify(diffs, null, 2)}</pre>
      </details>
    </section>
  );
}

Below are more focused examples showing how to work with nodes and trees.

Creating Nodes

import { node, check } from '@suidrmz/expectree';

// With alias (dot syntax)
const n1 = node('user.isAdmin', { type: 'user', role: 'admin' });

// With tags and groups
const n2 = node(
  'auth.check',
  { type: 'auth' },
  {
    tags: ['security', 'auth'],
    group: 'authentication',
  }
);

// Simple check
const n3 = check({ type: 'feature', enabled: true });

Building Trees

const tree = new TreeBuilder()
  .addExpectation(node('A', { type: 'check' }))
  .and()
  .addExpectation(node('B', { type: 'check' }))
  .or()
  .group(b => b.addExpectation(node('C', { type: 'check' })).not())
  .build();

Fulfilling Nodes

// Get node reference
const nodeA = tree.findOne('A'); // by alias
const nodeB = tree.findByTag('auth')[0]; // by tag
const nodeC = tree.findByGroup('user')[0]; // by group

// Fulfill nodes
nodeA.fulfill();
nodeB.reject();
nodeC.reset(); // back to pending

// Check individual node status
console.log(nodeA.isFulfilled()); // true
console.log(nodeB.isRejected()); // true
console.log(nodeC.isPending()); // true

Querying Nodes

// Find by alias
const node = tree.findOne('user.isAdmin');

// Find by tag
const authNodes = tree.findByTag('auth');

// Find by group
const userNodes = tree.findByGroup('user');

// String selector syntax
tree.find('#auth'); // by tag
tree.find('@user'); // by group
tree.find('user.isAdmin'); // by alias

Snapshots & Time Travel

const { snapshot, diffs, isFulfilled } = useExpectationTree(tree);

console.log(snapshot.status); // "PASSED" | "FAILED" | "PENDING"
console.log(snapshot.expectations.length); // all nodes with current statuses
console.log(diffs); // list of changes since the last snapshot

// Handy helpers for dashboards/tests
console.log(isFulfilled()); // is the whole scenario satisfied?

Async & External Signals

Expectations support fulfillAsync, rejectAsync, and evaluateAsync, making it easy to plug in telemetry streams (e.g., command output, API checks, IDE events). Snapshots/diffs capture the resulting state transitions.

import { node } from '@suidrmz/expectree';

const apiHealthy = node('service.apiHealthy', { type: 'healthcheck' });

// Wire in your own async logic
apiHealthy.evaluateAsync(async () => {
  const res = await fetch('/health');
  return res.ok ? 'FULFILLED' : 'REJECTED';
});

Import/Export

Use exportExpectations and importExpectations to persist scenarios, share them with authoring tools, or replay sessions from logs.

import {
  exportExpectations,
  importExpectations,
  TreeBuilder,
} from '@suidrmz/expectree';

// Serialize a tree definition (no runtime state)
const serialized = exportExpectations(tree);

// Later or elsewhere — recreate the same structure
const restoredTree = new TreeBuilder().fromSerialized(serialized).build();

Philosophy

  • Scenario-first – everything ladders up to interactive journeys.
  • Transparent state – immutable snapshots and diffs are table stakes.
  • Extensible adapters – domain-specific instrumentation lives outside the core.

Contributing

Contributions are very welcome — from bug reports and small fixes to new helpers, visualizers, and example scenarios.

  • Open an issue if something is confusing, missing, or broken.
  • Send a PR that follows the existing code style and includes tests where it makes sense (the tests/unit folder has plenty of patterns to copy).
  • If you’re unsure whether an idea fits, a short issue or email is perfect to start the conversation.

If you’d like to get involved but aren’t sure where to start, reach out at [email protected] and we can find a good entry point.

Sponsorship & Support

If Expectree helps you ship better tutorials, exams, runbooks, or workflows, consider supporting its development.

  • Encourage adoption inside your team or company.
  • Sponsor feature work, consulting, or priority support by emailing [email protected].

Even a short note about how you’re using the library is incredibly motivating and helps steer the roadmap.

Real‑world Use Cases

I’d love to hear about real-world scenarios where Expectree is used — from education tools to incident dashboards and beyond.

If you have a production or experimental use case:

  • Share a brief description (and anything you can show publicly) via [email protected].
  • Let me know what worked well, what was awkward, and what you wish the library did better.

These stories help shape future APIs, examples, and documentation.

License

MIT