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

@salilvnair/state-machine

v1.0.1

Published

Generic visual state machine library — ck8t card style, React Flow canvas, consumer-provided logic

Readme

@salilvnair/state-machine

Generic visual state machine library — a ck8t-styled React Flow canvas for building, running, and debugging event-driven workflows, with all persistence left to the host app.

npm version license

Repository: https://github.com/salilvnair/state-machine

Built for Daakia's stateful mock server engine — a mock route can be gated by a real transition event from a workflow built here, so a mock's response changes based on prior calls — but the library itself has no Daakia-specific code baked in.


Table of contents


Features

  • Visual canvas — React Flow based, ck8t card visual style, with triggers, states, conditions, functions, and terminals as draggable blocks
  • Consumer-provided persistence — the library never talks to a database directly; implement SMConsumerBase once and every save/load/delete call is routed through your host app's own storage
  • React-free execution engine@salilvnair/state-machine/engine is a separate entry point with zero React/canvas imports, safe to run in a plain Node.js process (e.g. inside a mock server) to evaluate transitions against real events
  • Workflows panel + Inspector + Execution panel — the full authoring experience (StateMachineWorkspace) ships as one embeddable component
  • Built-in block library — trigger, state, condition, function, terminal — each with its own visual definition, tree-shakeable via named exports
  • REST/SOAP starter examplesREST_EXAMPLE / SOAP_EXAMPLE configs to seed a new workspace or a demo

Install

npm install @salilvnair/state-machine @salilvnair/dui @xyflow/react react react-dom zustand

@salilvnair/dui, @xyflow/react, react, react-dom, and zustand are peer dependencies — install the versions your app already uses; this avoids shipping a second copy of React (or a second DUI theme context) inside the library bundle.

Import the three stylesheets once, in your app's entry point (or wherever you mount the workspace):

import '@salilvnair/state-machine/src/style/tokens.css'
import '@salilvnair/state-machine/src/style/ck8t-blocks.css'
import '@salilvnair/state-machine/src/style/canvas.css'

Quick start

import { StateMachineWorkspace, useSMWorkspaceStore } from '@salilvnair/state-machine'
import '@salilvnair/state-machine/src/style/tokens.css'
import '@salilvnair/state-machine/src/style/ck8t-blocks.css'
import '@salilvnair/state-machine/src/style/canvas.css'

// Once, at app startup — hydrates the workspace from your storage and wires
// every future save/delete call back to it. See SMConsumerBase below.
await useSMWorkspaceStore.getState().registerConsumer(new MySMConsumer())

export function WorkflowsPage() {
  return (
    <StateMachineWorkspace
      onCopyWorkflowId={(machine) => navigator.clipboard.writeText(machine.id)}
      onConnectWorkflow={(machine) => openConnectDialog(machine)}
    />
  )
}

That's the whole embeddable experience — canvas, side nav, workflows list, inspector, and execution/debug panel all come with it.

Bringing your own persistence — SMConsumerBase

The library never imports a database, fetch, or postMessage — you tell it how to load and save by extending SMConsumerBase once:

import { SMConsumerBase } from '@salilvnair/state-machine'

class MySMConsumer extends SMConsumerBase {
  async onLoadWorkspace() {
    const [machines, folders, todos] = await Promise.all([
      db.query('SELECT * FROM sm_machines'),
      db.query('SELECT * FROM sm_folders'),
      db.query('SELECT * FROM sm_todos'),
    ])
    return { machines, folders, todos }
  }

  async onSaveMachine(machine) {
    await db.upsert('sm_machines', machine)
  }

  async onDeleteMachine(id) {
    await db.delete('sm_machines', { id })
  }

  async onManualSave(machine) {
    // fired by the Save button in the topbar — use for an explicit,
    // user-initiated save distinct from autosave-on-change
  }
}

registerConsumer() calls onLoadWorkspace() immediately to hydrate the Zustand store, then wires every subsequent mutation to your onSave*/ onDelete* methods — the canvas, workflows panel, and todo list all read from and write through the same store.

The engine, without React

@salilvnair/state-machine/engine is a separate build entry with no React, @xyflow/react, or DUI in its import chain — safe to import from a plain Node.js process (a mock server, a CLI, a test runner) to evaluate a saved workflow's transitions against real events:

