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

kaleido

v0.2.2

Published

Simple webapp state management with streams and lenses

Downloads

30

Readme

Kaleido

Join the chat at https://gitter.im/kaleidostate/kaleido

Kaleido is a small and easy to use library to manage state in web applications. It was designed specifically with component based view frameworks like Mithril or React in mind, but it can be used independently. It makes use of techniques from functional programming and reactive programming, specifically relying on streams as data structures and lenses as a way to access these data structures' contents.

Currently, the stream implementation is provided by the great library flyd and Kaleido depends on Ramda as a functional programming utility belt, also providing the lens implementation. However, there are plans to abstract away at least the stream implementation and provide adapters for other popular libraries like RxJS , Most or Kefir

Architecture

In modern web development, the trend is going towards modularizing all the things. You have component based view frameworks, modular CSS architectures, even atomic design principles. But somehow, most state management tools come with a quite monolithic approach. State manipulating logic is often concentrated in one place. While it is advisable to represent your app's state in one central data structure for comprehensibility and maintainability reasons, the approach of having all state manipulation logic in one place is flawed, because it does not scale well.

Consider adding a new functionality to your app, coming with its own piece of state to be stored, logic to manipulate this state, an own kind of view representation and its own styling. Wouldn't it be great to only have to add code to a separate, additional and confined module, instead of having the view and styling in one file, and the logic in the other? Essentially, this is what they call the "Open/Closed principle". You want your application be open for extension, but closed for modification. And this is where Kaleido can come in handy.

Let's have a look at how the data flows in an app using Kaleido:

Kaleido Architecture

This diagram might seem a bit daunting at first, but trust me, it's easy to understand: The state of your app lives in one space, called state. The state is a stream and in it flow many instances of one big object. You guessed it: The state object. A state might look somewhat similar to this:

{
  scopeA: {
    foo: [],
    bar: '',
  },
  scopeB: {
    baz: 42,
  },
  scopeC: ['this', 'that'],
}

Do you see how it is logically separated by so called scopes? A scope is a part of your state. It corresponds to a semantical separation inside your app, there could be for example a todos scope in a Todo application, or a cart scope in some eCommerce context. However, it is important to note that scopes do not (have to) map to view components one-to-one. Your view is accessing the state via scopes, but it could access multiple scopes simultaneously. One scope can also be accessed by multiple view components.

But how does the view access such a scope? Great question, Jimmy. And the answer comes from the functional programming world: Lenses. (If you're not yet familiar with what lenses are, they are a functional way to access a subpart of a data structure. Learn more) Upon registering a scope, a lens focusing the desired part of the state is created and can then be used to retrieve the data, set new values, and also manipulate it by passing in a function.

Every time you use those lenses to update a scope, a new state object is passed down the stream and every listeners on the state stream get updated. This is the point where the view framework should get rerendered. (Provided you have made it listen to the state stream. See the Views section for more details.) Additionally, every scope includes an update stream containing all updates only happening on that particular scope. You can use this to build your views reactively by mapping over it.

How to

Installation

You can install Kaleido from npm: npm install kaleido.

Instanciating a kaleidoscope

Kaleido exports its main function scope as the default export, you can import it like so:

import scope from 'kaleido';

You invoke the scope function passing in a path to the part of the state you want the scope to live in, and an optional initial value to set the state to.

const todos = scope(['todos', 'list'], []);

This would result in a state of the following shape:

{
  todos: {
    list: []
  }
}

Accessing the scope

After you have created your scope, inside your view components you can access the current state via

todos.get();

And set the state to a new value with

todos.set(['Install Kaleido', 'Be awesome']);

Also, you can execute a function taking the current state as an input and setting the state to its result:

todos.do( state => state.concat(['Watch cute kitten videos']) );
// Alternatively, you can use todos.over(...) 

This is especially convenient when using curried functions, as offered by Ramda:

todos.do(append('Ride a unicorn'));

You see, Kaleido scopes work just like lenses on the state. And indeed Kaleido uses Ramda's lenses under the hood. But wait, there's more!

Update streams

The handle returned by scope also includes an update stream you can subscribe to. Everytime the state is updated, the new state inside the scope will be passed down the stream.

todos.$.map(renderTodoItem);
// Alternatively: todos.stream

The update stream is a flyd stream, so you have access to its API to use and transform the stream however you like.

Composing state

Composing the state is as easy as instantiating multiple scopes: It doesn't matter whether you do this in the same component or in different ones:

// todoList.js
const todoItems = scope(['todos', 'list'], []);
const todoInput = scope(['todos', 'input'], '');

// notifications.js
const notifications = scope(['notifications'], []); 

This will build a state of the following shape:

{
  todos: {
    list: [],
    input: ''
  },
  notifications: []
}

These scopes are then handled independently. However, you can import the whole state as a stream from Kaleido. You can then use the state stream to inspect the whole app's state or subscribe to changes on all scopes.

import { state } from 'kaleido';

// Debug all state updates
state.map(console.log);

Views

Mithril JS

In mithril, the view is rerendered after an event handler has been called and after a m.request Promise chain. So if your view only updates the state through those, you should be good to go without any further wiring. However, if you update the state from anywhere else, you should either call m.redraw() manually when you have updated the state, or you can redraw the view automatically on every state update with state.map(m.redraw). I haven't tested the latter performancewise though, so you might have to be careful with that.

React

In React, components get rerendered when their internal state changes. I am currently working on a solution for binding Kaleidoscopes to React component's state so they get rerendered reliably.

Future plans

  • Make Kaleido State locally instanceable
  • React bindings
  • Evaluate use for other frameworks like Vue, Angular, etc.
  • Prevent scope collisions
  • Time travel
  • Abstract flyd dependency to be exchangeable with other stream libraries

Postamble

If you have any feedback, spot a mistake or bug or just want to discuss anything about Kaleido, feel free to contact me in the Kaleido gitter!