A simple key-value store with a powerful real-time state management API
Install using your preferred package manager, or import from a cdn:
yarn add nestore
<script src="https://unpkg.com/nestore"></script>
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: [],
})
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
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
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)
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')
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)
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.
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', () => {})
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
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.
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.
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], [...])
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], [...])
NSTOptions
NSTEmit
...
delimiter
adapters
See Adapters
maxListeners
delimiter
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')
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
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.
MIT