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

protonic

v0.3.0-alpha

Published

A minimal library for managing application state.

Readme

PROTONIC

It's like other things, only different.

Overview

Protonic is a (very) simple (framework agnostic) library for managing state through streams. It's inspired by other popular state-management patterns and libraries (notably Redux, Flux, and RxJS), but makes certain assumptions and trade-offs in support of reducing complexity and developing applications following a specific pattern. By itself, Protonic isn't much of a framework, but is designed to work with a simple one-way data flow pattern that we'll describe here. (This pattern isn't anything new, but for the sake of this document and this particular flavor of the pattern we'll call it the Protonic Pattern.)

Getting Started (Quickly)

To use Protonic in your app, install it with npm:

npm install protonic --save

import Immutable from 'immutable';
import { Stream } from 'protonic';

// To create a stream:
let initialState = Immutable.Map({ name: 'Jessika Pava' })
let myStream = new Stream(initialState);


// To subscribe to a stream:
let subscriber = myStream.subscribe(state => console.log(state.get('name')));
// immediately logs "Jessika Pava"

// To send state to a stream:
myStream.state = myStream.state.set('name', '"Snap" Wexley');
// logs '"Snap" Wexley'

// To unsubscribe from a stream:
subscriber.unsubscribe();

myStream.state = myStream.state.set('name', 'Karé Kun');
// nothing logs.

Manual

Overview

Getting Started

One-Way Data Flow

Example Components in Angular and React

Other Streams: Funnels and Views

Source Documentation

Actions and Transformers

Transformers

Transformers are solely responsible for manipulating state for a stream. They are not pure reducers, however. Transformers don't take state as an input. Rather, they fetch the current state from the stream directly. They also do not return new state. Rather, they send the new state back to the stream. Regardless, transformers should still follow the same rules as pure reducers, specifically that they do not depend on state outside of the stream, and that they do not alter state outside of the stream.

// A typical transformer
function incrementPilotSortie(amount) {
  let currentState = stream.state; // state is an Immutable data structure.
  let sorties = currentState.get('sorties');
  let newSorties = sorties + amount;
  let newState = currentState.set('sorties', newSorties);
  stream.next(newState);
}

This can be simplified to:

// A typical transformer
function incrementPilotSortie(amount) {
  stream.state = stream.state.update('sorties', num => num + amount);
}

Actions

Most of the time, when a user triggers an action, we would expect state in multiple streams to be update. An example would be a section of the UI updating to show a progress bar or spinner when data is fetched from the server. Since transformers are prohibited from affecting state from outside their stream, actions are our way of combining transformers and necessary side-effects (ajax calls, for example).

function getPilots () {
  uiTransformers.setSpinner(true);
  $get('api/to/pilots')
    .then(result => {
      pilotTransformers.updatePilots(result);
      uiTransformers.setSpinner(false);
    },
    error => {
      uiTransformers.setSpinner(false);
      uiTransformers.setError(error);
    });
}

API

Streams

A stream is created by calling new Stream() an optionally passing in an initial state. If you do pass in an initial state, it must be an Immutable data structure.

import { Stream } from 'protonic';

let myStream = new Stream(Immutable.Map({name: 'Testor'}));

You can get the current state of a stream at any time by access the .state property.

let currentState = myStream.state;
// currentState is Immutable.Map of {name: 'Testor'}

You can subscribe to any changes to the stream's state with the .subscribe method. .subscribe takes a callback function (we'll call it an observer) that is called any time the stream's state changes. .subscribe returns an object with an .unsubscribe method. .unsubscribe should be called whenever the observer is done listening to state changes.

let subscriber = myStream.subscribe(state => console.log(state));
// logs out state anytime state changes.

// later on
subscriber.unsubscribe();

You can send new state to the stream with the .next(newState) method. This will send the state to any observers, provided that the new state is not falsey and is distinct from the stream's current state.

myStream.next(Immutable.Map({name: 'Snap'}))
// myStream.state is now Immutable.Map of {name: 'Snap'}

Protonic also allows you to set the state through the .state setter property. It is functionally equivalent to calling .next(newState), but might be more declarative:

myStream.state = Immutable.Map({name: 'Snap'});
// myStream.state is now Immutable.Map of {name: 'Snap'}

Funnels

A funnel is created with new Funnel() and takes a required argument which is a map of streams to combine into one state object. The key for each stream in the map will end up corresponding to the key for that stream's part of the state in the combined state object.

import { Funnel } from 'protonic';
import pilotStream from 'path/to/pilot-stream';
import shipStream from 'path/to/ship-stream';

let AppStream = new Funnel(Immutable.Map({
  Pilots: pilotStream,
  Ships: shipStream
}))

Just like with Streams, you can get the current state of the Funnel with the .state property.

let currentState = AppStream.state;
// currentState is Immutable.Map of
// { Pilot: { name: 'Testor' },
//   Ship: { type: 'X-Wing' } }

Also like Streams, you can subscribe to state changes with the .subscribe method. Note that state will not be sent to observers until all of the source streams have emitted state to the funnel.

let subscriber = AppStream.subscribe(state => console.log(state));
// logs out state anytime state changes.

// later on
subscriber.unsubscribe();

Finally, when you are done with a Funnel, you can call .destroy method to clean up the Funnel and unsubscribe from the source streams.

AppStream.destroy()

Views

A View is created with new View(soureStream, viewFunction), and requires a source stream which it will subscribe to and a view function to change the shape of the state before emitting to its own subscribers.

import { View } from 'protonic';
import AppStream from 'path/to/app-stream';

let myView = new View(AppStream, state => {
  let pilotName = state.getIn(['Pilot', 'name']);
  let shipType = state.getIn(['Ship', 'type']);
  return Immutable.Map({
    description: pilotName + ' flies an ' + shipType
  });
})

As with Streams and Funnels, the current state of a View is accessible by its .state property:

let currentState = myView.state;
// currentState is Immutable.Map of { description: 'Testor flies an X-Wing' }

Also like Streams and Funnels, you can subscribe to state changes from the view. Just like Streams, observers will only receive state if the new state calculated by the View is distinct from the last state calculated by the View.

let subscriber = myView.subscribe(state => console.log(state));
// logs out state anytime state changes.

// later on
subscriber.unsubscribe();

And, like Funnels, when you are done with a View, it can be cleaned up with the .destroy method.

myView.destroy();

Logging and Debugging

TODO Talk about Logging/Debugging with Stacks.

Why Protonic

TODO Talk more about philosophy of Protonic

TODO Talk about gotchas (i.e. Actions cannot subscribe to streams)