import create , { State , StateSelector , EqualityChecker , PartialState , GetState , Subscribe , Destroy , StoreApi , } from 'zustand' ; import shallow from 'zustand/shallow' ; import flow from 'lodash/flow' ; import matchAll from 'string.prototype.matchall' ; import unset from 'lodash/unset' ; import hasIn from 'lodash/hasIn' ; import isEqual from 'lodash/isEqual' ; import _get from 'lodash/get' ; matchAll . shim ( ) ; type TStateRecords = Record < string | number | symbol | any , any > ; export enum LogLevel { None = 'none' , Diff = 'diff' , All = 'all' } interface IAddonsSettings { name ? : string ; logLevel ? : LogLevel | string ; } interface SetStateSettings { excludeFromLogs ? : boolean | undefined ; replace ? : boolean | undefined ; } type SetState < T extends State > = ( partial : PartialState < T > , replace ? : boolean ) => void ; type SetStateAddons < T extends State > = ( partial : PartialState < T > , setSettings ? : SetStateSettings ) => void ; declare type StateCreator < T extends State , CustomSetState = SetState < T > | SetStateAddons < T > > = ( set : CustomSetState , get : GetState < T > , api : StoreApi < T > ) => T ; type SetStateExtraParam = boolean | undefined | SetStateSettings ; interface IAddons { computed ? : TStateRecords ; watchers ? : TStateRecords ; middleware ? : Array < any > ; settings ? : IAddonsSettings ; } function getDeps ( fn ) { const reg = new RegExp ( /(?:this\.)(\w+)/g ) ; const reg2 = new RegExp ( /(?:this\[')(\w+)(?:'\])/g ) ; const matches = Array . from ( fn . toString ( ) . matchAll ( reg ) ) . map ( match => match [ 1 ] ) ; const matches2 = Array . from ( fn . toString ( ) . matchAll ( reg2 ) ) . map ( match => match [ 1 ] ) ; const mergedMatches = Array . from ( new Set ( [ ... matches , ... matches2 ] ) ) ; return mergedMatches ; } function intersection ( aArray , bArray ) { let intersections = [ ] ; for ( let elem of bArray ) { if ( aArray . includes ( elem ) ) { intersections . push ( elem ) ; } } return intersections ; } export interface UseStore < T extends State > { ( ) : T ; < U > ( selector , equalityFn ? ) : U ; setState : SetState < T > | SetStateAddons < T > ; getState : GetState < T > ; subscribe : Subscribe < T > ; destroy : Destroy ; } export default function createStore < TState extends TStateRecords > ( stateInitializer , addons ? ) { let _api ; let _originalSetState ; let _computed = { } ; let updatedComputed = { } ; let _computedMerged = false ; let _computedPropDependencies = { } ; let _watchers = { } ; let _settings = { name : addons ?. settings ?. name ?? 'MyStore' , logLevel : addons ?. settings ?. logLevel ?? LogLevel . None } ; function attachMiddleWare ( config ) { return ( set , get , api ) => { return config ( ( args , setSettings ) => { return setMiddleware ( [ args , setSettings ] , set ) ; } , get , api ) ; } ; } function setState ( args , setSettings ) { setMiddleware ( [ args , setSettings ] , _originalSetState ) ; } function setMiddleware ( args , set , recursiveOp = false ) { const [ partialState , setSettings ] = args ; let replaceState = false ; let excludeFromLogs = false ; if ( typeof setSettings !== 'undefined' ) { if ( typeof setSettings === 'boolean' ) { replaceState = setSettings ; } else if ( typeof setSettings === 'object' ) { replaceState = setSettings . replace || false ; excludeFromLogs = setSettings . excludeFromLogs || false ; } } let logOperations = ( ! excludeFromLogs ?? true ) && _computedMerged && ( _settings . logLevel as string ) !== ( LogLevel . None as string ) ; const currentState = _api . getState ( ) ; const changes = typeof partialState === 'function' ? partialState ( currentState ) : partialState ; const group = `${ _settings . name } state changed` ; logOperations && ! recursiveOp && console . group ( group ) ; logOperations && ! recursiveOp && ( _settings . logLevel as string ) === ( LogLevel . All as string ) && console . log ( 'Previous State' , currentState ) ; logOperations && ! recursiveOp && console . log ( 'Applying' , changes ) ; updatedComputed = { } ; if ( ! replaceState ) { for ( const [ compPropName , compPropDeps ] of Object . entries ( _computedPropDependencies ) ) { const needsRecompute = intersection ( Object . keys ( changes ) , compPropDeps ) ; if ( needsRecompute . length > 0 ) { updatedComputed [ compPropName ] = _computed [ compPropName ] . apply ( { ... currentState , ... changes , ... updatedComputed } , [ { set : _api . setState , get : _api . getState } , ] ) ; } } } if ( Object . keys ( updatedComputed ) . length > 0 ) { logOperations && console . log ( 'Updating computed values' , updatedComputed ) ; } const newState = { ... changes , ... updatedComputed } ; if ( replaceState ) { for ( const [ compPropName ] of Object . entries ( _computed ) ) { if ( ! ( compPropName in newState ) ) { delete _computed [ compPropName ] ; delete _computedPropDependencies [ compPropName ] ; } } for ( const [ watcherName ] of Object . entries ( _watchers ) ) { if ( ! ( watcherName in newState ) ) { unset ( _watchers , watcherName ) ; } } } set ( newState , replaceState ) ; for ( const [ propName , fn ] of Object . entries ( _watchers ) ) { let newVal = _get ( newState , propName ) , currentVal = _get ( currentState , propName ) ; if ( hasIn ( newState , propName ) && ! isEqual ( newVal , currentVal ) ) { logOperations && console . log ( `Triggering watcher: ${ propName }` ) ; fn . apply ( { set : _api . setState , get : _api . getState , api : _api } , [ newVal , currentVal ] ) ; } } if ( Object . keys ( updatedComputed ) . length > 0 && ! replaceState ) { setMiddleware ( [ updatedComputed , setSettings ] , _originalSetState , true ) ; } logOperations && ( _settings . logLevel as string ) === ( LogLevel . All as string ) && ! recursiveOp && console . log ( 'New State' , _api . getState ( ) ) ; logOperations && ! recursiveOp && console . groupEnd ( ) ; } const middlewareFunctions = ( middleware , store ) => ( middleware ? flow ( middleware ) ( store ) : store ) ; function configComputed ( computed ) { let computedToAdd = { } ; for ( const [ key , value ] of Object . entries ( computed ) ) { const deps = getDeps ( value ) ; if ( deps . length > 0 ) { _computedPropDependencies [ key ] = deps ; _computed [ key ] = value ; } computedToAdd = { ... computedToAdd , [ key ] : value . apply ( { ... _api . getState ( ) , ... computedToAdd } , [ { set : _api . setState , get : _api . getState } , ] ) } ; } if ( Object . keys ( computedToAdd ) . length > 0 ) { _api . setState ( computedToAdd ) ; } _computedMerged = true ; } function configWatchers ( watchers ) { for ( const [ propName , fn ] of Object . entries ( watchers ) ) { _watchers [ propName ] = fn ; } } function _stateCreator < TState extends State > ( set , get , api ) { _api = api ; _originalSetState = { ... api } . setState ; _api . setState = setState ; return { ... stateInitializer ( set as SetState < TState > | SetStateAddons < TState > , get , api ) } ; } const hook = create ( attachMiddleWare ( middlewareFunctions ( addons ?. middleware , _stateCreator ) ) ) ; const stateHook = ( selector , equalityFn ? ) => { if ( typeof selector === 'string' ) { if ( selector . indexOf ( ',' ) !== - 1 ) { const props = selector . split ( ',' ) . map ( part => part . trim ( ) ) ; return hook ( state => props . map ( prop => state [ prop ] ) , shallow ) ; } return hook ( state => state [ selector ] , equalityFn ) ; } return hook ( selector , equalityFn ) ; } ; Object . assign ( stateHook , _api ) ; configComputed ( addons ?. computed ?? { } ) ; configWatchers ( addons ?. watchers ?? { } ) ; return stateHook ; }