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

@ushiboy/cyclone

v0.2.0

Published

Application state management library

Downloads

3

Readme

Cyclone

Build Status

Cyclone is an application state management library.

Quick Sample

Here is a simple counter.

import { createStore, none } from '@ushiboy/cyclone';

const initialState = { count: 0 };

const update = (state, action) => {
  switch (action.type) {
    case 'increment': {
      return [{ count: state.count + 1 }, none()];
    }
    case 'decrement': {
      return [{ count: state.count - 1 }, none()];
    }
    default: {
      return [state, none()];
    }
  }
}

const store = createStore(initialState, update);

store.subscribe(() => {
  console.log(store.getState());
});

store.dispatch({ type: 'increment' });
store.dispatch({ type: 'increment' });
store.dispatch({ type: 'decrement' });

Main Concept

  • State: the state of your application.
  • Action: the instructions for changing state.
  • Update: the processing function that receives State and Action and returns the next State and Action.
  • Store: it is initialized with State and Update, receives Action and updates State and notifies.

State

The objects with keys and values.

type State = {
  [stateName: string]: any;
};

Action

The objects with type property.

type Action = {
  type: string
};

Update

The function that takes State and Action as arguments and returns updated State and next Action.

type Update<S, A> = (state: S, action: A) => [S, A | Promise<A> | Array<A | Prmise<A>>];

Store

Notify of change of State by Action.

type Store<S, A> = {
  dispatch(action: A | Promise<A>): Promise<void>,
  getState(): S,
  subscribe(listener: () => void): () => void,
  unsubscribe(listener: () => void): void
};

API

createStore

It create and return a Store from the initial state and Update function.

createStore<S, A>(initialState: S, update: Update<S, A>): Store<S, A>

reducer

It define Update which is responsible for specific elements in State. It is used in combination with combine.

reducer<RS, A>(stateName: string, update: ReducerUpdate<RS, A>): ReducerConfig<RS, A>
reducer<RS, A>(stateName: string, dependencies: string[], update: ReducerUpdate<RS, A>): ReducerConfig<RS, A>

It returns a ReducerConfig object. If you specify a list of depending State element names on the 2nd argument, it can be received after the 3rd argument of the ReducerUpdate function.

type ReducerConfig<RS, A> = {
  stateName: string,
  update: ReducerUpdate<RS, A>,
  dependencies: string[]
};

type ReducerUpdate<RS, A> = (state: RS, action: A, ...dependencyState: any[]) => [RS, A | Promise<A>];

combine

It receives multiple ReducerConfig objects, creates and returns a single Update function.

combine<S, A>(...reducerConfig: ReducerConfig): Update<S, A>;

none

It is used when there is no next Action after Update processing.

none(): Action

Store

Store instance methods.

dispatch

It execute Action against Store.

dispatch(a: A | Promise<A>): Promise<void>

getState

It returns the current State of Store.

getState(): S

subscribe

It subscribes to the Store change notification and returns unsubscriber.

If you execute unsubscriber, It unsubscribes to the Store change notification.

subscribe(listener: () => void): () => void

unsubscribe

It unsubscribes to the Store change notification.

unsubscribe(listener: () => void): void

Usage

Reduce State Case

An example where three states are processed individually and one depends on the other two.

import { createStore, combine, reducer, none } from '@ushiboy/cyclone';

const store = createStore({ a: 0, b: 0, c: '' }, combine(
  reducer('a', (state, action) => {
    switch (action.type) {
      case 'a$set': {
        return [action.payload, none()];
      }
      default: {
        return [state, none()];
      }
    }
  }),
  reducer('b', (state, action) => {
    switch (action.type) {
      case 'b$set': {
        return [action.payload, none()];
      }
      default: {
        return [state, none()];
      }
    }
  }),
  // depends on 'a' and 'b'
  reducer('c', ['a', 'b'], (state, action, a, b) => {
    switch (action.type) {
      case 'sum': {
        return [`${a + b}`, none()];
      }
      default: {
        return [state, none()];
      }
    }
  })
));

store.subscribe(() => {
  console.log(store.getState());
});

store.dispatch({ type: 'a$set', payload: 1 }); // { a: 1, b: 0, c: '' }
store.dispatch({ type: 'b$set', payload: 2 }); // { a: 1, b: 2, c: '' }
store.dispatch({ type: 'sum' });               // { a: 1, b: 2, c: '3' }

Action Chain Case

An example of waiting for 1 second and erasing the message after displaying the message.

import { createStore, none } from '@ushiboy/cyclone';

const showMessage = () => ({ type: 'show' });

const waitAndClearMessage = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ type: 'clear' });
    }, 1000);
  });
};

const store = createStore({ msg: '' }, (state, action) => {
  switch (action.type) {
    case 'show': {
      return [{ msg: 'hello' }, waitAndClearMessage()]; // chain action
    }
    case 'clear': {
      return [{ msg: '' }, none()];
    }
    default: {
      return [state, none()];
    }
  }
});

store.subscribe(() => {
  console.log(store.getState());
});

store.dispatch(showMessage());

Advanced Usage

Extra Argument

(This feature is experimental. It may change in the future.)

If there is a parameter you want to inject into the action, set it to the 3rd argument of the createStore method.

const store = createStore(initialState, update, { webApi: {...} });

The action can use it by returning a function that receives the injected parameter.

const fetchAction = () => async ({ webApi }) => {
  const data = await webApi.fetch();
  return {
    type: 'fetch',
    payload: {
      data
    }
  };
};

store.dispatch(fetchAction());

Change Log

0.2.0

Changed subscribe method to return unsubscriber.

0.1.1

Removed @babel/polyfill dependency.

0.1.0

Initial Cyclone release.

License

MIT