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

@jarenjs/flow

v0.56.0

Published

Executable workflow documents for the Jaren suite: the jaren-fsm finite-state-machine format compiled to a pure step function, with query-document guards and effect descriptors as data

Readme

@jarenjs/flow

Executable workflow documents, in two formats. The jaren-fsm format is a finite state machine as one JSON value — declared states, an initial state, and a document-ordered transition table whose guards and effect props are Jaren JSON Query documents — compiled, once, into a pure step function. The jaren-dag format is an acyclic dataflow whose nodes are the suite's own engines — query documents, JSLT stylesheets, registered async tasks — wired by edges that carry data and compiled into a run-to-completion executor.

It is the executable half of a round trip the suite already ships: a stateDiagram-v2 parsed by @jarenjs/mermaid projects (via a JSLT stylesheet) into exactly this shape, and this engine runs it. The format is a strict superset of that projection contract — every projected document compiles unchanged.

The grammar is published as JSON Schema in schemas/jaren-fsm.schema.json (with a mechanically derived draft-07 twin for providers pinned to older drafts) — hand it to a constrained decoder and a language model cannot emit a machine with an unknown member or a malformed guard. The normative contract is docs/FLOW-FORMAT.md. Zero dependencies outside the suite; no eval, CSP-safe; the only runtime import is @jarenjs/json.

The format in one glance

{
  "$fsm": "0.1",
  "initial": "idle",
  "states": [
    "idle",
    { "id": "loading", "entry": [{ "run": "fetch", "with": { "url": "$.context.url" } }] },
    { "id": "done", "final": true }
  ],
  "transitions": [
    { "from": "idle", "event": "start", "to": "loading" },
    { "from": "loading", "event": "ok", "guard": "$.payload.fresh", "to": "done" },
    { "from": "loading", "event": "fail", "to": "idle" }
  ]
}

Guards are asserted by effective boolean value against one scope — { state, event, payload, context } — and effects are data: a fired transition returns resolved { run, with? } descriptors (exit → transition → entry); the engine never executes them. Which registry runs them is the host's business, the same boundary discipline as @jarenjs/app effects.

Usage

import { compileFsm, createFsmSession } from '@jarenjs/flow';

const fsm = compileFsm(doc);        // throws FlowCompileError (JF0xxx) on a bad document
fsm.initial;                        // 'idle'
fsm.states;                         // ['idle', 'loading', 'done'] (frozen)
fsm.events('loading');              // ['ok', 'fail'] (frozen)

// The pure core: state in, result out. Nothing is held, nothing runs.
const r = fsm.step('loading', 'ok', { payload: { fresh: true }, context: { url: '/api' } });
r; // { changed: true, state: 'done', effects: [], final: true, errors: [] }

// The thin mutable convenience over it:
const s = createFsmSession(fsm);    // starts at doc initial
s.can('start');                     // true — a dry run (step is pure, so can() IS step())
s.send('start');                    // advances; returns the same result shape
s.state;                            // 'loading'
s.done;                             // final-state flag

step is total over machine input: an event no transition answers is ignored (changed: false, no effects) — the conventional FSM reading. Only caller mistakes throw: an undeclared state id or a non-string event (FlowRuntimeError, JF2001/JF2002).

Fail closed, report precisely

A guard that throws at evaluation reads false; an effect whose with throws is omitted. Neither aborts the step — each appends a plain-data record { code, docPath, message } to the result's errors array, where docPath is a JSON Pointer to exactly the query that failed (/transitions/2/guard). That is the suite's standard machine-repairable error shape: a host surfaces the records through its own error channel, and a repair loop knows precisely what to fix.

Compile-time failures throw FlowCompileError with the same code + docPath discipline (JF0001–JF0009, table in FLOW-FORMAT.md §5); a guard that cannot compile is a compile error, never a runtime surprise.

Hosting a machine in @jarenjs/app

fsmToApp turns the same document into generated standard app documents — a state slice plus one plain-JSON action per named event, compiled by the app like any hand-written action, with no flow code at runtime:

import { fsmToApp, fsmStateSchema } from '@jarenjs/flow';
import { createApp } from '@jarenjs/app';

const { slice, actions, events } = fsmToApp(doc);   // { pointer: '/fsm', namespace: 'fsm/' }
const app = createApp({
  state: { fsm: slice },
  view,
  actions: { ...actions, ...hostActions },
}, { node, effects, validateState });               // fsmStateSchema(doc) guards the slice

Control state lives at <pointer>/current; the host's app state is the machine's context; guards and effect props evaluate pre-transition in both worlds, and target states are baked as literals. Selection semantics are provably the headless engine's — the test suite drives both through the same scripts and asserts state and effects agree. The convention, the scope mapping table, multi-machine layout and the honestly-stated divergences (a throwing guard fails the hosted transaction instead of reading false) live in docs/APP-INTEGRATION.md.

The dataflow half — jaren-dag

"Connect different parts together" as one schema-validated JSON value: nodes from a closed vocabulary (input, output, const, query, jslt, task) wired by edges, run to completion for one input. The suite's engines compose as data — a query filters, a stylesheet projects, and the result can be a vnode tree without this package ever importing a rendering line:

import { compileDag } from '@jarenjs/flow';