import { StateMachineEngine } from '@salilvnair/state-machine/engine'

const engine = new StateMachineEngine(workflowConfig)
const result = engine.send('ORDER_PLACED', { orderId: '123' })
// result.state, result.trace, ...

This is what lets a stateful mock server evaluate a workflow's current state on every incoming request without pulling in a browser-only dependency graph.

Public API

Everything below is exported from the package root (@salilvnair/state-machine) unless noted otherwise.

| Export | What it is | |---|---| | StateMachineWorkspace | The full embeddable canvas + side nav + inspector + execution panel | | WorkflowsPanel, SideNav | Individual pieces of the workspace, for a custom layout | | StateMachineCanvas | Just the React Flow canvas, no chrome | | SMConsumerBase, ISMConsumer | The persistence contract — extend/implement to wire your storage | | StateMachineEngine | The transition-evaluation engine (also available React-free via /engine) | | useSMStore, useSMWorkspaceStore, useSMTabsStore, useSMTodoStore | The Zustand stores backing the canvas, workspace, open tabs, and todo list | | BlockRegistry | Registry of block type → visual definition, for adding custom block types | | triggerDefinition, stateDefinition, conditionDefinition, functionDefinition, terminalDefinition | The five built-in block definitions, individually importable for tree-shaking | | configToGraph | Converts a plain StateMachineConfig into React Flow nodes/edges | | REST_EXAMPLE, SOAP_EXAMPLE | Starter workflow configs |

Plus the full type surface: StateMachineConfig, StateDefinition, TransitionDefinition, GuardFn, ActionFn, SMEvent, SMNodeData, SMNodeType, SMNodeDefinition, SMBlockManifest, SMPortDefinition, ExecutionStatus, TraceEntry, SMachine, SMachineFolder, SMWorkspaceCallbacks, SMTab, SMTodoItem.

Styling

Three stylesheets ship from src/style/ rather than a single bundled CSS file, so you can see exactly what each layer does:

| File | Contents | |---|---| | tokens.css | Design tokens + ck8t CSS-variable aliases | | ck8t-blocks.css | The ck8t bs-* visual system the block cards use | | canvas.css | Canvas, node, and edge layout styles |

Import all three — they're small and load once.

TypeScript

The package ships hand-generated .d.ts declarations built alongside the JS output (vite-plugin-dts, rollupTypes: false) — every exported symbol keeps its own declaration file rather than being flattened into one giant type, so "go to definition" in your editor lands on the real source-shaped file.

Local development

git clone https://github.com/salilvnair/state-machine.git
cd state-machine
npm install
npm run dev             # standalone demo app at localhost:5173
npm run build            # builds the standalone demo app
npm run build:lib        # builds the publishable library into dist/lib
npm run typecheck        # tsc --noEmit

Publishing to npm

This section is for library maintainers.

Prerequisites

  1. Node.js 18+ and npm 9+
  2. Publish access to the @salilvnair scope: npm login

Step 1 — Bump the version

npm version patch   # bug fix:      1.0.0 -> 1.0.1
npm version minor   # new feature:  1.0.0 -> 1.1.0
npm version major   # breaking:     1.0.0 -> 2.0.0

Step 2 — Build

prepublishOnly runs this automatically on npm publish, but build first to inspect the output:

npm run build:lib

Verify dist/lib contains (at minimum): index.mjs, index.d.ts, engine.mjs, engine/index.d.ts, state-machine.css.

Step 3 — Verify what will be published

npm pack --dry-run

Only dist/, src/style/, README.md, LICENSE, and package.json should appear — no src/canvas, src/store, etc. (those live in dist/lib as compiled output + declarations instead).

Step 4 — Test the tarball locally

npm pack
# creates salilvnair-state-machine-X.Y.Z.tgz

# in a consumer app:
npm install /path/to/salilvnair-state-machine-X.Y.Z.tgz

Step 5 — Publish

Scoped packages default to private, so --access public is required — though publishConfig.access: "public" in package.json already sets this as the default, so a plain npm publish is enough:

npm publish
# rehearsal first:
npm publish --dry-run

Step 6 — Push tags

git push && git push --tags

License

MIT © Salil V Nair — see LICENSE.