@superutils/store
v0.1.24
Published
Generic, reactive, persistent and fully-typed Map-like data store with advanced search, filtering, and sorting capabilities.
Downloads
582
Maintainers
Readme
@superutils/store
A generic, reactive, persistent and fully-typed Map-like data store with advanced search, filtering, and sorting capabilities. It supports both in-memory caching and persistent storage (LocalStorage in browsers, or JSON files in NodeJS).
Built on RxJS for reactive data handling, it is optimized for small to medium datasets and provides a seamless way to manage application state with optional persistence.
For full API reference and example code playground check out the docs page.
Table of Contents
Installation
NPM
Install using your favorite package manager (e.g., npm, yarn, pnpm, bun, etc.):
npm install @superutils/storeDependency: @superutils/core will be automatically installed by package manager
CDN / Browser
If you are not using a bundler, you can include the minified browser build directly:
<script src="https://unpkg.com/@superutils/store@latest/dist/browser/index.min.js"></script>OR,
<script src="https://cdn.jsdelivr.net/npm/@superutils/store/dist/browser/index.min.js"></script>Basic Usage
Map-based Store
The Store class can be used just like a standard JavaScript Map, but with the added benefit of optional persistence and reactivity.
import { createStore } from '@superutils/store'
// Initialize the store that saves the stringified data to `localStorage.users` in the browser.
// Bypassing the name or using `null` will create an in-memory store.
const userStorage = createStore({ name: 'users' })
// Set and get values
userStorage.set('alice', { name: 'Alice', age: 30 })
// functional update
userStorage.set('alice', alice => alice ?? { name: 'Alice', age: 30 })
console.log(userStorage.get('alice')) // prints: { name: 'Alice', age: 30 }
console.log(userStorage.size) // 1Object-based Store
createObjectStore provides a type-safe way to manage a single plain object as a store, where keys of the object become keys in the store.
import { createObjectStore } from '@superutils/store'
const userStore = createObjectStore({
name: 'user-profile',
initialValue: {
age: 25,
name: 'Jane Doe',
roles: ['guest'],
},
})
console.log(userStore.get('name'), userStore.get('age')) // Prints: 'Jane Doe' 25
console.log(userStore.toObject()) // prints: { age: 25, name: 'Jane Doe', roles: [ 'guest' ] }Persistent Storage (NodeJS)
In NodeJS environments, you can use node-localstorage to persist your data to the file system.
import { createStore } from '@superutils/store'
import { LocalStorage } from 'node-localstorage'
// Provide a localStorage implementation for NodeJS that can be used throughout the application mimicking the browser LocalStorage behavior.
globalThis.localStorage = new LocalStorage(
'./data', // directory to store files in
1e7, // max file size
)
// Create a store that saves the data to ./data/settings.json
// Bypassing the name or using `null` will create an in-memory store.
const store = createStore({ name: 'settings.json' })
store.set('theme', 'dark') // Automatically saved to ./data/settings.json
/**
* Alternatively, you can also provide a LocalStorage instance to each Store instance.
*/
createStore({
name: 'settings.json',
storage: new LocalStorage('./data', 1e9),
})Advanced Usage
Data Validation
To ensure data integrity, you can provide a validate object containing hooks for various operations (set, setAll, delete, clear, write). These hooks are executed immediately before the store's internal state is updated. If a validator throws an error, the operation is aborted.
import { createObjectStore } from '@superutils/store'
const settingsStore = createObjectStore({
name: 'app-settings',
initialValue: {
theme: 'light',
version: '1.0.0',
},
validate: {
set([key, value]) {
console.log('size:', this.size) // "this" refers to the store instance
if (key !== 'theme' || ['light', 'dark', 'system'].includes(value)) return
// throw error to abort operation
throw new Error(`Invalid theme: ${value}`)
},
delete: ([keys]) => {
if (!keys.includes('version')) return
throw new Error('The "version" key is protected and cannot be deleted')
},
},
})
settingsStore.set('theme', 'system')
console.log(settingsStore.get('theme')) // 'system'
try {
settingsStore.set('theme', 'invalid') // throws error
} catch (err) {
console.log(err)
}Reactive Updates (RxJS & Callbacks)
You can subscribe to changes using the internal RxJS Subject or a simple onChange callback.
import { createStore } from '@superutils/store'
const store = createStore({
name: 'my-data',
onChange: data => console.log('Data changed!', data),
})
// Or use the RxJS subject directly
const sub = store.subject$.subscribe(data => {
console.log('Reactive update:', data)
})
store.set('key', 'value')Search and Filtering
Store provides powerful search and filter capabilities directly on your data.
import { createStore } from '@superutils/store'
const store = createStore({
name: 'products',
// Pre-populate the storage with sample data
initialValue: new Map([
[1, { id: 1, name: 'Laptop', category: 'electronics', price: 1000 }],
[2, { id: 2, name: 'Chair', category: 'furniture', price: 150 }],
]),
})
// Search for items using a query object
const searchResult = store.search({
asMap: false,
query: { category: 'electronics' },
})
console.log(searchResult) // [{ id: 1, name: 'Laptop', ... }]
// Filter items using a predicate
const expensiveItems = store.filter(val => val.price > 500)Attaching Business Logic: Store Augmentation
Using createStore, you can attach custom business logic to your store instance, allowing you to encapsulate operations without having to create a subclass.
import { createStore } from '@superutils/store'
const getContext = store => ({
// Context can be an object or a function that returns an object store => ({
get isAuthenticated() {
return store.has('token')
},
login: async () => {
store.set('token', 'some-token')
},
logout: () => store.delete('token'),
})
const authStore = createStore(
{
initialValue: new Map(),
name: 'auth',
},
getContext,
)
// Access your custom logic directly from the store instance
if (!authStore.isAuthenticated) {
authStore.login().then(() => console.log('Logged in'))
}Object-based Store With Augmentation
createObjectStore supports augmentation the same way as createStore.
import { createObjectStore } from '@superutils/store'
type UserProfile = {
age: number
name: string
roles: string[]
}
const userStore = createObjectStore(
{
name: 'user-profile',
initialValue: {
age: 25,
name: 'Jane Doe',
roles: ['guest'],
} as UserProfile,
},
store => ({
promoteToAdmin() {
// Update properties with type safety
store.set('roles', (roles = []) => [...roles, 'admin'])
},
}),
)
userStore.promoteToAdmin()
console.log(userStore.get('roles')) // ['guest', 'admin']OOP: Subclassing Store
You can extend the Store class to create custom store implementations with specialized logic or default behaviors.
import { Store } from '@superutils/store'
interface Product {
id: number
name: string
price: number
inStock: boolean
}
class ProductStore extends Store<number, Product, false> {
constructor(
...[name, options]: ConstructorParameters<
typeof Store<number, Product, false>
>
) {
super(name, { ...options, delay: 100 }) // Set a default delay for this store type
}
getInStockProducts(limit?: number, asMap = false) {
return this.filter(product => product.inStock, limit, asMap)
}
}
const products = new ProductStore('my-products')
products.set(1, { id: 1, name: 'Laptop', price: 1200, inStock: true })
products.set(2, { id: 2, name: 'Mouse', price: 25, inStock: false })
console.log(products.getInStockProducts()) // { id: 1, name: 'Laptop', price: 1200, inStock: true }