Skip to content

qiwi/cyclone

Repository files navigation

@qiwi/cyclone

CI Maintainability Test Coverage

"State machine" for basic purposes.

Motivation

There're many redux-state-machine implementations. krasimir/stent is pretty good among others (just opinion). But:

  • Stent does not allow to "lock" the execution thread. Therefore impossible to verify that next step strictly follows (corresponds) by the prev.
  • Has no standard mechanics for state rollback.

If these points are not significant for you, Stent might be your best choice.

Features

  • History-like api
  • Lock mechanics
  • Multi-step transition declarations

Typings

  • Typescript typings/index.d.ts
  • Flowtype libdef flow-typed/index.flow.js should be found by Flow. If not, add [lib] section to .flowconfig

API

import {Machine} from '@qiwi/cyclone'

const handler1 = () => {}
const handler2 = () => {}
const opts = {
  initialState: 'foo',
  initialData: {a: 'AAA'},
  transitions: {
    'foo>bar': true,  // NOTE applies static DEFAULT_HANDLER
    'bar>baz': handler1,
    'baz>foo': handler2,
    'foo>bar>baz>foo': handler1
  },
  historySize: 5,     // default = 10
}
const machine = new Machine(opts)

Proto

current

Returns machine state digest:

    machine.current()   // {state: 'foo', data: {a: 'AAA'}, id: '0.2234...', date: 2018-10-07T16:59:23.644Z}
next

Transits the machine to a new state:

    machine.next('bar', {optional: 'args'}, 'for', 'handler')
    machine.current()   // {state: 'bar', data: {...}, ...}
prev

Reverts the last transition:

    machine.current()   // {state: 'bar', data: {...}, ...}
    machine.prev()      // btw, it returns machine ref
    machine.current()   // {state: 'foo', data: {...}, ...}
lock / unlock

Prevents state update.

    machine.lock('key')
    machine.next('qux', {a: 'a'})   // MachineError: Lock violation
    machine.unlock('invalidKey')    // MachineError: Invalid unlock key
    machine.unlock('key')

Static

DEFAULT_HANDLER
DEFAULT_HANDLER('foo', 'bar')        // 'bar'
DEFAULT_HANDLER('foo', 'bar', 'baz') // 'baz'

Usage examples

Imagine, Rematch model:

    import txn from '../../../../api/txn'
    import Machine from '@qiwi/cyclone'
    
    const machine = new Machine({
      initialState: 'init',
      initialData: {},
      transitions: {
        'init>loading': true,
        'loading>ok': (state, res) => res,
        'loading>err': (state, res) => res,
        'ok>loading': true,
        'err>loading': true
      }
    })
    
    export default {
      state: machine.current(),
      reducers: {
        next(prev, next, ...payload) {
          return machine.next(next, ...payload).current()
        }
      },
      effects: {
        async read (opts) {
          this.next('loading')
          const res = await txn.readList(opts)
          this.next('ok', res)
        }
      }
    }

Alternatives

License

MIT