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

mithril-hobbit-archivist

v0.2.0

Published

Central state management and subscription for Mithril

Downloads

12

Readme

Mithril Hobbit Archivist

NPM Version NPM License NPM Downloads

Hobbit archivist is part of the Hobbit framework for mithril. It offers a complete system to store and observe an application's state from a central location. This package is heavily inspired from system like Redux and Cerebral.

The state management system is easy to use and extremely light - weighing less than 1KB - bringing the whole mithril package to just about 9 KB.

How to install

To install Hobbit archivist, either install the hobbit framework or this package individually through npm:

npm install --save mithril-hobbit-archivist

Hobbit archivist is also available as a UMD module in a minified or unminified format.

How to use

Hobbit archivist need to be configured and started before it can be used. By default, a simple - in-memory - data store is used when creating the first store. This basic data can be used for debugging or simple applications, but more complex project may need a more robust data store.

The store can be created anywhere in the application structure as long as it is created before the rendering process in mithril or before it is used.

import { Store } from 'mithril-hobbit-archivist';

const stateStore = Store.createStore();

The createStore accepts two arguments. The first argument is the intial content of the state store.

import { Store } from 'mithril-hobbit-archivist';

const stateStore = Store.createStore({
    foo: 'bar',
});

This ensures that the value set as the initial state will always exist in the state on the first rendering even if the set method is not called. This is very useful for SSR for example where it is common to cache and load data from the server.

The second argument of createStore is the connector, which can be customized for more advanced behavior.

With the store created, the state can be handled using a series of methods from the store.

stateStore.getState();
//Returns the whole state store

stateStore.find('this.is.a.path');
//Returns the value in the state if it exists

stateStore.set('this.is.a.path', {value});
//Sets the value in the state. If part of the path does not exist, it creates it

stateStore.remove('this.is.a.path');
//Removes the value from the store

stateStore.clear();
//Clears the store completely

Binding and subscribing

Hobbit archivist exposes two helper function to simplify the binding of values from the store to a component.

bind will bind a specific value from the store to the component it is given, wrapping it in a higher-order component that passes down an attribute called state that contains the fetched value.

import m from 'mithril';
import { Store, bind } from 'mithril-hobbit-archivist';

const stateStore = Store.createStore({
    foo: 'bar',
});

const index = {
    view: function(vnode) {
        return m('div.index', vnode.attrs.state); // Prints bar
    },
};

m.mount(document.body.querySelector('#root'), bind(index, 'foo'));

subscribe acts like bind, but it also allows for notifying the component of any change in the selected values. The developer is responsible for handling rerenders as the subscribe method only calls a lifecyle method on the wrapped component called onstatechanged. In addition to adding the state in the attributes, it also exposes and unsubscribe attribute function that stops notification when called.

import m from 'mithril';
import { Store, subscribe } from 'mithril-hobbit-archivist';

const stateStore = Store.createStore({
    foo: 'bar',
});

const index = {
    onstatechanged: function(newState) {
        m.redraw(); //Redraws on state changed
    },
    onremove: function(vnode) {
        vnode.attrs.usubscribe(); //Stops notification when removed
    },
    view: function(vnode) {
        return m('div.index', vnode.attrs.state); // Prints bar
    },
};

m.mount(document.body.querySelector('#root'), subscribe(index, 'foo'));

The onstatechanged lifecycle method will give the new state as its only parameter, if the old state needs to be compared to the new state, the component must keep track of its state by itself.

//...

//TODO: API may change when the onstatechanged gets hooked like other lifecycle methods
const index = {
    foo: null,
    oncreate: function(vnode) {
        this.foo = vnode.attrs.state;
    },
    onstatechanged: function(newState) {
        if (this.foo !== newState)
        {
            this.foo = newState;
            m.redraw(); //Redraws on state changed and different
        }
    },
    view: function(vnode) {
        ...
    },
};

//...

Creating a connector

To create a connector, a creatable element must be passed as the second argument of createStore, for example, a ES6 class.

This element must then implement all these methods:

class connector {
    constructor(initial) { ... }
    
    set(path, value) { ... }
    
    remove(path) { ... }
    
    find(path) { ... }
    
    clear() { ... }
    
    all() { ... }
}

Refer to the API and examples for details on the connector interface.

Use with lokijs as a connector

import { Store } from 'mithril-hobbit-archivist';

class connector {
    constructor(initial) {
        //TODO: Create initial state
        this.db = new loki();
        this.data = this.db.addCollection('data');
    }
    
    set(path, value) {
        const existingRecord = this.data.findOne({path});
        if (existingRecord) 
        {
            this.data.update(Object.assign({}, existingRecord, {
                value,
            }));
        }
        else
        {
            this.data.insert({path, value});
        }
    }
    
    remove(path) {
        this.data.findAndRemove({path});
    }
    
    find(path) {
        const found = this.data.findOne({path});
        return found ? found.value : undefined;
    }
    
    clear() {
        this.data.clear();
    }
    
    all() {
        return this.data.find({}).map((record) => record.value);
    }
}

const stateStore = Store.createStore(null, connector);

API

Store.createStore(intialState = {}, connector = null)

Store.createStore creates the store with the initial state. If no connector is given, the default memory connector is used which simply creates a basic javascript object in memory.

  • initialState is the initial state to give to the connector to setup the data on the first load.
  • connector is the connector to use for the state management. If none is given, the default memory connector is used instead. Any connector must have a set of functions described in the documentation.

Store.getState()

Store.getState() returns the full content of the state store.

subscribe(subscriber)

subscribe(subscriber) subscribes the subscriber to the notify functionality of the store. Whenever the set or remove function is called on the store, it notifies all subscribers with the changed path. The method rutuns a key that can identify the subscriber when it comes to unsubscribing it.

  • subscriber is the object subscribing to changes in the store. It must implement a notify(path) method to receive any notification.

unsubscribe(key)

unsubscribe(key) unsubcribes the subscriber under the key from the notification system.

find(path)

find(path) finds the value under the path inside the state connector. The path is expected to be a string path with dot notation, though a state connector may change this behavior.

  • path is the path to find the data, written as a standard javascript dot notation.

set(path, value)

set(path, value) sets the given value under the path in the state using the state connector. The path is expected to be a string path with dot notation, though a state connector may change this behavior. In the default memory connector, if the path does not fully exists, it will be created.

  • path is the path to find the data, written as a standard javascript dot notation.
  • value is the value to set at the end of the path.

remove(path)

remove(path) removes the value under the path using the state connector. The path is expected to be a string path with dot notation, though a state connector may change this behavior.

  • path is the path to find the data, written as a standard javascript dot notation.

reset()

reset() fully resets the store using the state connector.

subscribe(component, path)

subscribe(component, path) is a helper to quickly subscribe a component to part of the state. Whenever the state found under the path or any ofits parent is the state tree is changed, the component will see the onstantechanged lifecycle hook triggered which then enables it to redraw itself if needed.

  • component is the component to subscribe to the data. It is given two attribtes, state which contains the state found under path if any and unsubscribe which is a function that can stop the automatic notification system when called.
  • path is the path used to find the value inside the state, refer to State.find for more info.

bind(component, path)

bind(component, path) is a helper to quickly bind a component to part of the state.

  • component is the component to subscribe to the data. It is given one attribte; state which contains the state found under path if any.
  • path is the path used to find the value inside the state, refer to State.find for more info.

Browsers support

Untested yet.