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

lightstate

v0.0.10

Published

reactive state container

Downloads

1

Readme

CircleCI

code style: prettier

Notes

Status: Experimental

Part of a lesson on Continuous Integration for AngularFirebase.com

LightState

Why? Because the best code is the code you don't write. Convention over configuration.

What? LightState is a magic state container designed for optimal developer happiness.

  • Not a library, just a drop-in tool
  • Simple data selection and mutation
  • Automatic actions
  • Automatic Observable/Promise management
  • Intercept state changes with middleware
  • Redux Dev Tools support out of the box
  • Only dependency is RxJS

LightState is not opinionated. You can have one global state or multiple smaller states.

Let there be State...

npm i lightstate -s

Quick Start

LightState provides you with one thing - StatefulObject.

// Define it
const state = new StatefulObject({ favorites: null });

// Slice it
const slice = state.at('favorites');

// Read it
slice.get$(); // as Rx Observable
slice.value; // as JS object

// Mutate it
slice.update({ beer: 'La Fin du Monde' });

Make sure to install Redux Dev Tools to watch your state magically manage itself.

Conventional Actions

Lightstate removes the mental boilerplate of redux by generating actions based on predictible conventions.

When you mutate the state, it creates an action based on the following convention (you can override this behavior).

name - optional name of the stateful object action - name of the action path - where the action occured in dot notation

[name] action@path

Synchronous mutations will look this this.

[userState] [email protected]
[userState] CLEAR

Async operations have their own convention. (1) start (2) mutate (3) complete/cancel/err.

[userState] [email protected]
[userState] [email protected]
[userState] [email protected]

Options

You can can pass options as a second argument.

  • name give your container a unique name for action creation (a random name is assigned by default).
  • devTools devtools config or false (enabled by default)
  • logger console log state changes (enabled by default)
  • middleware a function that can intercept state changes
  • [key:string] add your own options, then intercept them with middleware
const opts = {
    name: 'user',
    devTools: false,
    middleware: myMiddlewareFunction
}
const state = new StatefulObject( default, opts )

Select

All selectors can return a plain object, or Observable with a $ convention.

// From Root state
state.get('hello.world'); // JS Object
state.get$('hello.world'); // Rx Observable

// From Slice
const slice = state.at('hello.world');
slice.value; // JS Object
slice.get$(); // Rx Observable

See if a property exists

state.has('some.deeply.nested.array[23]');
// returns boolean

Perform advanced collection filtering when you have an array of objects.

state.where$('users', {
  age: v => v > 21,
  city: v => v === 'NYC'
});
// returns filtered Observable array

Mutate

Set will override the entire object with new data.

state.set({ username: 'nash' });

Update will only mutate the specified values.

state.update({ age: 30 });

Modify deeply nested properties

state.updateAt('users.userABC.profile', { name: 'doug' });
state.setAt(...);

Also

state.clear(); // next state {}
state.reset(); // reset to initial state

Slice

In most cases, you will need a reference to a specific slice of the stateful object.

const beer = state.at('beers.budlight.platinum');

A slice is just a scoped reference to the parent object, allowing you to to make references to deeply nested paths in the state.

beer.update({ abv: 6.0 });

Dispatch Custom Actions

You should use the mutation API above most of the time, but sometimes you might want to dispatch and handle an action manually.

Dispatch allows you to pass an action name, payload, and handler function (similar to a reducer function), then you return the next state.

const handler = (state, payload) => {
  return { ...state, username: payload };
};
state.dispatch('CHANGE_USERNAME', payload, handler);

Async Data

Async state management is a challenge on the web. The tool can automatically subscribe to Observables and resolve Promises, then make updates with the resolved values. It also keeps track of all running subscriptions in the state object, so you can manage, cancel, and analyze streaming subscriptions

Method 1 - Automatic

const observable = http.get(...);

this.state.async('tasks.items', observable);
// note that it also works with promises

The feed method will subscribe to the Observable (or Promise), patch the emitted values to the state, and dispatch two actions:

  1. OBSERVABLE_START@path on subscribe
  2. OBSERVABLE_SET@path update with emitted data

Bonus features: If you feed multiple Observables to a path, it will automatically cancel previous requests. It will also let you know when your observable sends the complete signal.

  1. OBSERVABLE_COMPLETE@path on complete
  2. OBSERVABLE_CANCEL@path if unsubscribed automatically

You can access all active subscriptions the stateful object with state.subs

Works great with realtime data feeds like Firebase.

Method 2 - Manually

You can dispatch arbitrary actions to signal the start of something async. This can be useful debugging or reacting to the action stream (see method 3).

this.state.signal('MAKE_GET_REQUEST');
http.get(url).pipe(tap(data => {
    state.update(data)
})
.subscribe()

Method 3 - React to an Action Stream

Inspired by NgRx Effects, you can listen to actions and react.

this.state.actions$
  .ofType('[mystate] MY_ACTION')
  .pipe(
    tap(action => {
      // do stuff
    })
  )
  .subscribe();

Middleware

You can intercept state changes with middleware. For example, this package uses middleware to implement a logger and Redux Devtools. Just return the next state.

const yourMiddleware = (current, next, action, opts) => {
  console.log(current, next, action, opts);
  next = { ...next, hello: 'middleware' };
  return next;
};

You can pass middleware through options or by calling use. Note: Only one middleware function is allowed per StatefulObject.

const state = new StatefulObject({ name: 'bubba' }, opts);
state.use(yourMiddleware);

Usage in Angular

If you have highly complex requirements, look into a fully integrated solution like NGXS or NgRX.

Global Store, Redux Pattern. If you want global store, create a service that instantiates a single StatefulObject then pass it around with Angular's DI.

Smart parent, dumb children. It's common to make a parent component stateful, then pass the state context down via @Input(). Don't be afraid to use multiple state containers. Each container gets a unique name and it's own instance in devtools.

Get Creative. This is not an opinionated library. Architect your own state management solution with this swiss army knife.