const dag = compileDag({
  $dag: '0.1',
  nodes: {
    rows:   { kind: 'input' },
    adults: { kind: 'query',
      query: { $for: { r: '$[*]' }, $where: { $ge: ['$r.age', 18] }, $return: '$r' } },
    view:   { kind: 'jslt',
      stylesheet: [{ match: '$', body: ['ul', {},
        [{ $for: { p: '$[*]' }, $return: ['li', {}, '$p.name'] }]] }] },
    out:    { kind: 'output' },
  },
  edges: [
    { from: 'rows', to: 'adults' },
    { from: 'adults', to: 'view' },
    { from: 'view', to: 'out' },
  ],
}, { tasks: {} });

await dag.run(people);   // ['ul', {}, [['li', {}, 'ada'], …]] — a vnode, as JSON

Cycles, port rules and the exactly-one-output rule are compile-time rejections (JF0xxx with docPath); a task node's handler is resolved at compile too, and called as handler({ with, input }, signal) with the run's shared AbortSignal. Independent branches run concurrently, but determinism is same input → same output values — results are keyed and port objects assemble in edge document order, so completion timing can never change a value. Failure is fail-closed: the first failing node aborts the shared signal and rejects the whole run (JF2006 with nodeId, docPath, cause; a caller abort is JF2007) — no retries, no partial results. Per-node observability is the onNode record stream ({ id, status, ms }), and a compiled dag hosts in an app as one effect through createTaskEffect — the recipe is in docs/APP-INTEGRATION.md, the contract in docs/FLOW-FORMAT.md §6–§7.

One scope, one honest quirk

$.state, $.event, $.payload, $.context — control state lives in the machine, data state lives in the host, passed per call. A guard written as a plain string that does not start with $ is a literal (query semantics), and a non-empty literal is EBV-true: a display guard carried over from a diagram label (count > 3) is vacuously true by design, because a picture's annotation must not change execution. Real conditions are $-paths ("$.payload.fresh") or operator documents ({ "$gt": ["$.context.count", 3] }).

Performance (measured)

npm run benchmark:flow (run it yourself — the FSM head-to-head needs the xstate benchmark devDependency, and the memory row needs node --expose-gc, which the script passes). The same logical machine is built with @jarenjs/flow and XState v5 and asserted to agree before timing:

  • Transitions — the pure step runs several times faster than an XState actor's send (≈5.6–8.1× across 5/50/500-state machines); the createFsmSession wrapper is on the page too.
  • CompilecompileFsm beats createMachine + createActor ≈1.5–2.6×. Not like-for-like: XState builds a scheduling actor, so the row is each engine's description→drivable cost.
  • The wedge — a conformance fact, not a timing: a jaren-fsm document is JSON including its guards, so it survives JSON.stringifyJSON.parse and still compiles and still fires its guard. XState's guards are functions JSON drops, so the round-tripped machine throws "Guard not implemented" at the guarded transition. Serialize, store, diff, ship, replay — that is the whole reason to speak JSON all the way down.
  • Where we lose — a compiled Jaren machine holds more memory than the XState actor (every guard compiles to its own query closure); the benchmark publishes the KiB-per-machine loss beside the wins.
  • The dag tax — no npm library executes schema-validated JSON dataflow, so the honest rival is the same pipeline hand-written in JavaScript. A compileDag run costs ~8–20× the hand-written baseline — the published price of dataflow as one serializable, constrained-decodable JSON value, sitting beside what it buys.

The full tables, the wedge as a conformance row, and the fairness notes are on the benchmarks page.

Authoring with a model

A jaren-fsm or jaren-dag document is JSON published as a schema, so a constrained decoder can author one — and because every compile error carries a code and a docPath, a schema-valid but semantically broken machine (a transition to an undeclared state, say) repairs in a bounded loop rather than flailing. @jarenjs/ai's Authoring engine documents section shows the composeChecks(schema, compileGate) recipe, and A model as a dataflow node runs a model as an ordinary dag task. Neither package imports the other — the composition is data.

Authoring by code

The same documents have a by-code twin. @jarenjs/linq/flow is the suite's pen for both formats: state ids, event names and node ids are literal types, so a transition into an undeclared state or an edge from an undeclared node is a compile error, and guards, effect props, node queries and edge selectors are callbacks captured over the scope the engine evaluates them in — never a path typed as a string.

import { defineFsm, on, state, effect } from '@jarenjs/linq/flow';

const doc = defineFsm({
  initial: 'idle',
  states: [
    'idle',
    state('loading', { entry: [effect('fetch', (s) => ({ url: s.context.url }))] }),
    state('done', { final: true }),
  ],
  transitions: [
    on('idle', 'start').to('loading'),
    on('loading', 'ok').when((s) => s.payload.fresh).to('done'),
    on('loading', 'fail').to('idle'),
  ],
});                                // the document at the top of this README, byte for byte

compileFsm(doc).step('idle', 'start', { context: { url: '/rows' } });

A model-authored document and a pen-written one go through the SAME gate — compileFsm/compileDag. The mapping table, and where the pen's refusals end and the compiler's begin, are FLOW-PEN.md.

Development

Unit tests live in test/flow/ at the repository root (npm run test:flow). See the repository README for the full suite documentation.