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

rstore

v0.4.5

Published

a reactive state manager

Downloads

67

Readme

rstore

Join the chat at https://gitter.im/nikitadyumin/rstore Build Status Coverage Status Downloads This Month Downloads Total

a straightforward, explicit, declarative and composable reactive store/model

Introduction

RStore is an observable (reactive) model.

Reactive?

| Active | Reactive | |---|---| | direct query at some point in time | a subscription to all updates | | console.log(state.getValue()) | state.subscribe(value => console.log(value)) |

In the reactive world of observables you don't have to think if something has happened by the time something else happens. So you can concentrate on a single thing - defining a way the changes combine and map to the result.

What are the benefits?

  • a state is defined in one place
store(0)
  • every observer immediately receives a notification on a state update (including the initial state)
store(0)
  .subscribe(value => console.log(value))
  • the state can only be updated by other observables, so it changes whenever source stream produce updates
  • the way updates modify the state is defined in terms of (widely adopted redux) reducers (see the interoperability section)
store(0)
  .plug(Rx.Observable.of(1), (x, y) => x + y)
  .subscribe(value => console.log(value))
  • observables (sources of updates) can be defined in many ways
    • RxJS 5
    • Bacon.js
    • Most.js
    • built-in factories:
      • fromEvent(element: HTMLElement, EventName: String) => Observable of events
      • interval(n: number, ...values) => Observable that emits values every n milliseconds
      • address() => Observable that acts like an Event Bus (see counters example)
    • custom observer that implements observable interface
  • RStore stores can be combined
const s1 = store(1);
const s2 = store(2);
s1.plug(s2, (x, y) => x + y)
  .subscribe(value => console.log(value)); // 3
  • RStore stores can be converted to RxJS Observables
store(1)
  .toRx()
  .map(x => x* 2)
  .subscribe(state => console.log(state));

Example:

1 ) describe a model (properties, initial state):

const {store} = require('rstore'); 
const myStore = store(0);

2 ) define sources of changes (using fromEvent helper or Rx, Bacon, Most.js streams, see defining inputs)

const inc$ = fromEvent(document.getElementById('inc'), 'click');
const dec$ = fromEvent(document.getElementById('dec'), 'click');

3 ) define how these changes affect the model:

myStore
    .plug(inc$, (state, _update) => state + 1)
    .plug(dec$, (state, _update) => state - 1);

4 ) subscribe to the store and get an updated model on every change:

myStore.subscribe(model => console.log(model));

See more examples

API

Creating stores

As RStore supports multiple kinds of observables (APIs), it exports 5 store constructors:

| constructor | description | |---|---| | store | general, with an autodetect of the observable API | | storeR | RStore observables | | storeBacon | Bacon.js | | storeMost | Most.js | | storeRx | RxJS5 |

A constructor is a function that expects a single argument - an initial model value: It could be a primitive value

store(0);

or an object

store({
    todos: []
});

RStore methods

| method | description | |---|---| | .plug(oObservable, fnReducer) | subscribes RStore to the oObservable and for every value calls the fnReducer | | .unplug(oObservable, fnReducer) | given oObservable and optionally fnReducer unsubscribes RStore from an external observer | | .subscribe(fnObserver) | subscribes to RStore - calls fnObserver for every state update, including an initial value | | .reset() | unsubscribes everything| | .toRx(Rx) | converts a store to an Rx Observable |

Interoperability

###RxJS5 Observables are sources of changes

store({
    c1 : 0,
    c2 : 0
}).plug(
    Rx.Observable.of(1), (s, u) => Object.assign({}, s, {c1: u}),
    Rx.Observable.of(2), (s, u) => Object.assign({}, s, {c2: u})
).subscribe(model => console.log(model.c1 + model.c2));

A store can be converted to an observable

store(2)
  .toRx(Rx) // Rx object, optional if Rx is global
  .map(x => x * 2)
  .subscribe(v => console.log(v)); // 4

###Redux Reducers can be reused

const observableOfChanges = Rx.Observable.from([
      {type: 'INCREMENT'},
      {type: 'INCREMENT'},
      {type: 'DECREMENT'},
      {type: 'INCREMENT'},
]);

const reduxReducer = (state, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return state + 1
        case 'DECREMENT':
          return state - 1
        default:
          return state;
      }
};

store(0)
  .plug(observableOfChanges, reduxReducer)
  .subscribe(state => console.log(state)); // 2

Redux store as a source of updates


const changes = [
    {type: 'INCREMENT'},
    {type: 'INCREMENT'},
    {type: 'DECREMENT'},
    {type: 'INCREMENT'}
];

function reduxReducer (state = 0, action) {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

const reduxStore = Redux.createStore(reduxReducer);

changes.forEach(m => reduxStore.dispatch(m));

rstore.store(0)
    .plug(reduxStore[Symbol.observable](), (s, u) => u)
    .subscribe(state => console.log(state)); // 4

changes.forEach(m => reduxStore.dispatch(m));

Composability

As stores are Observables, they can be used as sources of changes:

// store 1 (counter from the previous example)
const counter1 = store(0);
const inc1$ = fromEvent(document.getElementById('inc1'), 'click');
const dec1$ = fromEvent(document.getElementById('dec1'), 'click');
counter1
    .plug(inc1$, (state, _update) => state + 1)
    .plug(dec1$, (state, _update) => state - 1);

counter1.subscribe(model => document.getElementById('res1').textContent = model);

// store 2 (a second counter, similar to counter 1)
const counter2 = store(0);
const inc2$ = fromEvent(document.getElementById('inc2'), 'click');
const dec2$ = fromEvent(document.getElementById('dec2'), 'click');
counter2
    .plug(inc2$, (state, _update) => state + 1)
    .plug(dec2$, (state, _update) => state - 1);

counter2.subscribe(model => document.getElementById('res2').textContent = model);

// a combining store, stores current states from both counters 
// notifies subscribers if any of the counter values changes 
store({
    c1 : 0,
    c2 : 0
}).plug(
    counter1, (s, u) => Object.assign({}, s, {c1: u}),
    counter2, (s, u) => Object.assign({}, s, {c2: u})
).subscribe(model => document.getElementById('total').textContent = model.c1 + model.c2);

more on composability

Redux DevTools Extension

STK works with Redux Devtools Extension:

  • Install the tools
  • Connect one of the stores to the DevTools:
const initial = 0;
const store = rstore.store(initial);
rstore.devtools.addStore(store, initial);

easy access to model fields

In many cases model modification functions are just setters that take the current state and a new value as inputs and produce a new model. It might be hard to access\update deeply nested fields as it is desirable that functions stay pure and data stays immutable.

rstore has a lens function that is a pair of a setter and a getter for the given field:

const lens = require('rstore').lens;
const l = lens('a');
const o1 = {a: 11};
const o2 = {a: 22};

// returns a value of the field 'a' in the object 'o1' (11)
console.log( l.get(o1) ) 
// returns a value of the field 'a' in the object 'o2' (22)
console.log( l.get(o2) ) 

// returns a new object like 'o1', but the field 'a' is set to '33' 
// ({a: 33}), object 'o1' stays the same
console.log( l.set(o1, 33) ) 
// returns a new object like 'o2', but the field 'a' is set to '44' 
// ({a: 44}), object 'o2' stays the same
console.log( l.set(o2, 44) ) 

more on lenses