pratiqdev / nestore-beta

Nested-wildcard event-based data store and state management API
0 stars 0 forks source link
event-emitter state-management

A simple key-value store with a powerful real-time state management API



Getting Started

Installation

Install using your preferred package manager, or import from a cdn:

yarn add nestore
<script src="https://unpkg.com/nestore"></script>

Usage

Import (or require) nestore and create a store with values, setters and listeners all in one place

// store.js
import nestore from 'nestore'

const nst = nestore({
    logged_in: false,
    user: null,
    messages: [],
    login: (NST, [name, password]) => {
        NST.set('logged_in', true)
        NST.store.user = name
    }
})

export default nst

Then import your store, register listeners on any path, and interact with the store

// app.js
import nst from './store.js'

nst.on('user', ({ key, path, value }) => {
    console.log(`Logged in as ${value}`)
})

nst.login('Alice', '1234')
nst.set('messages')

Nestore will automatically infer the types from the values in the initialStore, or you can provide a custom type definition for more type-safety

export type MyStore = {
    user: null | MyUser;
    messages: MyMessage[]
}

export type MyUser = {
    id: number;
    name: string;
}

export type MyMessage =  {
    time: number;
    text: string;
    media?: string[];
}

const myStore = nestore<MyStore>({
    user: null,
    messages: [],
})

Store Actions


Create a Store

Import nestore and create a store. The store is an object that contains the current values of the state. The store can hold any values, nested at any depth, that can be accessed from anywhere. The store always maintains the same reference allowing for a single-source-of-truth.

Import nestore, create your store, and export it.

import nestore from 'nestore'

const nst = nestore({ 
    logged_in: false,
    user_name: null,
    time: Date.now()
    1: 'one',
})

export default nst


Access the Store

All values are available through the store except in-store-mutators. Use the get method for easy or programmatic access to paths, or access the values directly through the store. Later we will react to changes with events.

The store is a mutable object with persistent references. Any direct access to nst.store.<path> will return that value with its current reference. Be cautious of unintended updates to store values by reference.

import nst from './myStore.js'

let loggedIn = nst.get('logged_in')
let user = nst.store.user_name


Update the Store

You can manually update/create values/keys externally using the set method or by updating the value directly. You can also update the entire store using either of these methods. Setting the value to null or undefined will not remove the key from the store.

import nst from './myStore.js'

nst.logged_in = false
nst.set('user_name', null)


Remove from the Store

To completely remove a key from the store 'object' - use the remove method. This will emit an event for the provided path.

nst.remove('user_name')


Reset the Store

Nestore keeps a copy (deep-clone) of the original store and provides a reset method.

import nst from './myStore.js'

nst.logged_in = false
nst.set('user_name', null)



Store Events

All actions and events within the store emit events that can be used to trigger external behavior when the data changes. Many storage mediums use the pub/sub pattern to react to real-time changes to data.


Listen to Changes

Nestore provides a method for registering an event listener that subscribes to a specific path, provided as the first argument to the nst.on method, and a callback to handle logic as the second argument. The callback will be always be invoked with an object of type NSTEmit.

nst.on('/', ({ value }) => {
    // react to the entire store (path and key are '/')
})
nst.on('path', ({ path, key, value }) => {
    // react to any changes to 'path'
})

Thanks to eventemitter2 we can listen to nested paths in objects and arrays. See more emitter methods and examples in Common Emitter Methods

nst.on('users.*.logged_in', ({ path, key, value }) => {
    // react to any users `logged_in` status
})

or we can use some convenience/utility methods provided by ee2 like:

// invoke the callback, then remove the lsitener
nst.once('path', () => {})
// invoke the callback n times, then remove the lsitener
nst.many('path', 5, () => {})
// invoke a callback on any change to the store
nst.onAny('path', () => {})

Emit Changes

Any update to the store using the set method will emit events for all paths/keys that were modified by the update

Events will only be emitted if the values are different (shallow equality) when preventRepeatUpdates is true, and when the emit flag is omitted or set to 'emit' or 'all'. See the Full API section

Manual Emit

You can also manually emit events to force update a listener. The value provided to the emit method should be an object with the type T_NSTEmit, but any values / types provided will be emitted.



Common Emitter Methods

Nestore extends the event-emitter-2 class. Visit the ee2 npm page to view the full documentation of every method included with the event emitter.

emit

Execute each of the listeners that may be listening for the specified event name in order with the list of arguments. emitter.

emitter.emit(event | eventNS, [arg1], [arg2], [...])

on / off

Execute each of the listeners that may be listening for the specified event name in order with the list of arguments.

Same as addListener and removeListener

emitter.emit(event | eventNS, [arg1], [arg2], [...])

Full API

Types

NSTOptions
NSTEmit
...

Nestore Async Generator

Nestore Options

delimiter
adapters See Adapters

Properties

maxListeners
delimiter

Methods

In Store Listeners

In Store Mutators

Manage all store logic in a single place with custom in-store mutator functions.

These can be any type of function (async/sync) and return any value. Just set the state when the values are ready.

const myStore = nestore({
    user_name: null,
    user_data: null,

    fetchUserData: async (nst, [name]) => {
        const { data } = await axios.get(`/api/users/${name}`)
        nst.set('user_data', data)
        return data
    }
})

// just run the function
myStore.fetchUserData('Johnny68')

// wait for the return value
let userData = await myStore.fetchUserData('Johnny68')

Adapters





About

An exploration of event based datastore management for JavaScript/TypeScript applications. Initially created to manage state and update the display of long-running nodejs CLI programs

Inspired by Zustand - API inspired by the vanilla implementation of Zustand



Contributing

This state-management solutions still requires at least:

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change and make sure all tests pass locally before making a pull request.

Please make sure to update tests as appropriate, or suggest new test cases.

GitHub Repository

GitHub Issues

NPM Package


License

MIT