UNPKG

mutable-store

Version:

a mutable state management library for javascript

156 lines (131 loc) 3.72 kB
export function getProps(obj: Record<string, any>) { const props = new Set(); const builtins = [ Object.prototype, Array.prototype, Function.prototype, String.prototype, Number.prototype, Boolean.prototype, Symbol.prototype, Date.prototype, RegExp.prototype, Map.prototype, Set.prototype, WeakMap.prototype, WeakSet.prototype, Promise.prototype, Error.prototype, ]; while (obj && !builtins.includes(obj)) { for (const key of Reflect.ownKeys(obj)) { if (key !== "constructor") { props.add(key); } } obj = Object.getPrototypeOf(obj); } return Array.from(props); } function makePropsReadOnly<T extends Record<string, any>>( obj: T, keys: (keyof T)[] ) { for (const key of keys) { if (Object.prototype.hasOwnProperty.call(obj, key)) { Object.defineProperty(obj, key, { writable: false, configurable: false, enumerable: true }); } else { if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line no-console console.warn(`Property "${String(key)}" does not exist on the object.`); } } } return obj; } type TMutableStore<T> = T & { subscribe: (fn: () => void) => () => void; ___thisIsAMutableStore___: true; ___version___: number; }; export default function createMutableStore<T extends Record<string, any>>( mutableState: T ): TMutableStore<T> { if (mutableState === null || typeof mutableState !== "object") { throw Error("mutableState must be an object"); } const props: (keyof T)[] = getProps(mutableState) as (keyof T)[]; if (props.includes("subscribe")) { throw Error( "subscribe is a reserved keyword for the store, please use another name for your property" ); } const subscriptions = new Set<() => void>(); const internalStoreUnSubs = new Set<() => void>(); const getInternalStores = () => props .filter((item) => (mutableState as any)[item].___thisIsAMutableStore___) .map((item) => (mutableState as any)[item]); (mutableState as any).___thisIsAMutableStore___ = true; (mutableState as any).___version___ = 1; const subscribe = (fn: () => void) => { subscriptions.add(fn); return () => { subscriptions.delete(fn); }; }; (mutableState as any).subscribe = subscribe; function callSubs() { try { subscriptions.forEach((fn) => fn()); } catch (e) { if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line no-console console.error(e); } } } // auto subscribe to internal stores function autoSubscribeToInternalStores() { getInternalStores().forEach((item) => internalStoreUnSubs.add( item.subscribe(() => { callSubs(); }) ) ); } autoSubscribeToInternalStores(); props.forEach((prop) => { if ( typeof (mutableState as any)[prop] === "function" && prop.toString().startsWith("set_") ) { const originalMethod = (mutableState as any)[prop]; (mutableState as any)[prop] = function (...args: any[]) { const result = originalMethod.apply(mutableState, args); setTimeout(() => { callSubs(); autoSubscribeToInternalStores(); }, 0); return result; }; } }); Object.preventExtensions(mutableState); makePropsReadOnly( mutableState, props.filter( (item) => typeof (mutableState as any)[item] === "function" || (mutableState as any)[item]?.___thisIsAMutableStore___ ) ); return mutableState as TMutableStore<T>; } export const Store = createMutableStore;