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

arbor-store

v0.5.0

Published

Seamless state management made with <3

Downloads

23

Readme

Arbor

npm version Build Status

Seamless state management made with ❤️.

What is it?

Arbor is a state tree management solution that leverages immutability through structural sharing to perform state tree mutations allowing subscribers to listen to state changes.

Even though immutability is Arbor's foundation, very little boilerplate is added to the developer's workflow and mutations are triggered via the old and familiar Javascript Object/Array APIs (thanks to ES6 Proxies under the hoods). Because Arbor mutations apply structural sharing under the hoods, features like state history playback and undos are easily implemented.

Additionally, Arbor allows custom types to be bound the specific paths within the state tree so you may better encapsulate your business logic keeping them separate from UI logic, increasing testability as well as business logic reusability.

Arbor is framework agnostic, though, there is a React binding that you can check out.

Getting Started

A simple Counter APP...

import Arbor from "arbor-store"

const store = new Arbor({
  counter1: {
    count: 0,
  },
  counter2: {
    count: 0,
  }
})

store.subscribe((nextState, previousState) => {
  console.log("new state:", nextState)
  console.log("old state:", previousState)
})

store.state.counter1.count++

Breaking it down

The State Tree

const store = new Arbor({
  counter1: {
    count: 0,
  },
  counter2: {
    count: 0,
  }
})

The snippet above defines a store whose state tree looks like this:

          (root)
          /    \
(counter1)     (counter2)
    |              |
  count = 0      count = 0

In the state tree, (root), (counter1) and (counter2) are tree node objects responsible for all the immutability "magic". Each node has a path that determines its location within the state tree. (root) for example is represented by the / path, (counter1) is represented by /counter1 and (counter2) represented by /counter2. Leaf nodes within the state tree are non-node types (count attributes).

Mutation Subscriptions

The code below registers a subscriber function which is called whenever a mutation happens in the state tree, providing access to the next and previous states.

store.subscribe((nextState, previousState) => {
  console.log("new state:", nextState)
  console.log("old state:", previousState)
})

Mutations

store.state.counter1.count++

Every mutation triggered by any node creates a mutation path that determines which nodes in the state tree were affected by the mutation and thus must be refreshed with new instances.

Once a mutation is finished, a new state tree is generated where nodes affected by the mutation path have their instances refreshed and nodes not affected by the mutation path are kept untouched (Structural Sharing), for instance:

Triggers a mutation in the state tree for the mutation path /counter1. That mutation path affects the (root) node whose path is /, and the (counter1) node whose path is /counter1. Since (counter2) whose path is /counter2 is not affected by the mutation path, it is reused in the new state tree:

          (root*)
          /    \
(counter1*)     (counter2)
    |              |
  count = 1      count = 0

Nodes marked with a * in the state tree above represent the nodes affected by the mutation path and thus are new node instances.

Splitting Business logic From UI Logic

As React applications grow larger, splitting business and UI logic can get tricky. Arbor allows custom node types to be bound to specific paths within the state tree, where business logic code can be encapsulated increasing testability and maintainability.

Custom node types are just plain ES6 classes that are explicitly bound to certain paths of the state tree and provide a constructor which "selects" what state attributes it cares about, for example:

class Todo {
  constructor({ id, title, status }) {
    this.id = id
    this.title = title
    this.status = status
  }

  start() {
    this.status = "doing"
  }

  finish() {
    this.status = "done"
  }
 }

 const store = new Arbor({
   todos: [
     { id: 1, title: "Do the dishes", status: "todo" },
     { id: 2, title: "Clean the house", status: "todo" }
   ]
 })

 store.bind("/todos/:index", Todo)

The example above defines a custom node type Todo and binds it to the /todos/:index path. There are a few things to notice here:

  1. The custom type Todo implements a constructor which takes all properties that make up a todo item.
  2. Business logic is defined by the new custom type for starting and finishing a todo.
  3. The custom type is bound to a wildcard path where :index represents any todo item within the todos array. Any access to any todo item in the array, will yield an instance of Todo, e.g.:
const todo = store.state.todos[0]
expect(todo).to.be.an.instanceOf(Todo)

Custom node types can represent either objects or array nodes within the State Tree. Custom array nodes must extend Array:

class Todos extends Array {
  constructor(items) {
    super(...items)
  }

  createTodo({ title }) {
    this.push({ id: Math.random(), title })
  }

  startTodo(index) {
    this[index].start()
  }

  finishTodo(index) {
    this[index].finish()
  }

  sortByTitle() {
    this.sort((todo1, todo2) => {
      if (todo1.title > todo2.title) return 1
      if (todo1.title < todo2.title) return -1
      return 0
    })
  }
}

store.bind("/todos", Todos)

State Tree Time Travel

Arbor leverages Structural Sharing in order to perform state mutations. A new state tree is always created by applying the minimum amount of operations necessary to generate the new state. With this, a series of state snapshots may be recorded, allowing for interesting use cases such as State Time Travel.

2017-12-14 20 51 16

License

MIT