@known-as-bmf/store
Version:
Lightweight synchronous state management library.
207 lines (202 loc) • 5.06 kB
JavaScript
import { produce } from 'immer';
import { createHookable } from '@known-as-bmf/hookable';
/**
* Map of error messages that can be thrown.
*
* @internal
*/
const errors = {
invalidStore: 'Provided value is not a valid store instance.',
};
/**
* Current subscriptions to store changes.
*
* @internal
*/
const SUBSCRIPTIONS = Symbol();
/**
* Current state of the store.
*
* @internal
*/
const STATE = Symbol();
/**
* State transformation (middleware).
*
* @internal
*/
const SET_STATE = Symbol();
/**
* The store class.
*
* @internal
*/
class StoreImpl {
}
/**
* Check if a value is a Store instance.
*
* @param input - The value to check.
*
* @internal
*/
function isStore(input) {
return input instanceof StoreImpl;
}
/**
* Function to assert that the store is a valid store instance.
*
* @param store - The store to assert.
*
* @internal
*/
function assertStore(store) {
if (!isStore(store)) {
throw new TypeError(errors.invalidStore);
}
}
/**
* Get the current value of a store.
*
* @param store - The store.
*
* @internal
*/
function getState(store) {
return store[STATE];
}
/**
* Set the current value of a store.
*
* @param store - The store.
* @param state - The new state.
*
* @internal
*/
function setState(store, state) {
store[SET_STATE](state);
}
/**
* Notify all subscribers to the store that the state has changed.
* If a selector was provided when subscribing,
* only notify if the desired value has changed.
*
* @param store - The store instance.
* @param event - The change event.
*
* @internal
*/
function notifySubscribers(store, event) {
store[SUBSCRIPTIONS].forEach(([selector, callback]) => {
if (selector(event.previous) !== selector(event.current)) {
callback(event);
}
});
}
/**
* Create a store.
*
* @param initialState - The initial value of the state.
* @param middleware - Middleware to use for this store. You can compose multiple
* middlewares with `composeMiddlewares` and `pipeMiddlewares`.
*
* @public
*/
function of(initialState, middleware) {
const store = Object.defineProperties(Object.create(StoreImpl.prototype), {
[SUBSCRIPTIONS]: {
value: new Set(),
writable: true,
},
[STATE]: { value: undefined, writable: true },
});
const setStateHook = createHookable((s) => {
store[STATE] = s;
return s;
});
Object.defineProperty(store, SET_STATE, { value: setStateHook });
const hooks = {
stateDidChange: setStateHook.leave,
stateWillChange: setStateHook.enter,
transformState: setStateHook.transformInput,
};
if (middleware) {
middleware(store, hooks);
}
setState(store, initialState);
return store;
}
/**
* Return the current state of a store.
*
* @param store - The store you want to get the current state from.
*
* @throws `TypeError` if the store is not a correct `Store` instance.
*
* @public
*/
function deref(store) {
assertStore(store);
return getState(store);
}
/**
* Change the state of a store using a function.
*
* @param store - The store of which you want to change the state.
* @param mutationFn - The function used to compute the value of the future state.
*
* @throws `TypeError` if the store is not a correct `Store` instance.
*
* @public
*/
function swap(store, mutationFn) {
assertStore(store);
const previous = getState(store);
const current = produce(previous, mutationFn);
setState(store, current);
notifySubscribers(store, { previous, current });
}
/**
* Change the state of a store with a new one.
*
* @param store - The store of which you want to change the state.
* @param current - The new state.
*
* @throws `TypeError` if the store is not a correct `Store` instance.
*
* @public
*/
function set(store, current) {
assertStore(store);
const previous = getState(store);
setState(store, current);
notifySubscribers(store, { previous, current });
}
function subscribe(store, callback, selector = (s) => s) {
assertStore(store);
const subscription = [selector, callback];
store[SUBSCRIPTIONS].add(subscription);
return () => {
store[SUBSCRIPTIONS].delete(subscription);
};
}
/**
* Compose middlewares from right to left.
*
* @param middlewares - Middlewares to compose.
*
* @public
*/
const pipeMiddlewares = (...middlewares) => (store, hooks) => middlewares.map((m) => m(store, hooks));
/**
* Compose middlewares from left to right.
*
* @param middlewares - Middlewares to compose.
*
* @public
*/
const composeMiddlewares = (...middlewares) => {
return pipeMiddlewares(...middlewares.reverse());
};
export { composeMiddlewares, deref, of, pipeMiddlewares, set, subscribe, swap };
//# sourceMappingURL=index.es.js.map