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

elastic-store

v0.2.0

Published

A flexible state container.

Downloads

163

Readme

Introduction

A simple and flexible state container.

Features

  • generates state tree from action tree
  • supports late action binding
  • supports multi level state tree
  • attach/detach middlewares
  • middlewares can be attached to particular nodes of state tree
  • supports late middleware binding

Installation

$ npm install --save elastic-store

Usage

// ES6
import {Store} from "elastic-store";

// node
var Store = require("elastic-store").Store;

Walkthrough

import {Store} from "elastic-store";

// Create action tree
let actions = {
	todos: {
		// initial value
		init () {
			return [];
		},
		add (todos, newTodo) {
			return todos.concat([newTodo]);
		},
		remove (todos, id) {
			return todos.filter((todo) => { todo.id !== id});
		}
	}
};

// Create store with given actions
let store = Store(actions);

// Add new todo
store.dispatch("todos.add", {id: 1, task: "Demo dispatch"});


// Get todos
console.log(store());
// => {todos: [{id: 1, task: "Demo dispatch"}]}

// Remove a todo
store.dispatch("todos.remove", 1);

// Get todos
console.log(store());
// => {todos: []}


let clone = (data) => {
	return JSON.parse(JSON.stringify(data));
};

// A middleware to log changes in state
let logger = (actionPath, next, store) => {
	return (state, payload) => {
		console.log("Before:", clone(state));

		let newState = next(state, payload);

		console.log("After:", clone(newState);

		return newState;
	}
}


// Attach logger to 'todos.add' action
// so that it only logs state changes made by this action
let attachedLogger = store.attach("todos.add", logger);

store.dispatch("todos.add", {id: 2, task: "Demo middleware."});
// => 
// Before: {todos: []}
// After: {todos: [{id: 2, task: "Demo middleware."}]};

store.dispatch("todos.remove", 2); // won't log changes made by this action

// Detach logger
attachedLogger.detach();

// Apply logger globally so that state changes made by every actions is logged.
let attachedLogger = store.attach(logger);


store.dispatch("todos.add", {id: 2, task: "Demo middleware."});
// => 
// Before: {todos: []}
// After: {todos: [{id: 2, task: "Demo middleware."}]};

store.dispatch("todos.remove", 2);
// => 
// Before: {todos: [{id: 2, task: "Demo middleware."}]};
// After: {todos: []}


// Use this state to render UI?
// Create a renderer middleware
let renderer = (actionPath, next, store) => {
	return (state, payload) => {
		let newState = next(state, payload);

		aViewFramework.render(document.body, aComponent, newState);

		return newState;
	}
};


// Apply it globally
let attachedRenderer = store.attach(renderer);

// Now every changes to state are rendered
// To stop rendering the changes just detach the middleware
attachedRenderer.detach();

Actions and state

elastic-store generates state tree from action tree.

The action tree below generates the state tree that follows it.

// action tree
let actions = {
	todos: {
		allResolved: {
			// default value for this node
			init () { return false; },
			toggle (prevState) { ... }
		},
		items: {
			// default value for this node
			init () { return []; },
			add (notifications, newOne) { ... },
			remove (notifications, id) { ... },
	   }
    },
	notifications: {
		init () {
			return [];
		}
		add (notifications, newOne) { ... },
		dismiss (notifications, newOne) { ... }
   }
};

// state tree
{
	todos: {
		allResolved: false,
		items: []
	},
	notifications: []
}

Create Store

import {Store} from "elastic-store";

let store = Store(actions, middlewares, initialState);

Get state

The store instance is a function. Invoke it to get the state in it.

Example

import {Store} from "elastic-store";

let aStore = Store(actions, middlewares, initialState);
aStore(); // returns the state

Add actions

Actions can be added in two different ways.

1. While creating store

import {Store} from "elastic-store";

let store = Store(actions);

2. After creating store

astore.actions(actions);

Example

let commonActions = {...};

let store = Store(commonActions);


// attach new actions
let newActions = {...};
store.actions(newActions);

Dispatch action

Changes to state are made by dispatching messages to actions.

Syntax

store.dispatch(actionPath, payload);

Example

let actions = {
	todos: {
		add (state, payload) {
			return state.concat([payload]);
		}
	}
};

// dispatch
store.dispatch("todos.add", {id: 1, text: "Demo action dispatch."});

Middlewares

Middlewares in elastic-store are similar to that of Express.js.

Attach Middleware

Middlewares can be attached in two different ways:

1. While creating store

Middlewares attached while creating a store are globally applied.

let aStore = Store(actions, middlewares);

2. After creating store

Middlewares attached to an instance acts 3 different ways depending on how they were attached.

Acts globally

A middleware can be attached globally, so that it can act upon any actions.

let actions = {
	todos: {
		add (state, todo) {...},
		remove (state, id) {...}
	},
	trash: {
		add (state, todo) {...},
		restore (state, id) {..}
	}
};

let aStore = Store(actions);

let clone = (data) => {
	return JSON.parse(JSON.stringify(data));
};

// A middleware to log changes in state
let logger = (actionPath, next, store) => {
	return (state, payload) => {
		console.log("Before:", clone(state));

		let newState = next(state, payload);

		console.log("After:", clone(newState);

		return newState;
	}
}

// acts globally i.e. on
// todos.add
// todos.remove
// trash.add
// trash.restore
let attachedMiddleware = aStore.attach(logger);
Acts on particular state node

A middleware can be attached to a particular state node, so that it can act upon any actions within that node.

// acts on actions under 'todos' i.e.
// todos.add
// todos.remove
let attachedMiddleware = aStore.attach("todos", logger);
Acts on particular action

A middleware can be attached to a particular action, so that it reacts to that single action.

// acts on the action 'todos.add'
let attachedMiddleware = aStore.attach("todos.add", logger);

Detach Middleware

Middlewares are detachable.

attachedMiddlewar.detach();

Custom middleware

A middleware has following signature:

/*
* actionPath: Dot separated path to an action in the action tree
* action: The action at the action path, which changes data
* store: The store
*/
let aMiddleware = (actionPath, action, store) => {
	/*
	* state: The entire state tree, returned by store()
	* payload: The payload for the action
	*			e.g. 'somePayload' in 'store.dispatch("action.path", somePayload)'
	*/
	return (state, payload) => {
		//...

		let newState = action(state, payload);
		
		//...

		return newState;
	}
}

Setting initial values

Each state node can have initial value. Actions can have init() method which returns the initial value.

let actions = {
	todos: {
		// initial value for this node
		init () { return []; },
		add () { ... },
		remove () { ... }
	}
};

Setting initial state

Initial state of a store can be set while creating one.

let initialState = {
	todos: [
		{id: 1, todo: "Do something awesome."}
	]
};

let actions = {
	todos: {
		add () { ... },
		remove () { ... }
	}
};

let aStore = Store(actions, middlewares, initialState);