diff --git a/src/utils/index.js b/src/utils/index.js index 45f7afe..6e46e2a 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -3,7 +3,7 @@ import compose from './compose'; import deepMerge from './deepMerge'; import makeCancelable from './makeCancelable'; -import createStore from './store'; +import createState from './local-state'; import monaco from './monaco'; -export { noop, compose, deepMerge, makeCancelable, createStore, monaco }; +export { noop, compose, deepMerge, makeCancelable, createState, monaco }; diff --git a/src/utils/local-state/createState.js b/src/utils/local-state/createState.js new file mode 100644 index 0000000..f0ec89a --- /dev/null +++ b/src/utils/local-state/createState.js @@ -0,0 +1,82 @@ +import { compose, curry, isObject, isFunction, isEmpty } from './utils'; + +function createState(initial, handler = {}) { + validateInitial(initial); + validateHandler(handler); + + const state = { current: initial }; + + const didUpdate = curry(didStateUpdate)(state, handler); + const update = curry(updateState)(state); + const validate = curry(validateChanges)(initial); + const getChanges = curry(extractChanges)(state); + + function getState(selector = state => state) { + return selector(state.current); + } + + function setState(causedChanges) { + compose( + didUpdate, + update, + validate, + getChanges, + )(causedChanges); + } + + return [getState, setState]; +} + +function extractChanges(state, causedChanges) { + return isFunction(causedChanges) + ? causedChanges(state.current) + : causedChanges; +} + +function updateState(state, changes) { + state.current = { ...state.current, ...changes }; + + return changes; +} + +function didStateUpdate(state, handler, changes) { + isFunction(handler) + ? handler(state.current) + : Object.keys(changes) + .forEach(field => handler[field]?.(state.current[field])); + + return changes; +} + +function validateChanges(initial, changes) { + if (!isObject(changes)) errorHandler('changeType'); + if (Object.keys(changes).some(field => !initial.hasOwnProperty(field))) errorHandler('changeField'); + + return changes; +} + +function validateHandler(handler) { + if (!(isFunction(handler) || isObject(handler))) errorHandler('handlerType'); +} + +function validateInitial(initial) { + if (!initial) errorHandler('initialIsRequired'); + if (!isObject(initial)) errorHandler('initialType'); + if (isEmpty(initial)) errorHandler('initialContent'); +} + +const errorMessages = { + initialIsRequired: 'initial state is required', + initialType: 'initial state should be an object', + initialContent: 'initial state shouldn\'t be an empty object', + handlerType: 'handler should be an object or a function', + changeType: 'provided value of changes should be an object', + changeField: 'it seams you want to change a field in the store which is not specified in the "initial" state', + default: 'an unknown error has occurred in `local-storer` package', +}; + +function errorHandler(type) { + throw new Error(errorHandler[type] || errorMessages.default); +} + +export default createState; diff --git a/src/utils/local-state/index.js b/src/utils/local-state/index.js new file mode 100644 index 0000000..9b9f200 --- /dev/null +++ b/src/utils/local-state/index.js @@ -0,0 +1,3 @@ +import createState from './createState'; + +export default createState; diff --git a/src/utils/local-state/utils.js b/src/utils/local-state/utils.js new file mode 100644 index 0000000..b034a7f --- /dev/null +++ b/src/utils/local-state/utils.js @@ -0,0 +1,25 @@ +function compose(...fns) { + return x => fns.reduceRight((y, f) => f(y), x); +} + +function curry(fn) { + return function curried(...args) { + return args.length >= fn.length + ? fn.apply(this, args) + : (...nextArgs) => curried.apply(this, [...args, ...nextArgs]); + } +} + +function isObject(value) { + return ({}).toString.call(value).includes('Object'); +} + +function isEmpty(obj) { + return !Object.keys(obj).length; +} + +function isFunction(value) { + return typeof value === 'function'; +} + +export { compose, curry, isObject, isEmpty, isFunction }; diff --git a/src/utils/monaco.js b/src/utils/monaco.js index 058baf6..a9161e1 100644 --- a/src/utils/monaco.js +++ b/src/utils/monaco.js @@ -3,10 +3,10 @@ import { compose, deepMerge, makeCancelable, - createStore, + createState, } from '../utils'; -const [getState, setState] = createStore({ +const [getState, setState] = createState({ config: defaultConfig, isInitialized: false, configScriptSrc: null, diff --git a/src/utils/store.js b/src/utils/store.js deleted file mode 100644 index a8a998a..0000000 --- a/src/utils/store.js +++ /dev/null @@ -1,36 +0,0 @@ -function createStore(initial, handlers = {}) { - if (!initial) { - throw new Error('initial state is required'); - } - - const state = { current: initial }; - - function getState(selector = state => state) { - return selector(state.current); - } - - function setState(causedChanges) { - const changes = typeof causedChanges === 'function' - ? causedChanges(state.current) - : causedChanges; - - // check only in development - validateChanges(initial, changes); - Object.assign(state.current, changes); - Object.keys(changes).forEach(field => handlers[field] && handlers[field](changes[field])); - } - - return [getState, setState]; -} - -function validateChanges(initial, changes) { - if (typeof changes !== 'object') { - throw new Error('provided value of changes should be an object'); - } - - if (Object.keys(changes).some(field => !initial.hasOwnProperty(field))) { - throw new Error('It seams you want to change a field in the store which is not specified in the "initial" state'); - } -} - -export default createStore;