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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@rotorsoft/flow

v1.1.2

Published

A minimalistic state machine

Downloads

24

Readme

flow

A minimalistic functional state machine

This is an attempt to build the simplest functional state machine without any external dependencies. Reviewers who can contribute to make it simpler or more efficient without compromising funcionality are welcome.

Borrowing from functional programming, coroutines, and generator functions; this "coded by convention" loop sets the foundation to compose more complex applications with just three basic constructs:

  • Actions - Pure functions with logic to drive internal transitions

  • Reducer - Pure function with logic to reduce the state from internal and external transition payloads

  • Flow - A closure with the coroutine implementing the loop and holding the state

Conventions

  • Anonymous actions yield control back to the caller (User). Think of it as a one-time yielding generator function.

  • Actions can optionally return:

    • An Object - action payload
    • An Action - (named or anonymous) to allow composition
    • An Array of the above - to be executed in order

The loop

The flow is initialized with a map of actions, optional parameters, the reducer, and optional callbacks (invoked, shifted) for tracing and debugging actions. The injected action map allows composition without coupling modules.

The returned coroutine must be started with a root action and can be successively invoked with transition payloads. It always returns its internal structure including the current state and scope.

Actions are internally invoked with 3 arguments: (state, scope, { params, actions }).

The flow keeps track of action recursion, indentation, and stack depth levels in the scope object.

Schema

flow = {
  state: {...} // current reduced state
  scope: { // current scope
    name: 'string', // action name
    recur: 'int', // recurrence counter
    parent: {...}, // parent scope
    level: 'int', // indentation level
    depth: 'int' // stack depth
  },
  yielding: 'function' // yielding action
  stack: [] // current stack
  done: 'bool', // true when end of stack reached
}

How to use

const flow = require('@rotorsoft/flow')

function action1(state, scope, { params }) {
  return [
    { ask: `Am I speaking with ${params.name}?` },
    (state, { recur }) => {
      if (state.action1.answer === 'yes') return
      if (recur < 2) return action1
    }
  ]
}

const root = (state, scope, { params, actions }) => {
  return [
    actions.action1,
    function root(state) {
      if (state.action1.answer === 'yes') return { say: `Hello ${params.name}. How are you today?` }
      return { say: "I'm sorry for the inconvenience." }
    }
  ]
}

const next = flow({
  params: { name: 'John Doe' },
  actions: { action1 },
  reducer: (state, scope, payload) => ({ ...state, ...payload })
})

let $ = next(root) // start root action
$ = next({ action1: { answer: 'what?' } }) // update action1 state
$ = next({ action1: { answer: 'yes' } }) // update action1 state
console.log($)

Test

npm test

The provided tests are self explanatory and should log a trace like this:

  simple test
[ 0] root() { // [authenticate(state,scope,{params}), next({authenticate,verifyPhone,canComeToThePhone})]
[ 2]    authenticate() { // [{"ask":"Am I speakin...}, (state,{recur})]
[ 2]       {"ask":"Am I speaking with John Doe?"}
[ 2]       (state,{recur}) ... {"authenticate":{"answer":"whatever"}}
[ 3]       authenticate:1() { // [{"ask":"Am I speakin...}, (state,{recur})]
[ 3]          {"ask":"Am I speaking with John Doe?"}
[ 3]          (state,{recur}) ... {"authenticate":{"answer":"yes"}}
[ 3]       } // authenticate:1
[ 2]    } // authenticate
[ 1]    next() {
[ 1]       {"say":"Hello John Doe. How are you today?","authenticated":true}
[ 1]    } // next
[ 0] } // root

===
{
  state: {
    ask: 'Am I speaking with John Doe?',
    authenticate: { answer: 'yes' },
    say: 'Hello John Doe. How are you today?',
    authenticated: true
  },
  scope: {},
  stack: [],
  done: true
}
===

    √ should authenticate

Leonardo da Vinci


Contributing

In lieu of a formal style guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code.

License

MIT