├── .babelrc ├── .gitbook └── assets │ └── redux-deduce.png ├── .gitignore ├── LICENSE ├── README.md ├── SUMMARY.md ├── api-reference.md ├── build ├── dedux.js └── dedux.js.map ├── core-concepts.md ├── package-lock.json ├── package.json ├── src ├── createSchema.js ├── functions │ ├── actionTypeParser.js │ ├── checkForEntities.js │ ├── findPath.js │ └── updateAtPath.js ├── index.js ├── normalizedDataCreation.js ├── normalizedDataReplacement.js ├── normalizedState.js ├── proxy.js ├── replace.js ├── symbols.js └── validators.js ├── test ├── Array │ ├── base.js │ ├── bool.js │ ├── number.js │ ├── object.js │ └── string.js ├── Object │ ├── array.js │ ├── base.js │ ├── bool.js │ ├── number.js │ ├── object.js │ ├── path.js │ └── string.js ├── action_creator.js ├── boolean.js ├── functions.js ├── index.js ├── number.js ├── string.js └── testForEntities.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["babel-plugin-add-module-exports", "transform-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /.gitbook/assets/redux-deduce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Redux-Deduce/redux-deduce/9324ccd68ae9a98447b8306b1e8eeccb78ade8ec/.gitbook/assets/redux-deduce.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_store 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Redux-Deduce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Official docs at [https://redux-deduce.gitbook.io/redux-deduce/](https://redux-deduce.gitbook.io/redux-deduce/) 4 | 5 | ![](.gitbook/assets/redux-deduce.png) 6 | 7 | ## Features 8 | 9 | Reduce your reducer boilerplate with Redux-deduce. 10 | 11 | * Perform common updates with our easy to use action creators without needing to write reducers. 12 | * Our deducer engine enhances your reducers, doing many simple tasks automatically. 13 | * Easy to add and remove from your project. 14 | * Just pass your root reducer through our decorating function then create your store. 15 | * All Redux and Redux devtools features remain intact 16 | * Time travel debugging with Redux. 17 | * Immutable updates. 18 | * Middleware integrations. 19 | 20 | ## Installation 21 | 22 | ```text 23 | npm install Redux-Deduce/redux-deduce 24 | ``` 25 | 26 | ## Basic Usage 27 | 28 | To enable redux-deduce you need to wrap your rootReducer with the 'deduce' reducer enhancer. 29 | 30 | ```javascript 31 | import {createStore} from 'redux'; 32 | import { deduce } from 'redux-deduce'; 33 | 34 | rootReducer = (state = {}, action) => { 35 | switch (action.type) { 36 | default: 37 | return state; 38 | } 39 | }; 40 | 41 | store = createStore(deduce(rootReducer)); 42 | ``` 43 | 44 | At this point, redux-deduce will now interpret any acceptable actions that are dispatched by Redux. Any custom actions or reducers that exist in your project take precedence and will run as normal. 45 | 46 | **For example:** 47 | 48 | Given the following state: 49 | 50 | ```javascript 51 | const state = { 52 | appState: { 53 | count: 0, 54 | isSelected: false 55 | } 56 | }; 57 | ``` 58 | 59 | We can update the app state using an action with the action type 'SET\_IN\_COUNT'. Redux-deduce will search the state-tree to find to find the correct update location and make an immutable update. 60 | 61 | ```javascript 62 | store.dispatch({ 63 | type: 'SET_COUNT', 64 | value: 5 65 | }); 66 | ``` 67 | 68 | Writing actions by hand is verbose and error-prone, so we introduce several action creators to help you validate and create actions that redux-deduce understands. 69 | 70 | ```javascript 71 | import { D } from 'redux-deduce'; 72 | 73 | store.dispatch(D.SET({ 74 | path: 'COUNT', 75 | value: 5 76 | })); 77 | ``` 78 | 79 | By using the action creators you get auto-complete in your IDE, better error message, and the error messages will indicate the line number that the action will be dispatched on instead of deep in the redux-deduce code. 80 | 81 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Introduction](README.md) 4 | * [Core Concepts](core-concepts.md) 5 | * [API Reference](api-reference.md) 6 | 7 | -------------------------------------------------------------------------------- /api-reference.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Top-level Exports 4 | 5 | * `deduce(rootReducer)` 6 | * `D.[ACTION METHOD]({ configuration })` 7 | 8 | ### deduce 9 | 10 | 11 | 12 | ## D.\[ACTION METHOD\]\_\[ \| IN \| ALL\] 13 | 14 | ## Primitive Actions 15 | 16 | ### SET 17 | 18 | Replace the root value in the state-tree. 19 | 20 | Works on: Strings, Numbers, Booleans, 21 | 22 | ```javascript 23 | // STATE = "" 24 | 25 | D.SET({ "value": "Ash Ketchum" }) 26 | 27 | // STATE = "Ash Ketchum" 28 | ``` 29 | 30 | ```javascript 31 | // STATE = 0 32 | 33 | D.SET({ "value": 4 }) 34 | 35 | // STATE = 4 36 | ``` 37 | 38 | ```javascript 39 | // STATE = false 40 | 41 | D.SET({ "value": true }) 42 | 43 | // STATE = true 44 | ``` 45 | 46 | Objects, Arrays 47 | 48 | ### INCREMENT 49 | 50 | ```javascript 51 | // STATE = 1 52 | 53 | D.INCREMENT({ "value": 1 }) 54 | 55 | // STATE = 2 56 | ``` 57 | 58 | ### DECREMENT 59 | 60 | ```javascript 61 | // STATE = 1 62 | 63 | D.INCREMENT({ "value": 1 }) 64 | 65 | // STATE = 0 66 | ``` 67 | 68 | ### SET\_ALL - Objects, Arrays 69 | 70 | #### Arrays 71 | 72 | ```java 73 | // STATE = ["0", "0", "0"] 74 | 75 | D.SET_ALL({ "value": "1" }) 76 | 77 | // STATE = ["1", "1", "1"] 78 | ``` 79 | 80 | ```java 81 | // STATE = [1, 2, 3] 82 | 83 | D.SET_ALL({ "value": 3 }) 84 | 85 | // STATE = [3, 3, 3] 86 | ``` 87 | 88 | ```java 89 | // STATE = [false, false, false] 90 | 91 | D.SET_ALL({ "value": true }) 92 | 93 | // STATE = [true, true, true] 94 | ``` 95 | 96 | #### Objects 97 | 98 | ```java 99 | /* 100 | STATE = { 101 | "Pokemon1": "dead", 102 | "Pokemon2": "dead", 103 | "Pokemon3": "dead", 104 | } 105 | */ 106 | 107 | D.SET_ALL({ "value": "alive" }) 108 | 109 | /* 110 | STATE = { 111 | "Pokemon1": "alive", 112 | "Pokemon2": "alive", 113 | "Pokemon3": "alive", 114 | } 115 | */ 116 | ``` 117 | 118 | ```java 119 | /* 120 | STATE = { 121 | "Score1": 300, 122 | "Score2": 400, 123 | "Score3": 200, 124 | } 125 | */ 126 | 127 | D.SET_ALL({ "value": 0 }) 128 | 129 | /* 130 | STATE = { 131 | "Pokemon1": 0, 132 | "Pokemon2": 0, 133 | "Pokemon3": 0, 134 | } 135 | */ 136 | ``` 137 | 138 | ```java 139 | /* 140 | STATE = { 141 | "Pokemon1": false, 142 | "Pokemon2": false, 143 | "Pokemon3": false, 144 | } 145 | */ 146 | 147 | D.SET_ALL({ "value": true }) 148 | 149 | /* 150 | STATE = { 151 | "Pokemon1": "alive", 152 | "Pokemon2": "alive", 153 | "Pokemon3": "alive", 154 | } 155 | */ 156 | ``` 157 | 158 | ### TOGGLE 159 | 160 | ```java 161 | // STATE = true 162 | 163 | D.TOGGLE({ "value": false }) 164 | 165 | // STATE = false 166 | ``` 167 | 168 | ### INSERT 169 | 170 | ```javascript 171 | // STATE = ["Pickachu", "Bulbasaur"] 172 | 173 | D.INSERT({ "value": "Squirtle", "index": 2 }) 174 | 175 | // STATE = ["Pickachu", "Bulbasaur", "Squirtle"] 176 | ``` 177 | 178 | ```javascript 179 | // STATE = [ 100, 500 ] 180 | 181 | D.INSERT({ "value": 400, "index": 2 }) 182 | 183 | // STATE = [ 100, 500, 400 ] 184 | ``` 185 | 186 | ```javascript 187 | // STATE = [ false, true ] 188 | 189 | D.INSERT({ "value": true, "index": 2 }) 190 | 191 | // STATE = [ false, true, true ] 192 | ``` 193 | 194 | ### REMOVE 195 | 196 | ```text 197 | // TO BE IMPLEMENTED 198 | ``` 199 | 200 | ### MERGE 201 | 202 | ```javascript 203 | /* 204 | STATE = { 205 | "Ash": "Online" 206 | } 207 | */ 208 | 209 | D.MERGE({ "value": { "Mindy": "Online" } }) 210 | 211 | /* 212 | STATE = { 213 | "Ash": "Online", 214 | "Mindy": "Online" 215 | } 216 | */ 217 | ``` 218 | 219 | ### CONCAT 220 | 221 | ```javascript 222 | /* 223 | STATE = [ "Ash" ] 224 | */ 225 | 226 | D.CONCAT({ "value": [ "Mindy" ] }) 227 | 228 | /* 229 | STATE = [ "Ash", "Mindy" ] 230 | */ 231 | ``` 232 | 233 | ## Using IN and ALL 234 | 235 | ### SET\_IN 236 | 237 | ```javascript 238 | // STATE = { } 239 | 240 | D.SET_IN({ "trainer": "Ash Ketchum" }) 241 | D.SET_IN({ "badges": 4 }) 242 | D.SET_IN({ "isBattling": true }) 243 | 244 | /* 245 | STATE = { 246 | "trainer": "Ash Ketchum", 247 | "badges": 4, 248 | "isBattling": true 249 | } 250 | */ 251 | ``` 252 | 253 | ### INCREMENT\_IN 254 | 255 | ```javascript 256 | // STATE = { "pokemon": 3 } 257 | 258 | D.INCREMENT_IN({ "path": "POKEMON", "value": 1}) 259 | 260 | // STATE = { "pokemon": 4 } 261 | ``` 262 | 263 | ### INCREMENT\_ALL 264 | 265 | ```javascript 266 | // STATE = [ 200, 400 ] 267 | 268 | D.INCREMENT_ALL({ "value": 100 }) 269 | 270 | // STATE = [ 300, 500 ] 271 | ``` 272 | 273 | ### DECREMENT\_IN 274 | 275 | ```javascript 276 | // STATE = { "pokemon": 4 } 277 | 278 | D.INCREMENT_IN({ "path": "POKEMON", "value": 1}) 279 | 280 | // STATE = { "pokemon": 3 } 281 | ``` 282 | 283 | ### DECREMENT\_ALL 284 | 285 | ```javascript 286 | // STATE = [ 200, 400 ] 287 | 288 | D.INCREMENT_ALL({ "value": 100 }) 289 | 290 | // STATE = [ 300, 500 ] 291 | ``` 292 | 293 | ### TOGGLE\_IN 294 | 295 | ```javascript 296 | // STATE = { "caught": false } 297 | 298 | D.TOGGLE_IN({ "path": "CAUGHT" }) 299 | 300 | // STATE = { "caught": true } 301 | ``` 302 | 303 | ### TOGGLE\_ALL 304 | 305 | ```javascript 306 | // STATE = [ false, false, false ] 307 | 308 | D.TOGGLE_IN({}) 309 | 310 | // STATE = [ true, true, true ] 311 | ``` 312 | 313 | ### ADD\_IN 314 | 315 | ```javascript 316 | // STATE = { "trainer": {} } 317 | 318 | D.ADD_IN({ "path": "TRAINER", "value": { "badges": 5 } }) 319 | 320 | // STATE 321 | ``` 322 | 323 | ### ADD 324 | 325 | ```javascript 326 | // STATE = [ "Mew", "Geodude", ] 327 | 328 | D.ADD({ "value": "Pickachu" }) 329 | 330 | // STATE = [ "Mew", "Geodude", "Pickachu" ] 331 | ``` 332 | 333 | ### INSERT\_IN 334 | 335 | ```javascript 336 | // STATE = { "pokemon": ["Pickachu", "Bulbasaur"] } 337 | 338 | D.INSERT_IN({ "value": "Squirtle", "index": 2, "path": "POKEMON" }) 339 | 340 | // STATE = { "pokemon": ["Pickachu", "Bulbasaur", "Squirtle"] } 341 | ``` 342 | 343 | ```javascript 344 | // STATE = { "scores": [ 100, 500 ] } 345 | 346 | D.INSERT_IN({ "value": 400, "index": 2, "path": "" }) 347 | 348 | // STATE = [ 100, 500, 400 ] 349 | ``` 350 | 351 | ```javascript 352 | // STATE = [ false, true ] 353 | 354 | D.INSERT_IN({ "value": true, "index": 2, "path": "POKEMON" }) 355 | 356 | // STATE = [ false, true, true ] 357 | ``` 358 | 359 | ### UPDATE\_IN 360 | 361 | // TO BE IMPLEMENT 362 | 363 | ### REMOVE\_IN 364 | 365 | ```javascript 366 | // STATE = [ "Wombat", "Golbat", "Geodude" ] 367 | 368 | D.REMOVE_IN({ 369 | "where": (v) => v == "Golbat" 370 | }) 371 | 372 | // STATE = [ "Wombat", "Geodude" ] 373 | ``` 374 | 375 | ### REMOVE\_ALL 376 | 377 | ```javascript 378 | // STATE = [ "Wombat", "Golbat", "Geodude" ] 379 | 380 | D.REMOVE_ALL({ }) 381 | 382 | // STATE = [ ] 383 | ``` 384 | 385 | ### MERGE\_IN 386 | 387 | ```javascript 388 | /* 389 | STATE = { 390 | "Ash": { 391 | "pokemon": [] 392 | }, 393 | "Mindy": { 394 | "pokemon": [], 395 | } 396 | } 397 | */ 398 | 399 | D.MERGE_IN({ "key": "Ash", "value": { "playing": true } }) 400 | 401 | /* 402 | STATE = { 403 | "Ash": { 404 | "pokemon": [], 405 | "playing": true 406 | }, 407 | "Mindy": { 408 | "pokemon": [], 409 | } 410 | } 411 | */ 412 | ``` 413 | 414 | ### MERGE\_ALL 415 | 416 | ```javascript 417 | /* 418 | STATE = { 419 | "Ash": { 420 | "pokemon": [] 421 | }, 422 | "Mindy": { 423 | "pokemon": [] 424 | } 425 | } 426 | */ 427 | 428 | D.MERGE_ALL({ "value": { "playing": true } }) 429 | 430 | /* 431 | STATE = { 432 | "Ash": { 433 | "pokemon": [], 434 | "playing": true 435 | }, 436 | "Mindy": { 437 | "pokemon": [], 438 | "playing": true 439 | } 440 | } 441 | */ 442 | ``` 443 | 444 | 445 | 446 | 447 | 448 | -------------------------------------------------------------------------------- /build/dedux.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("dedux",[],t):"object"==typeof exports?exports.dedux=t():e.dedux=t()}(window,function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},r.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=14)}([function(e,t,r){"use strict";var n=Symbol("id"),i=Symbol("foreign key"),o=Symbol("proxy"),u=Symbol("entity"),a=Symbol("get-normalized"),c=Symbol("get-schema"),f=Symbol("root data"),l=Symbol("replace");e.exports={entity:u,id:n,foreignKey:i,getNormalized:a,getSchema:c,getRootData:f,replace:l,proxy:o}},function(e,t,r){"use strict";var n=function(){return function(e,t){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return function(e,t){var r=[],n=!0,i=!1,o=void 0;try{for(var u,a=e[Symbol.iterator]();!(n=(u=a.next()).done)&&(r.push(u.value),!t||r.length!==t);n=!0);}catch(e){i=!0,o=e}finally{try{!n&&a.return&&a.return()}finally{if(i)throw o}}return r}(e,t);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},o=function(){function e(e,t){for(var r=0;r1&&void 0!==arguments[1]&&arguments[1];return"object"===o.typeOf(t)?!!o.isEntity(t)||Object.values(t).reduce(function(t,n){return t||e(n,r)},r):"array"===o.typeOf(t)&&t.reduce(function(t,n){return t||e(n,r)},r)}},function(e,t,r){"use strict";var n=function(){return function(e,t){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return function(e,t){var r=[],n=!0,i=!1,o=void 0;try{for(var u,a=e[Symbol.iterator]();!(n=(u=a.next()).done)&&(r.push(u.value),!t||r.length!==t);n=!0);}catch(e){i=!0,o=e}finally{try{!n&&a.return&&a.return()}finally{if(i)throw o}}return r}(e,t);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),i=Object.assign||function(e){for(var t=1;t=0;f--)if(s[f]!=y[f])return!1;for(f=s.length-1;f>=0;f--)if(l=s[f],!u(e[l],t[l],r))return!1;return typeof e==typeof t}(e,t,r))};function a(e){return null===e||void 0===e}function c(e){return!(!e||"object"!=typeof e||"number"!=typeof e.length)&&("function"==typeof e.copy&&"function"==typeof e.slice&&!(e.length>0&&"number"!=typeof e[0]))}},function(e,t,r){"use strict";var n=function(){function e(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:"data";return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this._proxyType=r,this._normalizedData=t,"data"===r?this._getProxy(t.getRoot()):"normalized"===r?this._getProxy(t.getNormalizedData()):"schema"===r?this._getProxy(t.getSchema()):void 0}return n(e,[{key:"_getProxy",value:function(e){if("data"!==this._proxyType)return this._createProxy(e);var t=this._normalizedData.getProxyFromCache(e);return t||(t=this._createProxy(e),this._normalizedData.setProxyInCache(e,t)),t}},{key:"_createProxy",value:function(t){var r=this,n=E[this._proxyType];return new Proxy(t,{get:function(t,i){return i===c?new e(r._normalizedData,"normalized"):i===f?new e(r._normalizedData,"schema"):i===l?r._getProxy(r._normalizedData.getRoot()):i===s?r._updateFn(t):n.includes(i)?r._lookupValue(t[i]):p.includes(i)?void 0:r._lookupValue(t[i])},ownKeys:function(e){var t=p.filter(function(e){return!n.includes(e)});return Reflect.ownKeys(e).filter(function(e){return!t.includes(e)})}})}},{key:"_updateFn",value:function(t){var r=this;return function(n){return new e(r._normalizedData.replace(t,n))}}},{key:"_lookupValue",value:function(e){var t=b.typeOf(e);if("object"!==t&&"array"!==t)return e;if("object"===t&&"data"===this._proxyType&&Object.getOwnPropertySymbols(e).includes(a)){var r=this._normalizedData.getEntity(e.entityType,e[a]);return this._getProxy(r)}return this._getProxy(e)}}]),e}();e.exports=function(e){var t=m(e),r=w(t,e),n=new g(r);return new _(n)}},function(e,t,r){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};e.exports=function(e,t){if(void 0===e)return[];var r=[];return function e(t,r,i,o){var u=arguments.length>4&&void 0!==arguments[4]?arguments[4]:"";if(""!==r&&(u+=(u?"_":"")+r.toUpperCase()),u===t||u.endsWith("_"+t))o(u);else{if("object"!==(void 0===i?"undefined":n(i))||Array.isArray(i))return;var a=!0,c=!1,f=void 0;try{for(var l,s=Object.keys(i)[Symbol.iterator]();!(a=(l=s.next()).done);a=!0){var y=l.value;e(t,y,i[y],o,u)}}catch(e){c=!0,f=e}finally{try{!a&&s.return&&s.return()}finally{if(c)throw f}}}}(e,"",t,function(e){return r.push(e)}),r}},function(e,t,r){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};e.exports=function e(t,r,i){if(0===t.length)return i(r);if("object"!==(void 0===r?"undefined":n(r)))return r;var o=Object.assign({},r),u=!0,a=!1,c=void 0;try{for(var f,l=Object.keys(r)[Symbol.iterator]();!(u=(f=l.next()).done);u=!0){var s=f.value;s.toUpperCase()===t[0]&&(o[s]=e(t.slice(1),r[s],i))}}catch(e){a=!0,c=e}finally{try{!u&&l.return&&l.return()}finally{if(a)throw c}}return o}},function(e,t,r){"use strict";e.exports=function(e){var t=void 0,r=void 0,n=!0,i=!1,o=void 0;try{for(var u,a=["SET_ALL","SET_IN","SET","INCREMENT_ALL","INCREMENT_IN","INCREMENT","DECREMENT_ALL","DECREMENT_IN","DECREMENT","TOGGLE_ALL","TOGGLE_IN","TOGGLE","ADD_TO","ADD","INSERT_IN","INSERT","REMOVE_ALL","REMOVE_FROM","REMOVE","MERGE_ALL","MERGE_IN","MERGE"][Symbol.iterator]();!(n=(u=a.next()).done);n=!0){var c=u.value;if(e.startsWith(c))return{verb:t=c,path:r=e.replace(t,"").replace("_","")}}}catch(e){i=!0,o=e}finally{try{!n&&a.return&&a.return()}finally{if(i)throw o}}return{verb:t,path:r}}},function(e,t,r){"use strict";var n=function(){function e(e,t){for(var r=0;r1)throw new Error("Path not unique, try a longer path specification.");return r[0].split("_")};function E(e,t,r){return void 0===t?e:Array.isArray(e)?r.index?[].concat(f(e.slice(0,r.index)),f(t),f(e.slice(r.index))):[].concat(f(e),f(t)):"object"===(void 0===e?"undefined":u(e))?Object.assign(o({},e),t):void 0}function b(e,t,r){if(Array.isArray(e))return e.map(function(e,n){return void 0===r||r(e,n)?t(e):e});if("object"===(void 0===e?"undefined":u(e))&&null!==e){var n={};return Object.entries(e).map(function(e){var o=i(e,2),u=o[0],a=o[1];void 0===r||r(u,a)?n[u]=t(u,a):n[u]=a}),n}}function m(e,t){if(Array.isArray(e))return void 0===t?[]:e.filter(function(e,r){return!t(e,r)});if("object"===(void 0===e?"undefined":u(e))&&null!==e){if(void 0===t)return{};var r={};return Object.entries(e).map(function(e){var n=i(e,2),o=n[0],u=n[1];t(o,u)||(r[o]=u)}),r}}function g(e,t){switch(t.type){case"ADD":return[].concat(f(e),[t.value]);case"CONCAT":return[].concat(f(e),f(t.value));case"REMOVE_ALL":return m(e);case"REMOVE_IN":if(void 0!==t.where)return m(e,t.where);if(void 0===t.index&&(t.index=e.length-1),void 0!==t.index)return m(e,function(e,r){return r===t.index});case"SET_IN":if(void 0!==t.index)return b(e,function(){return t.value},function(e,r){return r===t.index});if(void 0!==t.where)return b(e,function(){return t.value},t.where);case"SET_ALL":return b(e,function(){return t.value});case"TOGGLE_IN":if(void 0!==t.index)return b(e,function(e){return!e},function(e,r){return r===t.index});if(void 0!==t.where)return b(e,function(e){return!e},t.where);case"TOGGLE_ALL":return b(e,function(e){return!e});case"INCREMENT_IN":if(void 0!==t.index)return b(e,function(e){return e+t.value},function(e,r){return r===t.index});if(void 0!==t.where)return b(e,function(e){return e+t.value},t.where);case"DECREMENT_IN":if(void 0!==t.index)return b(e,function(e){return e-t.value},function(e,r){return r===t.index});if(void 0!==t.where)return b(e,function(e){return e-t.value},t.where);case"UPDATE_IN":return b(e,t.set,t.where);case"INSERT":return[].concat(f(e.slice(0,t.index)),[t.value],f(e.slice(t.index)));case"INCREMENT_ALL":return b(e,function(e){return e+t.value});case"DECREMENT_ALL":return b(e,function(e){return e-t.value});case"MERGE_ALL":return b(e,function(e){return Object.assign(o({},e),t.value)});case"MERGE_IN":if(void 0!==t.index)return b(e,function(e){return Object.assign(o({},e),t.value)},function(e,r){return r===t.index});if(void 0!==t.where)return b(e,function(e){return Object.assign(o({},e),t.value)},t.where);default:return e}}function w(e,t){var r=l(t.type),n=r.verb,a=r.path,y={};if(a){if("SET_IN"===n)return void 0!==t.key?s(p(a,e),e,function(e){return o({},e,c({},t.key,t.value))}):void 0!==t.index?s(p(a,e),e,function(e){return b(e,function(){return t.value},function(e,r){return r===t.index})}):void 0!==t.where?s(p(a,e),e,function(e){return b(e,function(){return t.value},t.where)}):s(p(a,e),e,function(){return t.value});if("INCREMENT_ALL"===n)return s(p(a,e),e,function(e){return b(e,function(e){return e+t.value})});if("INCREMENT_IN"===n){if(void 0!==t.index)return s(p(a,e),e,function(e){return b(e,function(e){return e+t.value},function(e,r){return r===t.index})});if(void 0!==t.where)return s(p(a,e),e,function(e){return b(e,function(e){return e+t.value},t.where)})}if("DECREMENT_ALL"===n)return s(p(a,e),e,function(e){return b(e,function(e){return e-t.value})});if("DECREMENT_IN"===n){if(void 0!==t.index)return s(p(a,e),e,function(e){return b(e,function(e){return e-t.value},function(e,r){return r===t.index})});if(void 0!==t.where)return s(p(a,e),e,function(e){return b(e,function(e){return e-t.value},t.where)})}if("INCREMENT"===n)return s(p(a,e),e,function(e){return e+t.value});if("DECREMENT"===n)return s(p(a,e),e,function(e){return e-t.value});if("TOGGLE"===n)return s(p(a,e),e,function(e){return!e});if("TOGGLE_IN"===n){if(void 0!==t.index)return s(p(a,e),e,function(e){return b(e,function(e){return!e},function(e,r){return r===t.index})});if(void 0!==t.key)return s(p(a,e),e,function(e){return b(e,function(e){return!e},function(e,r){return e===t.key})});if(void 0!==t.where)return s(p(a,e),e,function(e){return b(e,function(e){return!e},t.where)})}if("MERGE"===n)return s(p(a,e),e,function(e){return Object.assign(o({},e),t.value)});if("MERGE_IN"===n)return s(p(a,e),e,function(e){if(Array.isArray(e)){if(t.index){var r=[].concat(f(e));return r[t.index]=Object.assign(o({},r[t.index]),t.value),r}if(t.where)return e.map(function(e){return t.where(e)?"object"===(void 0===e?"undefined":u(e))?Object.assign(o({},e),t.value):t.value:e})}if("object"===(void 0===e?"undefined":u(e))){if(void 0!==t.key&&"object"===u(t.value))return o({},e,c({},t.key,Object.assign(o({},e[t.key]),t.value)));if(void 0!==t.key)return o({},e,c({},t.key,t.value));if(void 0!==t.where)return Object.entries(e).forEach(function(e){var r=i(e,2),n=r[0],u=r[1];t.where(n,u)?y[n]=Object.assign(o({},u),t.value):y[n]=o({},u)}),y}});if("MERGE_ALL"===n)return s(p(a,e),e,function(e){return Object.entries(e).forEach(function(e){var r=i(e,2),n=r[0],u=r[1];Array.isArray(u)?y[n]=[].concat(f(u)):y[n]=Object.assign(o({},u),t.value)}),y});if("ADD_TO"===n)return s(p(a,e),e,function(e){return[].concat(f(e),[t.value])});if("INSERT_IN"===n)return s(p(a,e),e,function(e){return[].concat(f(e.slice(0,t.index)),[t.value],f(e.slice(t.index)))});if("REMOVE_ALL"===n)return s(p(a,e),e,function(e){return m(e)});if("REMOVE_FROM"===n){if(void 0!==t.index)return s(p(a,e),e,function(e){return m(e,function(e,r){return r===t.index})});if(void 0!==t.where)return s(p(a,e),e,function(e){return m(e,t.where)})}if("SET_ALL"===n)return s(p(a,e),e,function(e){return b(e,function(e){return t.value})});if("TOGGLE_ALL"===n)return s(p(a,e),e,function(e){return b(e,function(e){return!e})})}switch(t.type){case"SET_ALL":return b(e,function(){return t.value});case"SET_IN":if(void 0!==t.key)return E(e,c({},t.key,t.value));if(void 0!==t.where)return b(e,function(){return t.value},t.where);case"MERGE_IN":if(void 0!==t.key)return b(e,function(e,r){return Object.assign(o({},r),t.value)},function(e,r){return e===t.key});if(void 0!==t.where)return b(e,function(e,r){return Object.assign(o({},r),t.value)},t.where);case"MERGE_ALL":return b(e,function(e,r){return Object.assign(o({},r),t.value)});case"MERGE":return E(e,t.value);case"REMOVE_ALL":return m(e);case"REMOVE_IN":if(void 0!==t.key)return m(e,function(e,r){return e===t.key});if(void 0!==t.where)return m(e,t.where);case"INCREMENT_IN":if(void 0!==t.key)return b(e,function(e,r){return r+t.value},function(e){return e===t.key});if(void 0!==t.where)return b(e,function(e,r){return r+t.value},t.where);case"DECREMENT_IN":if(void 0!==t.key)return b(e,function(e,r){return r-t.value},function(e){return e===t.key});if(void 0!==t.where)return b(e,function(e,r){return r-t.value},t.where);case"TOGGLE_IN":if(void 0!==t.key)return b(e,function(e,t){return!t},function(e){return e===t.key});if(void 0!==t.where)return b(e,function(e,t){return!t},t.where);case"TOGGLE_ALL":return b(e,function(e,t){return!t});default:return e}}function _(e,t,r,n){if(!t)throw new Error("All actions need to have a configuration object.");!function(e,t){if(e){var r=y(e,t);if(0===r.length)throw new Error("Path: "+e+" could not be found.");if(r.length>1)throw new Error("Multiple valid paths found. Use a longer path to ensure a unique selection.")}}(t.path,n),function(e,t,r){if(t){if(r.includes("value")&&!("value"in t))throw new Error(e+" should include a value property in the payload.");if(r.includes("in")&&void 0===t.index&&void 0===t.key&&void 0===t.where)throw new Error(e+" should include either a key, index, or where property in the payload.")}}(e,t,r);var i=t.path;return delete t.path,i?Object.assign({type:e+"_"+i},t):Object.assign({type:e},t)}var O=function(){function e(t){a(this,e),this.closedState=t}return n(e,[{key:"SET_ALL",value:function(e){return _("SET_ALL",e,["value"],this.closedState.state)}},{key:"SET_IN",value:function(e){return _("SET_IN",e,["value","in"],this.closedState.state)}},{key:"SET",value:function(e){return _("SET",e,["value"],this.closedState.state)}},{key:"INCREMENT_ALL",value:function(e){return _("INCREMENT_ALL",e,["value"],this.closedState.state)}},{key:"INCREMENT_IN",value:function(e){return _("INCREMENT_IN",e,["value","in"],this.closedState.state)}},{key:"INCREMENT",value:function(e){return _("INCREMENT",e,["value"],this.closedState.state)}},{key:"DECREMENT_ALL",value:function(e){return _("DECREMENT_ALL",e,["value"],this.closedState.state)}},{key:"DECREMENT_IN",value:function(e){return _("DECREMENT_IN",e,["value","in"],this.closedState.state)}},{key:"DECREMENT",value:function(e){return _("DECREMENT",e,["value"],this.closedState.state)}},{key:"TOGGLE_ALL",value:function(e){return _("TOGGLE_ALL",e,[],this.closedState.state)}},{key:"TOGGLE_IN",value:function(e){return _("TOGGLE_IN",e,["in"],this.closedState.state)}},{key:"TOGGLE",value:function(e){return _("TOGGLE",e,[],this.closedState.state)}},{key:"ADD_TO",value:function(e){return _("ADD_TO",e,["value"],this.closedState.state)}},{key:"ADD",value:function(e){return _("ADD",e,["value"],this.closedState.state)}},{key:"INSERT_IN",value:function(e){return _("INSERT_IN",e,["value","in"],this.closedState.state)}},{key:"INSERT",value:function(e){return _("INSERT",e,["value"],this.closedState.state)}},{key:"UPDATE_IN",value:function(e){return _("UPDATE_IN",e,["in"],this.closedState.state)}},{key:"REMOVE_ALL",value:function(e){return _("REMOVE_ALL",e,[],this.closedState.state)}},{key:"REMOVE_IN",value:function(e){return _("REMOVE_IN",e,["in"],this.closedState.state)}},{key:"REMOVE",value:function(e){return _("REMOVE",e,[],this.closedState.state)}},{key:"MERGE_ALL",value:function(e){return _("MERGE_ALL",e,["value"],this.closedState.state)}},{key:"MERGE_IN",value:function(e){return _("MERGE_IN",e,["value","in"],this.closedState.state)}},{key:"MERGE",value:function(e){return _("MERGE",e,["value"],this.closedState.state)}}]),e}(),T=new function e(){var t=this;a(this,e),this.closedState={},this.deduce=function(e){var r=e(void 0,{}),n=d(r),i=n&&v(r);return function(r,o){var a=e(r,o);return i=n?i[h](a):a,t.closedState.state=i,i!==r?i:"number"==typeof r?(t.closedState.state=function(e,t){switch(t.type){case"INCREMENT":return e+t.value;case"DECREMENT":return e-t.value;case"SET":return t.value;default:return e}}(r,o),t.closedState.state):"boolean"==typeof r?(t.closedState.state=function(e,t){switch(t.type){case"SET":return t.value;case"TOGGLE":return!e;default:return e}}(r,o),t.closedState.state):"string"==typeof r?(t.closedState.state=function(e,t){switch(t.type){case"SET":return t.value;default:return e}}(r,o),t.closedState.state):Array.isArray(r)?(t.closedState.state=g(r,o),t.closedState.state):"object"===(void 0===r?"undefined":u(r))&&null!==r?(t.closedState.state=w(r,o),t.closedState.state):void 0}},this.actions=new O(this.closedState)};e.exports={deduce:T.deduce,D:T.actions}}])}); 2 | //# sourceMappingURL=dedux.js.map -------------------------------------------------------------------------------- /build/dedux.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack://dedux/webpack/universalModuleDefinition","webpack://dedux/webpack/bootstrap","webpack://dedux/./src/symbols.js","webpack://dedux/./src/validators.js","webpack://dedux/./src/functions/checkForEntities.js","webpack://dedux/./src/normalizedDataCreation.js","webpack://dedux/./src/normalizedDataReplacement.js","webpack://dedux/./src/normalizedState.js","webpack://dedux/./src/createSchema.js","webpack://dedux/./node_modules/deep-equal/lib/is_arguments.js","webpack://dedux/./node_modules/deep-equal/lib/keys.js","webpack://dedux/./node_modules/deep-equal/index.js","webpack://dedux/./src/proxy.js","webpack://dedux/./src/functions/findPath.js","webpack://dedux/./src/functions/updateAtPath.js","webpack://dedux/./src/functions/actionTypeParser.js","webpack://dedux/./src/index.js"],"names":["root","factory","exports","module","define","amd","window","installedModules","__webpack_require__","moduleId","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","configurable","enumerable","get","r","value","n","__esModule","object","property","prototype","hasOwnProperty","p","s","id","Symbol","foreignKey","proxy","entity","getNormalized","getSchema","getRootData","replace","deepEqual","Validator","_classCallCheck","this","_entity","type","_typeof","Array","isArray","data","typeOf","entries","map","e","val","obj","isEntity","keysAndSymbols","getOwnPropertySymbols","concat","getOwnPropertyNames","length","tag","validateStringTag","Error","JSON","stringify","toUpperCase","includes","entity1","entity2","strict","entities","forEach","_ref","_ref2","_slicedToArray","key","undefined","V","checkForEntities","state","hasEntities","arguments","values","reduce","acc","NormalizedDataCreator","schema","rootEntity","_schema","_rootEntity","_normalized","newTables","Reflect","ownKeys","entityType","getName","newId","_defineProperty","proxyData","proxyId","_extends","normalizedData","normalizer","process","collection","parents","path","elements","getElements","isVacentTag","addToTable","addProxy","_ref3","_ref4","getNormalizedData","secretSymbols","NormalizedDataReplacement","oldNormalizedData","oldRoot","_old","_new","_oldRoot","_proxyRemovalList","Set","fk","target","newEntity","primaryKey","oldValue","newValue","merge","addToProxyRemovalList","_path","_toArray","remainingPath","slice","merged","firstHalf","secondHalf","_toConsumableArray","insertAt","mergeAtPath","addNewEntity","_this","nextEntity","getEntity","filter","add","_this2","getProxyParents","pId","has","addParentProxies","keys","proxiesToRemove","_iteratorNormalCompletion","_didIteratorError","_iteratorError","_step","_iterator","iterator","next","done","err","return","newProxyTable","table","_iteratorNormalCompletion2","_didIteratorError2","_iteratorError2","_step2","_iterator2","_this3","_ref5","_ref6","tableName","newNorm","_newNorm$getPath2","getPath","_newNorm$getPath2$","entityId","targetEntity","clearProxies","mergeInOldData","getNewData","getNewRoot","NormalizedState","proxObj","_replace2","SchemaCreator","entityObj","validateTag","checkForInconsistencies","vacantEntity","tables","checkForUndefinedEntities","schemaCreator","addVacantType","addType","supportsArgumentsClass","toString","supported","unsupported","propertyIsEnumerable","shim","push","pSlice","objectKeys","isArguments","actual","expected","opts","Date","getTime","a","b","isUndefinedOrNull","isBuffer","ka","kb","sort","objEquiv","x","copy","dataProxy","schemaProxy","normalizedProxy","allowedKeyList","normalized","createSchema","normalizeState","StateProxy","proxyType","_proxyType","_normalizedData","_getProxy","getRoot","_createProxy","cachedProxy","getProxyFromCache","setProxyInCache","allowedKeys","Proxy","prop","_updateFn","_lookupValue","hiddenKeys","linkedEntity","normalData","paths","dfs","prevKey","callback","endsWith","updateAtPath","newObj","assign","actionType","verb","action","startsWith","actionTypeParser","findPath","processState","split","insert","index","update","predicate","elem","idx","del","switch_array","where","el","set","switch_object","_actionTypeParser","arr","number","bool","k","v","newState","subObj","_ref7","_ref8","handleAction","config","required","checkPath","payload","validatePayload","Actions","closedState","container","Container","deduce","reducer","initialState","shouldNormalize","proxied","switch_number","switch_boolean","switch_string","actions","D"],"mappings":"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","file":"dedux.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine(\"dedux\", [], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"dedux\"] = factory();\n\telse\n\t\troot[\"dedux\"] = factory();\n})(window, function() {\nreturn "," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 14);\n","//internal symbols\nconst id = Symbol('id')\nconst foreignKey = Symbol('foreign key')\nconst proxy = Symbol('proxy')\n\n//tagging symbol\nconst entity = Symbol('entity')\n\n//data access symbols\nconst getNormalized = Symbol('get-normalized')\nconst getSchema = Symbol('get-schema')\nconst getRootData = Symbol('root data')\n\n//method access symbols\nconst replace = Symbol('replace')\n\nmodule.exports = {\n entity,\n id,\n foreignKey,\n getNormalized,\n getSchema,\n getRootData,\n replace,\n proxy,\n}\n","const deepEqual = require('deep-equal')\n\nclass Validator {\n constructor(entity) {\n this._entity = entity\n }\n\n typeOf(value) {\n const type = typeof value\n if (type !== 'object') return type\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'array'\n return 'object'\n }\n\n getElements(data) {\n if (this.typeOf(data) === 'object') return Object.entries(data)\n else if (this.typeOf(data) === 'array') return data.map((e, i) => [i, e])\n }\n\n getName(name) {\n if (this.typeOf(name) === 'string') return name\n else return (name.name)\n }\n\n isEntity(val) {\n return !!(this.typeOf(val) === 'object' && val[this._entity])\n }\n\n isVacentTag(obj) {\n if (!this.isEntity(obj)) return false\n const keysAndSymbols = Object.getOwnPropertySymbols(obj).concat(Object.getOwnPropertyNames(obj))\n return keysAndSymbols.length === 1 && keysAndSymbols[0] === this._entity\n }\n\n validateTag(tag) {\n if (this.typeOf(tag) === 'string') {\n return validateStringTag(tag)\n } else if (this.typeOf(tag) === 'object') {\n if (this.typeOf(tag.name) === 'string') return validateStringTag(tag.name)\n else throw new Error(`Invalid Tag. \\n\n ${JSON.stringify(tag)} \\n\n Tag must contain a 'name' property with a string value`)\n }\n throw new Error(`Invalid Tag. \\n\n ${tag} \\n\n Tag must be a string or an object with a 'name' property and a string value`)\n\n function validateStringTag(tag) {\n if (tag[0].toUpperCase() === tag[0]) return\n const validPrimitives = [\n 'number',\n 'string',\n 'boolean',\n 'function',\n 'null',\n 'undefined',\n 'object',\n 'array'\n ]\n if (!validPrimitives.includes(tag))\n throw new Error(`Invalid Tag. ${tag} is not a valid primitive.`)\n }\n }\n\n checkForInconsistencies(entity1, entity2, name) {\n if (!entity1 || !entity2) return\n if (!deepEqual(entity1, entity2, { strict: true }))\n throw new Error(`Conflicting Schema definitions for ${name}. \\n\n ${JSON.stringify(entity1)} \\n\n does not match \\n\n ${JSON.stringify(entity2)}`)\n }\n\n checkForUndefinedEntities(entities) {\n Object.entries(entities).forEach(([key, value]) => {\n if (value === undefined) throw new Error(`Could not infer Schema on ${key}`)\n })\n }\n}\n\n\nmodule.exports = Validator","const { replace, entity } = require('../symbols')\nconst Validator = require('../validators')\nconst V = new Validator(entity)\n\nfunction checkForEntities(state, hasEntities = false) {\n if (V.typeOf(state) === 'object') {\n if (V.isEntity(state)) return true\n return Object.values(state).reduce((acc, value) => {\n return acc || checkForEntities(value, hasEntities)\n }, hasEntities)\n }\n if (V.typeOf(state) === 'array') {\n return state.reduce((acc, value) => {\n return acc || checkForEntities(value, hasEntities)\n }, hasEntities)\n }\n else return false\n}\n\nmodule.exports = checkForEntities","const { entity, id, foreignKey, proxy } = require('./symbols')\nconst Validator = require('./validators')\nconst V = new Validator(entity)\n\nclass NormalizedDataCreator {\n constructor(schema, rootEntity) {\n this._schema = schema\n this._rootEntity = rootEntity\n this._normalized = newTables(schema)\n }\n\n addToTable(data) {\n const entityType = V.getName(data[entity])\n const newId = this._schema[entityType][id]++\n data[id] = newId\n this._normalized[entityType][newId] = data\n\n return { [foreignKey]: newId, entityType }\n }\n\n addProxy(data, proxyData) {\n const proxyId = this._schema[proxy][id]++\n data[proxy] = proxyId\n this._normalized[proxy][proxyId] = { ...proxyData, [id]: proxyId }\n return proxyId\n }\n\n getNormalizedData() {\n return {\n normalizedData: this._normalized,\n rootEntity: this._rootEntity,\n schema: this._schema,\n }\n }\n}\n\nfunction newTables(schema) {\n let newTables = {}\n Reflect.ownKeys(schema).forEach(name => newTables[name] = {})\n return newTables\n}\n\n//exported\nfunction normalizeState(schema, state) {\n const normalizer = new NormalizedDataCreator(schema, state) //performs side effects\n process(state, { parents: {}, path: [] })\n return normalizer.getNormalizedData()\n\n function process(state, { parents, path }) {\n const elements = V.getElements(state)\n if (!elements) return state\n if (isVacantArray(state)) return []\n const foreignKey = V.isEntity(state) && normalizer.addToTable(state) //side effect\n if (foreignKey) path = [foreignKey]\n const proxyId = normalizer.addProxy(state, { parents, path }) //side effect\n\n parents = { ...parents, [proxyId]: true }\n elements.forEach(([key, value]) => {\n state[key] = process(value, { parents, path: [...path, key] })\n })\n\n return foreignKey || state\n }\n}\n\nfunction isVacantArray(collection) {\n return V.typeOf(collection === 'array') && V.isVacentTag(collection[0])\n}\n\nmodule.exports = normalizeState\n","const { entity, id, foreignKey, proxy } = require('./symbols')\nconst secretSymbols = [entity, id, foreignKey, proxy]\nconst Validator = require('./validators')\nconst V = new Validator(entity)\n\n\nclass NormalizedDataReplacement {\n constructor(oldNormalizedData, oldRoot) {\n this._old = oldNormalizedData\n this._new = {}\n this._oldRoot = oldRoot\n this._proxyRemovalList = new Set()\n }\n\n getEntity(entityType, fk) {\n return this._old[entityType][fk]\n }\n\n getPath(target) {\n const proxyId = target[proxy]\n return this._old[proxy][proxyId].path\n }\n\n addNewEntity(newEntity) {\n const type = V.getName(newEntity[entity])\n const primaryKey = newEntity[id]\n if (!this._new[type]) this._new[type] = {}\n this._new[type][primaryKey] = newEntity\n }\n\n mergeAtPath(path, oldValue, newValue) {\n if (path.length === 0) return this.merge(oldValue, newValue)\n this.addToProxyRemovalList(oldValue)\n let [key, ...remainingPath] = path\n const merged = insertAt(key, oldValue, this.mergeAtPath(remainingPath, oldValue[key], newValue))\n if (V.isEntity(merged)) this.addNewEntity(merged)\n else return merged\n }\n\n merge(oldValue, newValue) {\n if (isForeignKey(oldValue)) {\n const nextEntity = this.getEntity(oldValue.entityType, oldValue[foreignKey])\n this.merge(nextEntity, newValue)\n return oldValue\n }\n\n const elements = V.getElements(newValue)\n if (!elements) return newValue\n if (this.getEntity(proxy, oldValue[proxy]).proxy === newValue) {\n return oldValue\n }\n const ownSecretSymbols = Object.getOwnPropertySymbols(oldValue).filter(s => secretSymbols.includes(s))\n ownSecretSymbols.forEach(s => newValue[s] = oldValue[s])\n elements.forEach(\n ([key, value]) => newValue[key] = this.merge(oldValue[key], value)\n )\n this.addToProxyRemovalList(newValue)\n if (V.isEntity(newValue)) this.addNewEntity(newValue)\n else return newValue\n\n }\n\n addToProxyRemovalList(entity) {\n this._proxyRemovalList.add(entity[proxy])\n }\n\n addParentProxies(proxyId) {\n const parents = this.getProxyParents(proxyId)\n parents.forEach(pId => {\n if (!this._proxyRemovalList.has(pId)) {\n this._proxyRemovalList.add(pId)\n this.addParentProxies(pId)\n }\n })\n }\n\n getProxyParents(proxyId) {\n const parents = this._old[proxy][proxyId].parents\n return Object.keys(parents)\n }\n\n clearProxies() {\n let proxiesToRemove = this._proxyRemovalList.values()\n for (let pId of proxiesToRemove) this.addParentProxies(pId)\n proxiesToRemove = this._proxyRemovalList.values()\n const newProxyTable = {}\n Object.entries(this._old[proxy]).forEach(([key, table]) => newProxyTable[key] = { ...table })\n for (let pId of proxiesToRemove) newProxyTable[pId].proxy = undefined\n this._new[proxy] = newProxyTable\n }\n\n mergeInOldData() {\n const tables = Object.entries(this._old)\n tables.forEach(([tableName, table]) => {\n if (!this._new[tableName]) this._new[tableName] = table\n else this._new[tableName] = { ...table, ...this._new[tableName] }\n })\n }\n\n getNewRoot() {\n const type = this._oldRoot[entity]\n const primaryKey = this._oldRoot[id]\n return this._new[type][primaryKey]\n }\n\n getNewData() {\n return this._new\n }\n}\n\n//export\nfunction replace(oldNormalizedData, oldRoot, target, value) {\n const newNorm = new NormalizedDataReplacement(oldNormalizedData, oldRoot)\n const [{ [foreignKey]: entityId, entityType }, ...path] = newNorm.getPath(target)\n const targetEntity = newNorm.getEntity(entityType, entityId)\n newNorm.mergeAtPath(path, targetEntity, value)\n newNorm.clearProxies()\n newNorm.mergeInOldData()\n const newNormData = newNorm.getNewData()\n const newRoot = newNorm.getNewRoot()\n return { normalizedData: newNormData, rootEntity: newRoot }\n}\n\nfunction insertAt(key, collection, value) {\n if (V.typeOf(collection) === 'object') return { ...collection, [key]: value }\n else if (V.typeOf(collection) === 'array') {\n const firstHalf = collection.slice(0, key)\n const secondHalf = collection.slice(key + 1)\n return [...firstHalf, value, ...secondHalf]\n }\n}\n\nfunction isForeignKey(data) {\n if (V.typeOf(data) !== 'object') return false\n const syms = Object.getOwnPropertySymbols(data)\n return syms.includes(foreignKey)\n}\n\nmodule.exports = replace","const { entity, id, foreignKey, proxy } = require('./symbols')\nconst Validator = require('./validators')\nconst V = new Validator(entity)\nconst replace = require('./normalizedDataReplacement')\n\n\nclass NormalizedState {\n constructor({ schema, normalizedData, rootEntity }) {\n this.schema = schema\n this.normalizedData = normalizedData\n this.rootEntity = rootEntity\n }\n\n getEntity(type, primaryKey) {\n return this.normalizedData[type][primaryKey]\n }\n\n getSchema() {\n return this.schema\n }\n\n getRoot() {\n return this.rootEntity\n }\n\n getNormalizedData() {\n return this.normalizedData\n }\n\n getProxyFromCache(obj) {\n const primaryKey = obj[proxy]\n const proxyRecord = this.normalizedData[proxy][primaryKey]\n return proxyRecord.proxy\n }\n\n setProxyInCache(obj, proxObj) {\n const primaryKey = obj[proxy]\n const proxyRecord = this.normalizedData[proxy][primaryKey]\n proxyRecord.proxy = proxObj\n }\n\n replace(target, value) {\n const { normalizedData, rootEntity } = replace(this.normalizedData, this.rootEntity, target, value)\n const schema = this.schema\n return new NormalizedState({ normalizedData, rootEntity, schema })\n }\n}\n\nmodule.exports = NormalizedState","const { entity, id, proxy } = require('./symbols')\nconst Validator = require('./validators')\nconst V = new Validator(entity)\n\nclass SchemaCreator {\n constructor() {\n this._schema = { [proxy]: {} }\n }\n\n addType(entityObj) {\n const tag = entityObj[entity]\n V.validateTag(tag)\n const name = V.getName(tag)\n delete entityObj[entity]\n if (!this._schema[name]) this._schema[name] = entityObj\n else V.checkForInconsistencies(entityObj, this.schema[name], name)\n return name\n }\n\n addVacantType(vacantEntity) {\n const tag = vacantEntity[entity]\n V.validateTag(tag)\n const name = V.getName(tag)\n if (!this._schema[name]) this._schema[name] = undefined\n return name\n }\n\n getSchema() {\n const tables = Reflect.ownKeys(this._schema)\n V.checkForUndefinedEntities(this._schema)\n tables.forEach(type => this._schema[type][id] = 0)\n return this._schema\n }\n}\n\nfunction createSchema(state) {\n const schemaCreator = new SchemaCreator()\n if (!state[entity]) state[entity] = 'ROOT'\n process(state)\n return schemaCreator.getSchema()\n\n\n function process(state) {\n if (V.typeOf(state) === 'array') return [process(state[0])]\n if (V.typeOf(state) === 'object') {\n if (V.isVacentTag(state)) return schemaCreator.addVacantType(state)\n const obj = { ...state }\n Object.entries(obj).forEach(\n ([key, value]) => obj[key] = process(value)\n )\n return V.isEntity(obj) ? schemaCreator.addType(obj) : obj\n } else return V.typeOf(state)\n }\n}\n\nmodule.exports = createSchema","var supportsArgumentsClass = (function(){\n return Object.prototype.toString.call(arguments)\n})() == '[object Arguments]';\n\nexports = module.exports = supportsArgumentsClass ? supported : unsupported;\n\nexports.supported = supported;\nfunction supported(object) {\n return Object.prototype.toString.call(object) == '[object Arguments]';\n};\n\nexports.unsupported = unsupported;\nfunction unsupported(object){\n return object &&\n typeof object == 'object' &&\n typeof object.length == 'number' &&\n Object.prototype.hasOwnProperty.call(object, 'callee') &&\n !Object.prototype.propertyIsEnumerable.call(object, 'callee') ||\n false;\n};\n","exports = module.exports = typeof Object.keys === 'function'\n ? Object.keys : shim;\n\nexports.shim = shim;\nfunction shim (obj) {\n var keys = [];\n for (var key in obj) keys.push(key);\n return keys;\n}\n","var pSlice = Array.prototype.slice;\nvar objectKeys = require('./lib/keys.js');\nvar isArguments = require('./lib/is_arguments.js');\n\nvar deepEqual = module.exports = function (actual, expected, opts) {\n if (!opts) opts = {};\n // 7.1. All identical values are equivalent, as determined by ===.\n if (actual === expected) {\n return true;\n\n } else if (actual instanceof Date && expected instanceof Date) {\n return actual.getTime() === expected.getTime();\n\n // 7.3. Other pairs that do not both pass typeof value == 'object',\n // equivalence is determined by ==.\n } else if (!actual || !expected || typeof actual != 'object' && typeof expected != 'object') {\n return opts.strict ? actual === expected : actual == expected;\n\n // 7.4. For all other Object pairs, including Array objects, equivalence is\n // determined by having the same number of owned properties (as verified\n // with Object.prototype.hasOwnProperty.call), the same set of keys\n // (although not necessarily the same order), equivalent values for every\n // corresponding key, and an identical 'prototype' property. Note: this\n // accounts for both named and indexed properties on Arrays.\n } else {\n return objEquiv(actual, expected, opts);\n }\n}\n\nfunction isUndefinedOrNull(value) {\n return value === null || value === undefined;\n}\n\nfunction isBuffer (x) {\n if (!x || typeof x !== 'object' || typeof x.length !== 'number') return false;\n if (typeof x.copy !== 'function' || typeof x.slice !== 'function') {\n return false;\n }\n if (x.length > 0 && typeof x[0] !== 'number') return false;\n return true;\n}\n\nfunction objEquiv(a, b, opts) {\n var i, key;\n if (isUndefinedOrNull(a) || isUndefinedOrNull(b))\n return false;\n // an identical 'prototype' property.\n if (a.prototype !== b.prototype) return false;\n //~~~I've managed to break Object.keys through screwy arguments passing.\n // Converting to array solves the problem.\n if (isArguments(a)) {\n if (!isArguments(b)) {\n return false;\n }\n a = pSlice.call(a);\n b = pSlice.call(b);\n return deepEqual(a, b, opts);\n }\n if (isBuffer(a)) {\n if (!isBuffer(b)) {\n return false;\n }\n if (a.length !== b.length) return false;\n for (i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n }\n try {\n var ka = objectKeys(a),\n kb = objectKeys(b);\n } catch (e) {//happens when one is a string literal and the other isn't\n return false;\n }\n // having the same number of owned properties (keys incorporates\n // hasOwnProperty)\n if (ka.length != kb.length)\n return false;\n //the same set of keys (although not necessarily the same order),\n ka.sort();\n kb.sort();\n //~~~cheap key test\n for (i = ka.length - 1; i >= 0; i--) {\n if (ka[i] != kb[i])\n return false;\n }\n //equivalent values for every corresponding key, and\n //~~~possibly expensive deep test\n for (i = ka.length - 1; i >= 0; i--) {\n key = ka[i];\n if (!deepEqual(a[key], b[key], opts)) return false;\n }\n return typeof a === typeof b;\n}\n","//hidden property access symbols\nconst { entity, id, foreignKey, getNormalized, getSchema, getRootData, replace, proxy, dataProxy, schemaProxy, normalizedProxy } = require('./symbols')\nconst secretSymbols = [entity, id, foreignKey, proxy, getNormalized, normalizedProxy, getSchema, schemaProxy, getRootData, dataProxy, replace]\nconst allowedKeyList = {\n data: [],\n schema: [entity],\n normalized: [id, foreignKey]\n}\n\nconst Validator = require('./validators')\nconst V = new Validator(entity)\n\nconst createSchema = require('./createSchema')\nconst NormalizedState = require('./normalizedState')\nconst normalizeState = require('./normalizedDataCreation')\n\n//exported\nfunction processState(state) {\n const schema = createSchema(state)\n const data = normalizeState(schema, state)\n const normalData = new NormalizedState(data)\n return new StateProxy(normalData)\n}\n\nclass StateProxy {\n constructor(normalizedData, proxyType = 'data') {\n this._proxyType = proxyType\n this._normalizedData = normalizedData\n if (proxyType === 'data') return this._getProxy(normalizedData.getRoot())\n if (proxyType === 'normalized') return this._getProxy(normalizedData.getNormalizedData())\n if (proxyType === 'schema') return this._getProxy(normalizedData.getSchema())\n }\n\n _getProxy(obj) {\n if (this._proxyType !== 'data') return this._createProxy(obj)\n else {\n let cachedProxy = this._normalizedData.getProxyFromCache(obj)\n if (!cachedProxy) {\n cachedProxy = this._createProxy(obj)\n this._normalizedData.setProxyInCache(obj, cachedProxy)\n }\n return cachedProxy\n }\n }\n\n _createProxy(obj) {\n const allowedKeys = allowedKeyList[this._proxyType]\n const handler = {\n get: (target, prop) => {\n if (prop === getNormalized) {\n return new StateProxy(this._normalizedData, 'normalized')\n }\n if (prop === getSchema) {\n return new StateProxy(this._normalizedData, 'schema')\n }\n if (prop === getRootData) return this._getProxy(this._normalizedData.getRoot())\n if (prop === replace) return this._updateFn(target)\n if (allowedKeys.includes(prop)) return this._lookupValue(target[prop])\n if (secretSymbols.includes(prop)) return undefined\n return this._lookupValue(target[prop])\n },\n ownKeys(target) {\n const hiddenKeys = secretSymbols.filter(x => !allowedKeys.includes(x))\n return Reflect.ownKeys(target).filter(x => !hiddenKeys.includes(x))\n }\n }\n return new Proxy(obj, handler)\n }\n\n _updateFn(target) {\n return (newValue) => {\n const newNormalData = this._normalizedData.replace(target, newValue)\n return new StateProxy(newNormalData)\n }\n }\n\n _lookupValue(value) {\n const type = V.typeOf(value)\n if (type !== 'object' && type !== 'array') return value\n if (type === 'object' && this._proxyType === 'data') {\n const symbols = Object.getOwnPropertySymbols(value)\n if (symbols.includes(foreignKey)) {\n const linkedEntity = this._normalizedData.getEntity(value.entityType, value[foreignKey])\n return this._getProxy(linkedEntity)\n }\n }\n return this._getProxy(value)\n }\n}\n\nmodule.exports = processState\n\n\n\n//TESTING\nconst state = {\n numbers: [5, 7, 9],\n thing: { prop1: 'hello', prop2: 'world' },\n}\n// const proxied = processState(state)\n// proxied.numbers[0]\n// const newState2 = proxied.thing[replace]({ prop1: 'goodbye', prop2: 'world' })\n// console.log('\\nreplaced state\\n', newState2)\n// console.log('\\nnormal data before lookup\\n', proxied[getNormalized].ROOT)\n// console.log('\\nproxied first\\n', proxied[getNormalized].ROOT)\n// newState2.numbers[0]\n// console.log(proxied[getSchema])\n// console.log(proxied.thing === proxied.thing)\n// console.log('\\nproxied after\\n', proxied[getNormalized].ROOT)\n// console.log('\\nproxied after\\n', proxied)\n// console.log('\\nnormal data after lookup\\n', proxied[getNormalized].ROOT)\n// proxied.numbers[0]\n// newState2.thing.prop1\n// console.log(proxied.thing)\n// const newState1 = proxied.numbers[replace]([1, 2])\n// console.log(newState1)\n// newState1.thing.prop1\n// console.log('\\nnew state 1 is \\n', newState1)\n// const newState2 = proxied.nu[replace]({ ...proxied.thing, prop1: 'goodbye' })\n// console.log('\\nnew state 2 is \\n', newState2)\n// console.log('\\nthe origional state is \\n', proxied)","function dfs(target, prevKey, obj, callback, path='') {\n // Record the current path we've traversed to.\n if (prevKey !== '') path += (path ? '_': '') + prevKey.toUpperCase();\n // If we've found a target path call the callback\n if ((path === target) || path.endsWith('_' + target)) {\n callback(path)\n } else {\n // Traverse further through the object.\n if (typeof obj !== 'object' || Array.isArray(obj)) return;\n for (let key of Object.keys(obj)) {\n dfs(target, key, obj[key], callback, path)\n }\n }\n}\n\nfunction findPath(path, obj) {\n // Recieve a short text path and return an array of the target path.\n if (path === undefined) return [];\n let paths = [];\n dfs(path, '', obj, (p) => paths.push(p))\n return paths;\n}\n\nmodule.exports = findPath;","// function updateAtPath(path, obj, callback) {\n// if (typeof obj !== 'object') return obj;\n// const newObj = Object.assign({}, obj);\n// for (let key of Object.keys(obj)) {\n// if (key.toUpperCase() === path) {\n// newObj[key] = callback(obj[key]);\n// } else {\n// newObj[key] = updateAtPath(path, obj[key], callback);\n// }\n// }\n// return newObj;\n// }\nfunction updateAtPath(path, obj, callback) {\n //if no path return updated state\n if (path.length === 0) return callback(obj);\n\n //base case no more object descendants\n if (typeof obj !== 'object') return obj;\n\n //make a copy\n const newObj = Object.assign({}, obj);\n for (let key of Object.keys(obj)) {\n if (key.toUpperCase() === path[0]) {\n newObj[key] = updateAtPath(path.slice(1), obj[key], callback);\n }\n }\n return newObj;\n}\nmodule.exports = updateAtPath;","function actionTypeParser(actionType) {\n const validActions = [\n 'SET_ALL',\n 'SET_IN',\n 'SET',\n 'INCREMENT_ALL',\n 'INCREMENT_IN',\n 'INCREMENT',\n 'DECREMENT_ALL',\n 'DECREMENT_IN',\n 'DECREMENT',\n 'TOGGLE_ALL',\n 'TOGGLE_IN',\n 'TOGGLE',\n 'ADD_TO',\n 'ADD',\n 'INSERT_IN',\n 'INSERT',\n 'REMOVE_ALL',\n 'REMOVE_FROM',\n 'REMOVE',\n 'MERGE_ALL',\n 'MERGE_IN',\n 'MERGE',\n ];\n let verb;\n let path;\n for (let action of validActions) {\n if (actionType.startsWith(action)) {\n verb = action;\n path = actionType.replace(verb, '').replace('_', '');\n return {verb, path}\n }\n }\n return {verb, path} // Undefined if no match\n}\n\nmodule.exports = actionTypeParser;","const actionTypeParser = require('./functions/actionTypeParser');\nconst updateAtPath = require('./functions/updateAtPath');\nconst findPath = require('./functions/findPath');\nconst processState = require('./proxy')\nconst checkForEntities = require('./functions/checkForEntities')\nconst { replace } = require('./symbols')\n\n/**\n * Helper functions\n */\nconst getPath = (path, state) => {\n const paths = findPath(path, state);\n if (paths.length === 0) throw new Error('Path not found.');\n if (paths.length > 1) throw new Error('Path not unique, try a longer path specification.');\n return paths[0].split('_');\n}\n\nfunction insert(state, values, action) {\n if (values === undefined) return state;\n if (Array.isArray(state)) {\n if (action.index) {\n return [\n ...state.slice(0, action.index),\n ...values,\n ...state.slice(action.index)\n ];\n }\n return [...state, ...values];\n }\n if (typeof state === \"object\") {\n return Object.assign({ ...state }, values)\n }\n}\n\nfunction update(state, callback, predicate) {\n if (Array.isArray(state)) {\n return state.map((elem, idx) => {\n if (predicate === undefined || predicate(elem, idx)) return callback(elem);\n return elem;\n });\n }\n if (typeof state === 'object' && state !== null) {\n const newObj = {};\n Object.entries(state).map(([key, value]) => {\n if (predicate === undefined || predicate(key, value)) newObj[key] = callback(key, value);\n else newObj[key] = value;\n });\n return newObj;\n }\n}\n\nfunction del(state, predicate) {\n if (Array.isArray(state)) {\n if (predicate === undefined) return [];\n return state.filter((elem, index) => !predicate(elem, index));\n }\n if (typeof state === 'object' && state !== null) {\n if (predicate === undefined) return {};\n const newObj = {};\n Object.entries(state).map(([key, value]) => {\n if (!predicate(key, value)) newObj[key] = value;\n });\n return newObj;\n }\n}\n\n/**\n * Sub reducers that are selected depending on the type of the root state\n */\n\nfunction switch_number(state, action) {\n switch (action.type) {\n case 'INCREMENT':\n return state + action.value;\n case 'DECREMENT':\n return state - action.value;\n case 'SET':\n return action.value;\n default:\n return state;\n }\n}\n\nfunction switch_boolean(state, action) {\n switch (action.type) {\n case 'SET':\n return action.value;\n case 'TOGGLE':\n return !state;\n default:\n return state;\n }\n}\n\nfunction switch_string(state, action) {\n switch (action.type) {\n case 'SET':\n return action.value;\n default:\n return state;\n }\n}\n\nfunction switch_array(state, action) {\n switch (action.type) {\n case 'ADD':\n return [...state, action.value];\n case 'CONCAT':\n return [...state, ...action.value];\n case 'REMOVE_ALL':\n return del(state);\n case 'REMOVE_IN':\n if (action.where !== undefined) {\n return del(state, action.where)\n }\n if (action.index === undefined) action.index = state.length - 1;\n if (action.index !== undefined) {\n return del(state, (e, i) => i === action.index);\n }\n case 'SET_IN':\n if (action.index !== undefined) {\n return update(state, () => action.value, (e, i) => i === action.index);\n }\n if (action.where !== undefined) {\n return update(state, () => action.value, action.where);\n }\n case 'SET_ALL':\n return update(state, () => action.value);\n case 'TOGGLE_IN':\n if (action.index !== undefined) {\n return update(state, el => !el, (e, i) => i === action.index);\n }\n if (action.where !== undefined) {\n return update(state, el => !el, action.where);\n }\n case 'TOGGLE_ALL':\n return update(state, el => !el);\n case 'INCREMENT_IN':\n if (action.index !== undefined) {\n return update(state, el => el + action.value, (e, i) => i === action.index);\n }\n if (action.where !== undefined) {\n return update(state, el => el + action.value, action.where);\n }\n case 'DECREMENT_IN':\n if (action.index !== undefined) {\n return update(state, el => el - action.value, (e, i) => i === action.index);\n }\n if (action.where !== undefined) {\n return update(state, el => el - action.value, action.where);\n }\n case 'UPDATE_IN':\n return update(state, action.set, action.where);\n case 'INSERT':\n // return insert(state, action.values, action)\n return [\n ...state.slice(0, action.index),\n action.value,\n ...state.slice(action.index)\n ];\n case 'INCREMENT_ALL':\n return update(state, el => el + action.value);\n case 'DECREMENT_ALL':\n return update(state, el => el - action.value);\n case 'MERGE_ALL':\n return update(\n state,\n obj => Object.assign({ ...obj }, action.value)\n );\n case 'MERGE_IN':\n if (action.index !== undefined) {\n return update(\n state,\n el => Object.assign({ ...el }, action.value),\n (e, i) => i === action.index\n );\n }\n if (action.where !== undefined) {\n return update(\n state,\n el => Object.assign({ ...el }, action.value),\n action.where\n );\n }\n default:\n return state;\n }\n}\n\nfunction switch_object(state, action) {\n let { verb, path } = actionTypeParser(action.type);\n let newObj = {}\n\n if (path) {\n if (verb === 'SET_IN') {\n if (action.key !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n obj => { return { ...obj, [action.key]: action.value } }\n );\n }\n if (action.index !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n (obj) => update(obj, () => action.value, (e, i) => i === action.index)\n );\n }\n if (action.where !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n (obj) => update(obj, () => action.value, action.where)\n );\n }\n return updateAtPath(getPath(path, state), state, () => action.value)\n }\n if (verb === 'INCREMENT_ALL') {\n return updateAtPath(\n getPath(path, state),\n state,\n arr => update(arr, el => el + action.value)\n );\n }\n if (verb === 'INCREMENT_IN') {\n if (action.index !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n arr => update(arr, el => el + action.value, (e, i) => i === action.index)\n );\n }\n if (action.where !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n (arr) => update(arr, el => el + action.value, action.where)\n );\n }\n }\n if (verb === 'DECREMENT_ALL') {\n return updateAtPath(\n getPath(path, state),\n state,\n arr => update(arr, el => el - action.value)\n );\n }\n if (verb === 'DECREMENT_IN') {\n if (action.index !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n (arr) => update(arr, el => el - action.value, (e, i) => i === action.index)\n );\n }\n if (action.where !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n (arr) => update(arr, el => el - action.value, action.where)\n );\n }\n }\n if (verb === 'INCREMENT') {\n return updateAtPath(\n getPath(path, state),\n state,\n number => number + action.value\n );\n }\n if (verb === 'DECREMENT') {\n return updateAtPath(\n getPath(path, state),\n state,\n number => number - action.value\n );\n }\n if (verb === 'TOGGLE') {\n return updateAtPath(getPath(path, state), state, (bool) => !bool);\n }\n if (verb === 'TOGGLE_IN') {\n if (action.index !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n arr => update(arr, el => !el, (e, i) => i === action.index)\n );\n }\n if (action.key !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n obj => update(obj, el => !el, (k, v) => k === action.key)\n );\n }\n if (action.where !== undefined) {\n return updateAtPath(getPath(path, state), state, (obj) => {\n return update(obj, el => !el, action.where);\n });\n }\n }\n\n if (verb === 'MERGE') {\n return updateAtPath(\n getPath(path, state),\n state,\n obj => { return Object.assign({ ...obj }, action.value) }\n );\n }\n\n if (verb === 'MERGE_IN') {\n return updateAtPath(getPath(path, state), state, (obj) => {\n if (Array.isArray(obj)) {\n if (action.index) {\n let newState = [...obj];\n newState[action.index] = Object.assign({ ...newState[action.index] }, action.value);\n return newState;\n }\n\n if (action.where) {\n return obj.map(value => {\n if (action.where(value)) {\n // Special case to merge object props instead of setting the value.\n if (typeof value === 'object') return Object.assign({ ...value }, action.value);\n return action.value;\n }\n return value;\n });\n }\n }\n if (typeof obj === 'object') {\n if (action.key !== undefined && typeof action.value === 'object') return { ...obj, [action.key]: Object.assign({ ...obj[action.key] }, action.value) };\n if (action.key !== undefined) return { ...obj, [action.key]: action.value };\n if (action.where !== undefined) {\n Object.entries(obj).forEach(([key, subObj]) => {\n if (action.where(key, subObj)) {\n newObj[key] = Object.assign({ ...subObj }, action.value);\n }\n else newObj[key] = { ...subObj }\n })\n return newObj;\n }\n }\n });\n }\n if (verb === 'MERGE_ALL') {\n return updateAtPath(getPath(path, state), state, (obj) => {\n Object.entries(obj).forEach(([key, subObj]) => {\n if (Array.isArray(subObj)) newObj[key] = [...subObj]\n else newObj[key] = Object.assign({ ...subObj }, action.value);\n })\n return newObj;\n });\n }\n if (verb === 'ADD_TO') {\n return updateAtPath(getPath(path, state), state, (arr) => [...arr, action.value]);\n }\n if (verb === 'INSERT_IN') {\n return updateAtPath(getPath(path, state), state, (arr) => [...arr.slice(0, action.index), action.value, ...arr.slice(action.index)]);\n }\n if (verb === 'REMOVE_ALL') {\n return updateAtPath(getPath(path, state), state, (arr) => del(arr));\n }\n if (verb === 'REMOVE_FROM') {\n if (action.index !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n (arr) => del(arr, (e, i) => i === action.index)\n );\n }\n if (action.where !== undefined) {\n return updateAtPath(\n getPath(path, state),\n state,\n (arr) => del(arr, action.where)\n );\n }\n }\n if (verb === 'SET_ALL') {\n return updateAtPath(\n getPath(path, state),\n state,\n arr => update(arr, el => action.value)\n );\n }\n // if (verb === 'SET_IN') {\n // if (action.index !== undefined) {\n // return updateAtPath(\n // getPath(path, state),\n // state,\n // arr => update(arr, () => action.value, (e, i) => i === action.index)\n // );\n // }\n // if (action.where !== undefined) {\n // return updateAtPath(\n // getPath(path, state),\n // state,\n // arr => update(arr, () => action.value, action.where)\n // );\n // }\n // }\n if (verb === 'TOGGLE_ALL') {\n return updateAtPath(\n getPath(path, state),\n state,\n arr => update(arr, bool => !bool)\n );\n }\n }\n switch (action.type) {\n case 'SET_ALL':\n return update(state, () => action.value)\n case 'SET_IN':\n if (action.key !== undefined) {\n return insert(state, { [action.key]: action.value });\n }\n if (action.where !== undefined) {\n return update(state, () => action.value, action.where);\n }\n case 'MERGE_IN':\n if (action.key !== undefined) {\n return update(\n state,\n (key, subObj) => Object.assign({ ...subObj }, action.value),\n (k, v) => k === action.key\n );\n }\n if (action.where !== undefined) {\n return update(\n state,\n (key, subObj) => Object.assign({ ...subObj }, action.value),\n action.where\n );\n }\n case 'MERGE_ALL':\n return update(\n state,\n (key, subObj) => Object.assign({ ...subObj }, action.value)\n );\n case 'MERGE':\n return insert(state, action.value)\n case 'REMOVE_ALL':\n return del(state);\n case 'REMOVE_IN':\n if (action.key !== undefined) {\n return del(state, (k, v) => k === action.key);\n }\n if (action.where !== undefined) {\n return del(state, action.where);\n }\n case 'INCREMENT_IN':\n if (action.key !== undefined) {\n return update(state, (k, v) => v + action.value, (key) => key === action.key);\n }\n if (action.where !== undefined) {\n return update(state, (k, v) => v + action.value, action.where);\n }\n case 'DECREMENT_IN':\n if (action.key !== undefined) {\n return update(state, (k, v) => v - action.value, (key) => key === action.key);\n }\n if (action.where !== undefined) {\n return update(state, (k, v) => v - action.value, action.where);\n }\n case 'TOGGLE_IN':\n if (action.key !== undefined) {\n return update(state, (k, bool) => !bool, (key) => key === action.key);\n }\n if (action.where !== undefined) {\n return update(state, (k, bool) => !bool, action.where);\n }\n case 'TOGGLE_ALL':\n return update(\n state,\n (k, bool) => !bool\n );\n default:\n return state;\n }\n}\n\n/***\n* Overall organization and exported classes\n**/\n\nfunction checkPath(path, state) {\n if (path) {\n let paths = findPath(path, state);\n if (paths.length === 0) throw new Error(`Path: ${path} could not be found.`);\n if (paths.length > 1) throw new Error(`Multiple valid paths found. Use a longer path to ensure a unique selection.`)\n }\n}\nfunction validatePayload(action, payload, required) {\n if (!payload) return;\n if (required.includes('value') && !('value' in payload)) {\n throw new Error(`${action} should include a value property in the payload.`)\n }\n if (required.includes('in')\n && payload.index === undefined\n && payload.key === undefined\n && payload.where === undefined) {\n throw new Error(`${action} should include either a key, index, or where property in the payload.`)\n }\n // if (required.includes('index') && payload.index === undefined) {\n // throw new Error(`${action} should include either a key or index property in the payload.`)\n // }\n}\nfunction handleAction(action, config, required, state) {\n if (!config) throw new Error('All actions need to have a configuration object.');\n checkPath(config.path, state);\n validatePayload(action, config, required);\n // Final configuration for action\n let path = config.path;\n delete config['path'];\n if (path) return Object.assign({ type: `${action}_${path}` }, config);\n return Object.assign({ type: action }, config);\n}\n\nclass Actions {\n constructor(closedState) {\n this.closedState = closedState;\n }\n SET_ALL(config) {\n return handleAction('SET_ALL', config, ['value'], this.closedState.state);\n }\n SET_IN(config) {\n return handleAction('SET_IN', config, ['value', 'in'], this.closedState.state);\n }\n SET(config) {\n return handleAction('SET', config, ['value'], this.closedState.state);\n }\n INCREMENT_ALL(config) {\n return handleAction('INCREMENT_ALL', config, ['value'], this.closedState.state);\n }\n INCREMENT_IN(config) {\n return handleAction('INCREMENT_IN', config, ['value', 'in'], this.closedState.state);\n }\n INCREMENT(config) {\n return handleAction('INCREMENT', config, ['value'], this.closedState.state);\n }\n DECREMENT_ALL(config) {\n return handleAction('DECREMENT_ALL', config, ['value'], this.closedState.state);\n }\n DECREMENT_IN(config) {\n return handleAction('DECREMENT_IN', config, ['value', 'in'], this.closedState.state);\n }\n DECREMENT(config) {\n return handleAction('DECREMENT', config, ['value'], this.closedState.state);\n }\n TOGGLE_ALL(config) {\n return handleAction('TOGGLE_ALL', config, [], this.closedState.state);\n }\n TOGGLE_IN(config) {\n return handleAction('TOGGLE_IN', config, ['in'], this.closedState.state);\n }\n TOGGLE(config) {\n return handleAction('TOGGLE', config, [], this.closedState.state);\n }\n ADD_TO(config) {\n return handleAction('ADD_TO', config, ['value'], this.closedState.state);\n }\n ADD(config) {\n return handleAction('ADD', config, ['value'], this.closedState.state);\n }\n INSERT_IN(config) {\n return handleAction('INSERT_IN', config, ['value', 'in'], this.closedState.state);\n }\n INSERT(config) {\n return handleAction('INSERT', config, ['value'], this.closedState.state);\n }\n UPDATE_IN(config) {\n return handleAction('UPDATE_IN', config, ['in'], this.closedState.state);\n }\n REMOVE_ALL(config) {\n return handleAction('REMOVE_ALL', config, [], this.closedState.state);\n }\n REMOVE_IN(config) {\n return handleAction('REMOVE_IN', config, ['in'], this.closedState.state);\n }\n REMOVE(config) {\n return handleAction('REMOVE', config, [], this.closedState.state);\n }\n MERGE_ALL(config) {\n return handleAction('MERGE_ALL', config, ['value'], this.closedState.state);\n }\n MERGE_IN(config) {\n return handleAction('MERGE_IN', config, ['value', 'in'], this.closedState.state);\n }\n MERGE(config) {\n return handleAction('MERGE', config, ['value'], this.closedState.state);\n }\n}\n\nclass Container {\n constructor() {\n this.closedState = {};\n this.deduce = (reducer) => {\n const initialState = reducer(undefined, {})\n const shouldNormalize = checkForEntities(initialState)\n let proxied = shouldNormalize && processState(initialState)\n return (state, action) => {\n let update = reducer(state, action);\n proxied = shouldNormalize ? proxied[replace](update) : update\n this.closedState.state = proxied;\n if (proxied !== state) return proxied;\n if (typeof state === 'number') {\n this.closedState.state = switch_number(state, action)\n return this.closedState.state;\n }\n if (typeof state === 'boolean') {\n this.closedState.state = switch_boolean(state, action);\n return this.closedState.state;\n }\n if (typeof state === 'string') {\n this.closedState.state = switch_string(state, action);\n return this.closedState.state;\n }\n if (Array.isArray(state)) {\n this.closedState.state = switch_array(state, action);\n return this.closedState.state;\n }\n if (typeof state === 'object' && state !== null) {\n this.closedState.state = switch_object(state, action);\n return this.closedState.state;\n }\n }\n };\n this.actions = new Actions(this.closedState);\n }\n}\n\nconst container = new Container();\nmodule.exports = {\n deduce: container.deduce,\n D: container.actions\n};"],"sourceRoot":""} -------------------------------------------------------------------------------- /core-concepts.md: -------------------------------------------------------------------------------- 1 | # Core Concepts 2 | 3 | ## Primitive Actions 4 | 5 | Redux-deduce comes with a handful of primitive actions that work directly on particular data types. These are SET, INCREMENT/DECREMENT, TOGGLE, ADD, INSERT, REMOVE, and MERGE. 6 | 7 | | **Action** | **Number** | **Boolean** | **String** | **Array** | **Object** | 8 | | --- | --- | --- | --- | --- | --- | --- | --- | 9 | | SET | ✔ | ✔ | ✔ | | | 10 | | INCREMENT/DECREMENT | ✔ | | | | | 11 | | TOGGLE | | ✔ | | | | 12 | | ADD | | | | ✔ | | 13 | | INSERT | | | | ✔ | | 14 | | REMOVE | | | | ✔ | ✔ | 15 | | MERGE | | | | | ✔ | 16 | 17 | ## Using IN and ALL to reveal intention with Arrays and Objects 18 | 19 | Image you have a collection full on Numbers such as`[0, 1, 2]` or `{a:1, b:2, c:3}`. You probably want to be able to perform updates in these collections, but you can call a SET or INCREMENT on a collection. 20 | 21 | To solve this, deduce allows the IN keyword to specify that you want to make an update inside an Array or Object. You can then provide an index, key, or where prop to specify where to make the update. 22 | 23 | ```javascript 24 | // Arrays 25 | D.SET_IN({index: 0, value: 4}) 26 | D.SET_IN({where: (value) => value === 0, value: 4}) 27 | 28 | // Objects 29 | D.SET_IN({key: 'a', value: 4}) 30 | D.SET_IN({where: (key, value) => key === 'a', value: 4}) 31 | ``` 32 | 33 | We currently have limited support of making updates to collection of collections. Array of Objects work OK and you can use the path syntax below for working with Objects. 34 | 35 | ## Using object paths for organization 36 | 37 | Dedux action types for objects employ a special **ITEM\_ACTION** and **PATH** syntax to allow you to reference keys within nested objects. This is primarily to capture the organizational use of objects in Redux. 38 | 39 | An **ITEM\_ACTION** is an action type that can be run on an item inside your object. These might be action types such as 'SET' or 'TOGGLE'. 40 | 41 | A **PATH** is a sequence of keys used in your objects that allow you to uniquely identify the location of a value in your store. 42 | **For example:** 43 | 44 | `{a:1, b:2, c:3}` 45 | 46 | The following paths are available: 'A', 'B', and 'C'. This allows you to use action types such as 'SET\_A' and 'SET\_B'. In general, this allows you to use objects to organize your state. 47 | 48 | **A more complicated example:** 49 | 50 | ```javascript 51 | { 52 | counters: {c1: 0, c2: 0, c3: 0}, 53 | togglers: {'a': true, 'b': false} 54 | } 55 | ``` 56 | 57 | The paths 'C1', 'C2', 'C3', 'A', and 'B' are available and allow you to use an action such as 'INCREMENT\_C1' or 'TOGGLE\_B'. You can also use the full path: 'INCREMENT\_COUNTERS\_C1'. This is useful when you have multiple keys with the same name. 58 | 59 | It's tempting to use this syntax when you have an array or object of repeated objects with unique IDs, but you should use the 'UPDATE' or 'MERGE' 'action type with the where property instead. 60 | 61 | In many cases you won't know the exact key until a user performs an action in your application. For these cases you can use the key property inside your action: 62 | 63 | ```javascript 64 | { 65 | type: 'INCREMENT_IN_COUNTERS', 66 | value: 1, 67 | key: 'c1' 68 | } 69 | ``` 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-deduce", 3 | "version": "0.0.0", 4 | "description": "Automated Redux state updates for common actions.", 5 | "main": "build/dedux.js", 6 | "scripts": { 7 | "test": "mocha \"test/*.js\"", 8 | "dev": "webpack --progress --colors" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Redux-Deduce/redux-deduce.git" 13 | }, 14 | "keywords": [ 15 | "Redux", 16 | "boilerplate", 17 | "reducer", 18 | "enhancer" 19 | ], 20 | "author": "Denali DeMots, Daniel Boisselle, Alex Egorenkov", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/Redux-Deduce/redux-deduce/issues" 24 | }, 25 | "homepage": "https://github.com/Redux-Deduce/redux-deduce#readme", 26 | "dependencies": { 27 | "deep-equal": "^1.0.1" 28 | }, 29 | "devDependencies": { 30 | "babel-loader": "^7.1.4", 31 | "babel-plugin-add-module-exports": "^0.2.1", 32 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 33 | "babel-preset-env": "^1.6.1", 34 | "deep-freeze": "0.0.1", 35 | "expect": "^22.4.3", 36 | "mocha": "^5.1.1", 37 | "webpack": "^4.8.0", 38 | "webpack-cli": "^2.1.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/createSchema.js: -------------------------------------------------------------------------------- 1 | const { entity, id, proxy } = require('./symbols') 2 | const Validator = require('./validators') 3 | const V = new Validator(entity) 4 | 5 | class SchemaCreator { 6 | constructor() { 7 | this._schema = { [proxy]: {} } 8 | } 9 | 10 | addType(entityObj) { 11 | const tag = entityObj[entity] 12 | V.validateTag(tag) 13 | const name = V.getName(tag) 14 | delete entityObj[entity] 15 | if (!this._schema[name]) this._schema[name] = entityObj 16 | else V.checkForInconsistencies(entityObj, this.schema[name], name) 17 | return name 18 | } 19 | 20 | addVacantType(vacantEntity) { 21 | const tag = vacantEntity[entity] 22 | V.validateTag(tag) 23 | const name = V.getName(tag) 24 | if (!this._schema[name]) this._schema[name] = undefined 25 | return name 26 | } 27 | 28 | getSchema() { 29 | const tables = Reflect.ownKeys(this._schema) 30 | V.checkForUndefinedEntities(this._schema) 31 | tables.forEach(type => this._schema[type][id] = 0) 32 | return this._schema 33 | } 34 | } 35 | 36 | function createSchema(state) { 37 | const schemaCreator = new SchemaCreator() 38 | if (!state[entity]) state[entity] = 'ROOT' 39 | process(state) 40 | return schemaCreator.getSchema() 41 | 42 | 43 | function process(state) { 44 | if (V.typeOf(state) === 'array') return [process(state[0])] 45 | if (V.typeOf(state) === 'object') { 46 | if (V.isVacentTag(state)) return schemaCreator.addVacantType(state) 47 | const obj = { ...state } 48 | Object.entries(obj).forEach( 49 | ([key, value]) => obj[key] = process(value) 50 | ) 51 | return V.isEntity(obj) ? schemaCreator.addType(obj) : obj 52 | } else return V.typeOf(state) 53 | } 54 | } 55 | 56 | module.exports = createSchema -------------------------------------------------------------------------------- /src/functions/actionTypeParser.js: -------------------------------------------------------------------------------- 1 | function actionTypeParser(actionType) { 2 | const validActions = [ 3 | 'SET_ALL', 4 | 'SET_IN', 5 | 'SET', 6 | 'INCREMENT_ALL', 7 | 'INCREMENT_IN', 8 | 'INCREMENT', 9 | 'DECREMENT_ALL', 10 | 'DECREMENT_IN', 11 | 'DECREMENT', 12 | 'TOGGLE_ALL', 13 | 'TOGGLE_IN', 14 | 'TOGGLE', 15 | 'ADD_TO', 16 | 'ADD', 17 | 'INSERT_IN', 18 | 'INSERT', 19 | 'REMOVE_ALL', 20 | 'REMOVE_FROM', 21 | 'REMOVE', 22 | 'MERGE_ALL', 23 | 'MERGE_IN', 24 | 'MERGE', 25 | ]; 26 | let verb; 27 | let path; 28 | for (let action of validActions) { 29 | if (actionType.startsWith(action)) { 30 | verb = action; 31 | path = actionType.replace(verb, '').replace('_', ''); 32 | return {verb, path} 33 | } 34 | } 35 | return {verb, path} // Undefined if no match 36 | } 37 | 38 | module.exports = actionTypeParser; -------------------------------------------------------------------------------- /src/functions/checkForEntities.js: -------------------------------------------------------------------------------- 1 | const { replace, entity } = require('../symbols') 2 | const Validator = require('../validators') 3 | const V = new Validator(entity) 4 | 5 | function checkForEntities(state, hasEntities = false) { 6 | if (V.typeOf(state) === 'object') { 7 | if (V.isEntity(state)) return true 8 | return Object.values(state).reduce((acc, value) => { 9 | return acc || checkForEntities(value, hasEntities) 10 | }, hasEntities) 11 | } 12 | if (V.typeOf(state) === 'array') { 13 | return state.reduce((acc, value) => { 14 | return acc || checkForEntities(value, hasEntities) 15 | }, hasEntities) 16 | } 17 | else return false 18 | } 19 | 20 | module.exports = checkForEntities -------------------------------------------------------------------------------- /src/functions/findPath.js: -------------------------------------------------------------------------------- 1 | function dfs(target, prevKey, obj, callback, path='') { 2 | // Record the current path we've traversed to. 3 | if (prevKey !== '') path += (path ? '_': '') + prevKey.toUpperCase(); 4 | // If we've found a target path call the callback 5 | if ((path === target) || path.endsWith('_' + target)) { 6 | callback(path) 7 | } else { 8 | // Traverse further through the object. 9 | if (typeof obj !== 'object' || Array.isArray(obj)) return; 10 | for (let key of Object.keys(obj)) { 11 | dfs(target, key, obj[key], callback, path) 12 | } 13 | } 14 | } 15 | 16 | function findPath(path, obj) { 17 | // Recieve a short text path and return an array of the target path. 18 | if (path === undefined) return []; 19 | let paths = []; 20 | dfs(path, '', obj, (p) => paths.push(p)) 21 | return paths; 22 | } 23 | 24 | module.exports = findPath; -------------------------------------------------------------------------------- /src/functions/updateAtPath.js: -------------------------------------------------------------------------------- 1 | // function updateAtPath(path, obj, callback) { 2 | // if (typeof obj !== 'object') return obj; 3 | // const newObj = Object.assign({}, obj); 4 | // for (let key of Object.keys(obj)) { 5 | // if (key.toUpperCase() === path) { 6 | // newObj[key] = callback(obj[key]); 7 | // } else { 8 | // newObj[key] = updateAtPath(path, obj[key], callback); 9 | // } 10 | // } 11 | // return newObj; 12 | // } 13 | function updateAtPath(path, obj, callback) { 14 | //if no path return updated state 15 | if (path.length === 0) return callback(obj); 16 | 17 | //base case no more object descendants 18 | if (typeof obj !== 'object') return obj; 19 | 20 | //make a copy 21 | const newObj = Object.assign({}, obj); 22 | for (let key of Object.keys(obj)) { 23 | if (key.toUpperCase() === path[0]) { 24 | newObj[key] = updateAtPath(path.slice(1), obj[key], callback); 25 | } 26 | } 27 | return newObj; 28 | } 29 | module.exports = updateAtPath; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const actionTypeParser = require('./functions/actionTypeParser'); 2 | const updateAtPath = require('./functions/updateAtPath'); 3 | const findPath = require('./functions/findPath'); 4 | const processState = require('./proxy') 5 | const checkForEntities = require('./functions/checkForEntities') 6 | const { replace } = require('./symbols') 7 | 8 | /** 9 | * Helper functions 10 | */ 11 | const getPath = (path, state) => { 12 | const paths = findPath(path, state); 13 | if (paths.length === 0) throw new Error('Path not found.'); 14 | if (paths.length > 1) throw new Error('Path not unique, try a longer path specification.'); 15 | return paths[0].split('_'); 16 | } 17 | 18 | function insert(state, values, action) { 19 | if (values === undefined) return state; 20 | if (Array.isArray(state)) { 21 | if (action.index) { 22 | return [ 23 | ...state.slice(0, action.index), 24 | ...values, 25 | ...state.slice(action.index) 26 | ]; 27 | } 28 | return [...state, ...values]; 29 | } 30 | if (typeof state === "object") { 31 | return Object.assign({ ...state }, values) 32 | } 33 | } 34 | 35 | function update(state, callback, predicate) { 36 | if (Array.isArray(state)) { 37 | return state.map((elem, idx) => { 38 | if (predicate === undefined || predicate(elem, idx)) return callback(elem); 39 | return elem; 40 | }); 41 | } 42 | if (typeof state === 'object' && state !== null) { 43 | const newObj = {}; 44 | Object.entries(state).map(([key, value]) => { 45 | if (predicate === undefined || predicate(key, value)) newObj[key] = callback(key, value); 46 | else newObj[key] = value; 47 | }); 48 | return newObj; 49 | } 50 | } 51 | 52 | function del(state, predicate) { 53 | if (Array.isArray(state)) { 54 | if (predicate === undefined) return []; 55 | return state.filter((elem, index) => !predicate(elem, index)); 56 | } 57 | if (typeof state === 'object' && state !== null) { 58 | if (predicate === undefined) return {}; 59 | const newObj = {}; 60 | Object.entries(state).map(([key, value]) => { 61 | if (!predicate(key, value)) newObj[key] = value; 62 | }); 63 | return newObj; 64 | } 65 | } 66 | 67 | /** 68 | * Sub reducers that are selected depending on the type of the root state 69 | */ 70 | 71 | function switch_number(state, action) { 72 | switch (action.type) { 73 | case 'INCREMENT': 74 | return state + action.value; 75 | case 'DECREMENT': 76 | return state - action.value; 77 | case 'SET': 78 | return action.value; 79 | default: 80 | return state; 81 | } 82 | } 83 | 84 | function switch_boolean(state, action) { 85 | switch (action.type) { 86 | case 'SET': 87 | return action.value; 88 | case 'TOGGLE': 89 | return !state; 90 | default: 91 | return state; 92 | } 93 | } 94 | 95 | function switch_string(state, action) { 96 | switch (action.type) { 97 | case 'SET': 98 | return action.value; 99 | default: 100 | return state; 101 | } 102 | } 103 | 104 | function switch_array(state, action) { 105 | switch (action.type) { 106 | case 'ADD': 107 | return [...state, action.value]; 108 | case 'CONCAT': 109 | return [...state, ...action.value]; 110 | case 'REMOVE_ALL': 111 | return del(state); 112 | case 'REMOVE_IN': 113 | if (action.where !== undefined) { 114 | return del(state, action.where) 115 | } 116 | if (action.index === undefined) action.index = state.length - 1; 117 | if (action.index !== undefined) { 118 | return del(state, (e, i) => i === action.index); 119 | } 120 | case 'SET_IN': 121 | if (action.index !== undefined) { 122 | return update(state, () => action.value, (e, i) => i === action.index); 123 | } 124 | if (action.where !== undefined) { 125 | return update(state, () => action.value, action.where); 126 | } 127 | case 'SET_ALL': 128 | return update(state, () => action.value); 129 | case 'TOGGLE_IN': 130 | if (action.index !== undefined) { 131 | return update(state, el => !el, (e, i) => i === action.index); 132 | } 133 | if (action.where !== undefined) { 134 | return update(state, el => !el, action.where); 135 | } 136 | case 'TOGGLE_ALL': 137 | return update(state, el => !el); 138 | case 'INCREMENT_IN': 139 | if (action.index !== undefined) { 140 | return update(state, el => el + action.value, (e, i) => i === action.index); 141 | } 142 | if (action.where !== undefined) { 143 | return update(state, el => el + action.value, action.where); 144 | } 145 | case 'DECREMENT_IN': 146 | if (action.index !== undefined) { 147 | return update(state, el => el - action.value, (e, i) => i === action.index); 148 | } 149 | if (action.where !== undefined) { 150 | return update(state, el => el - action.value, action.where); 151 | } 152 | case 'UPDATE_IN': 153 | return update(state, action.set, action.where); 154 | case 'INSERT': 155 | // return insert(state, action.values, action) 156 | return [ 157 | ...state.slice(0, action.index), 158 | action.value, 159 | ...state.slice(action.index) 160 | ]; 161 | case 'INCREMENT_ALL': 162 | return update(state, el => el + action.value); 163 | case 'DECREMENT_ALL': 164 | return update(state, el => el - action.value); 165 | case 'MERGE_ALL': 166 | return update( 167 | state, 168 | obj => Object.assign({ ...obj }, action.value) 169 | ); 170 | case 'MERGE_IN': 171 | if (action.index !== undefined) { 172 | return update( 173 | state, 174 | el => Object.assign({ ...el }, action.value), 175 | (e, i) => i === action.index 176 | ); 177 | } 178 | if (action.where !== undefined) { 179 | return update( 180 | state, 181 | el => Object.assign({ ...el }, action.value), 182 | action.where 183 | ); 184 | } 185 | default: 186 | return state; 187 | } 188 | } 189 | 190 | function switch_object(state, action) { 191 | let { verb, path } = actionTypeParser(action.type); 192 | let newObj = {} 193 | 194 | if (path) { 195 | if (verb === 'SET_IN') { 196 | if (action.key !== undefined) { 197 | return updateAtPath( 198 | getPath(path, state), 199 | state, 200 | obj => { return { ...obj, [action.key]: action.value } } 201 | ); 202 | } 203 | if (action.index !== undefined) { 204 | return updateAtPath( 205 | getPath(path, state), 206 | state, 207 | (obj) => update(obj, () => action.value, (e, i) => i === action.index) 208 | ); 209 | } 210 | if (action.where !== undefined) { 211 | return updateAtPath( 212 | getPath(path, state), 213 | state, 214 | (obj) => update(obj, () => action.value, action.where) 215 | ); 216 | } 217 | return updateAtPath(getPath(path, state), state, () => action.value) 218 | } 219 | if (verb === 'INCREMENT_ALL') { 220 | return updateAtPath( 221 | getPath(path, state), 222 | state, 223 | arr => update(arr, el => el + action.value) 224 | ); 225 | } 226 | if (verb === 'INCREMENT_IN') { 227 | if (action.index !== undefined) { 228 | return updateAtPath( 229 | getPath(path, state), 230 | state, 231 | arr => update(arr, el => el + action.value, (e, i) => i === action.index) 232 | ); 233 | } 234 | if (action.where !== undefined) { 235 | return updateAtPath( 236 | getPath(path, state), 237 | state, 238 | (arr) => update(arr, el => el + action.value, action.where) 239 | ); 240 | } 241 | } 242 | if (verb === 'DECREMENT_ALL') { 243 | return updateAtPath( 244 | getPath(path, state), 245 | state, 246 | arr => update(arr, el => el - action.value) 247 | ); 248 | } 249 | if (verb === 'DECREMENT_IN') { 250 | if (action.index !== undefined) { 251 | return updateAtPath( 252 | getPath(path, state), 253 | state, 254 | (arr) => update(arr, el => el - action.value, (e, i) => i === action.index) 255 | ); 256 | } 257 | if (action.where !== undefined) { 258 | return updateAtPath( 259 | getPath(path, state), 260 | state, 261 | (arr) => update(arr, el => el - action.value, action.where) 262 | ); 263 | } 264 | } 265 | if (verb === 'INCREMENT') { 266 | return updateAtPath( 267 | getPath(path, state), 268 | state, 269 | number => number + action.value 270 | ); 271 | } 272 | if (verb === 'DECREMENT') { 273 | return updateAtPath( 274 | getPath(path, state), 275 | state, 276 | number => number - action.value 277 | ); 278 | } 279 | if (verb === 'TOGGLE') { 280 | return updateAtPath(getPath(path, state), state, (bool) => !bool); 281 | } 282 | if (verb === 'TOGGLE_IN') { 283 | if (action.index !== undefined) { 284 | return updateAtPath( 285 | getPath(path, state), 286 | state, 287 | arr => update(arr, el => !el, (e, i) => i === action.index) 288 | ); 289 | } 290 | if (action.key !== undefined) { 291 | return updateAtPath( 292 | getPath(path, state), 293 | state, 294 | obj => update(obj, el => !el, (k, v) => k === action.key) 295 | ); 296 | } 297 | if (action.where !== undefined) { 298 | return updateAtPath(getPath(path, state), state, (obj) => { 299 | return update(obj, el => !el, action.where); 300 | }); 301 | } 302 | } 303 | 304 | if (verb === 'MERGE') { 305 | return updateAtPath( 306 | getPath(path, state), 307 | state, 308 | obj => { return Object.assign({ ...obj }, action.value) } 309 | ); 310 | } 311 | 312 | if (verb === 'MERGE_IN') { 313 | return updateAtPath(getPath(path, state), state, (obj) => { 314 | if (Array.isArray(obj)) { 315 | if (action.index) { 316 | let newState = [...obj]; 317 | newState[action.index] = Object.assign({ ...newState[action.index] }, action.value); 318 | return newState; 319 | } 320 | 321 | if (action.where) { 322 | return obj.map(value => { 323 | if (action.where(value)) { 324 | // Special case to merge object props instead of setting the value. 325 | if (typeof value === 'object') return Object.assign({ ...value }, action.value); 326 | return action.value; 327 | } 328 | return value; 329 | }); 330 | } 331 | } 332 | if (typeof obj === 'object') { 333 | if (action.key !== undefined && typeof action.value === 'object') return { ...obj, [action.key]: Object.assign({ ...obj[action.key] }, action.value) }; 334 | if (action.key !== undefined) return { ...obj, [action.key]: action.value }; 335 | if (action.where !== undefined) { 336 | Object.entries(obj).forEach(([key, subObj]) => { 337 | if (action.where(key, subObj)) { 338 | newObj[key] = Object.assign({ ...subObj }, action.value); 339 | } 340 | else newObj[key] = { ...subObj } 341 | }) 342 | return newObj; 343 | } 344 | } 345 | }); 346 | } 347 | if (verb === 'MERGE_ALL') { 348 | return updateAtPath(getPath(path, state), state, (obj) => { 349 | Object.entries(obj).forEach(([key, subObj]) => { 350 | if (Array.isArray(subObj)) newObj[key] = [...subObj] 351 | else newObj[key] = Object.assign({ ...subObj }, action.value); 352 | }) 353 | return newObj; 354 | }); 355 | } 356 | if (verb === 'ADD_TO') { 357 | return updateAtPath(getPath(path, state), state, (arr) => [...arr, action.value]); 358 | } 359 | if (verb === 'INSERT_IN') { 360 | return updateAtPath(getPath(path, state), state, (arr) => [...arr.slice(0, action.index), action.value, ...arr.slice(action.index)]); 361 | } 362 | if (verb === 'REMOVE_ALL') { 363 | return updateAtPath(getPath(path, state), state, (arr) => del(arr)); 364 | } 365 | if (verb === 'REMOVE_FROM') { 366 | if (action.index !== undefined) { 367 | return updateAtPath( 368 | getPath(path, state), 369 | state, 370 | (arr) => del(arr, (e, i) => i === action.index) 371 | ); 372 | } 373 | if (action.where !== undefined) { 374 | return updateAtPath( 375 | getPath(path, state), 376 | state, 377 | (arr) => del(arr, action.where) 378 | ); 379 | } 380 | } 381 | if (verb === 'SET_ALL') { 382 | return updateAtPath( 383 | getPath(path, state), 384 | state, 385 | arr => update(arr, el => action.value) 386 | ); 387 | } 388 | // if (verb === 'SET_IN') { 389 | // if (action.index !== undefined) { 390 | // return updateAtPath( 391 | // getPath(path, state), 392 | // state, 393 | // arr => update(arr, () => action.value, (e, i) => i === action.index) 394 | // ); 395 | // } 396 | // if (action.where !== undefined) { 397 | // return updateAtPath( 398 | // getPath(path, state), 399 | // state, 400 | // arr => update(arr, () => action.value, action.where) 401 | // ); 402 | // } 403 | // } 404 | if (verb === 'TOGGLE_ALL') { 405 | return updateAtPath( 406 | getPath(path, state), 407 | state, 408 | arr => update(arr, bool => !bool) 409 | ); 410 | } 411 | } 412 | switch (action.type) { 413 | case 'SET_ALL': 414 | return update(state, () => action.value) 415 | case 'SET_IN': 416 | if (action.key !== undefined) { 417 | return insert(state, { [action.key]: action.value }); 418 | } 419 | if (action.where !== undefined) { 420 | return update(state, () => action.value, action.where); 421 | } 422 | case 'MERGE_IN': 423 | if (action.key !== undefined) { 424 | return update( 425 | state, 426 | (key, subObj) => Object.assign({ ...subObj }, action.value), 427 | (k, v) => k === action.key 428 | ); 429 | } 430 | if (action.where !== undefined) { 431 | return update( 432 | state, 433 | (key, subObj) => Object.assign({ ...subObj }, action.value), 434 | action.where 435 | ); 436 | } 437 | case 'MERGE_ALL': 438 | return update( 439 | state, 440 | (key, subObj) => Object.assign({ ...subObj }, action.value) 441 | ); 442 | case 'MERGE': 443 | return insert(state, action.value) 444 | case 'REMOVE_ALL': 445 | return del(state); 446 | case 'REMOVE_IN': 447 | if (action.key !== undefined) { 448 | return del(state, (k, v) => k === action.key); 449 | } 450 | if (action.where !== undefined) { 451 | return del(state, action.where); 452 | } 453 | case 'INCREMENT_IN': 454 | if (action.key !== undefined) { 455 | return update(state, (k, v) => v + action.value, (key) => key === action.key); 456 | } 457 | if (action.where !== undefined) { 458 | return update(state, (k, v) => v + action.value, action.where); 459 | } 460 | case 'DECREMENT_IN': 461 | if (action.key !== undefined) { 462 | return update(state, (k, v) => v - action.value, (key) => key === action.key); 463 | } 464 | if (action.where !== undefined) { 465 | return update(state, (k, v) => v - action.value, action.where); 466 | } 467 | case 'TOGGLE_IN': 468 | if (action.key !== undefined) { 469 | return update(state, (k, bool) => !bool, (key) => key === action.key); 470 | } 471 | if (action.where !== undefined) { 472 | return update(state, (k, bool) => !bool, action.where); 473 | } 474 | case 'TOGGLE_ALL': 475 | return update( 476 | state, 477 | (k, bool) => !bool 478 | ); 479 | default: 480 | return state; 481 | } 482 | } 483 | 484 | /*** 485 | * Overall organization and exported classes 486 | **/ 487 | 488 | function checkPath(path, state) { 489 | if (path) { 490 | let paths = findPath(path, state); 491 | if (paths.length === 0) throw new Error(`Path: ${path} could not be found.`); 492 | if (paths.length > 1) throw new Error(`Multiple valid paths found. Use a longer path to ensure a unique selection.`) 493 | } 494 | } 495 | function validatePayload(action, payload, required) { 496 | if (!payload) return; 497 | if (required.includes('value') && !('value' in payload)) { 498 | throw new Error(`${action} should include a value property in the payload.`) 499 | } 500 | if (required.includes('in') 501 | && payload.index === undefined 502 | && payload.key === undefined 503 | && payload.where === undefined) { 504 | throw new Error(`${action} should include either a key, index, or where property in the payload.`) 505 | } 506 | // if (required.includes('index') && payload.index === undefined) { 507 | // throw new Error(`${action} should include either a key or index property in the payload.`) 508 | // } 509 | } 510 | function handleAction(action, config, required, state) { 511 | if (!config) throw new Error('All actions need to have a configuration object.'); 512 | checkPath(config.path, state); 513 | validatePayload(action, config, required); 514 | // Final configuration for action 515 | let path = config.path; 516 | delete config['path']; 517 | if (path) return Object.assign({ type: `${action}_${path}` }, config); 518 | return Object.assign({ type: action }, config); 519 | } 520 | 521 | class Actions { 522 | constructor(closedState) { 523 | this.closedState = closedState; 524 | } 525 | SET_ALL(config) { 526 | return handleAction('SET_ALL', config, ['value'], this.closedState.state); 527 | } 528 | SET_IN(config) { 529 | return handleAction('SET_IN', config, ['value', 'in'], this.closedState.state); 530 | } 531 | SET(config) { 532 | return handleAction('SET', config, ['value'], this.closedState.state); 533 | } 534 | INCREMENT_ALL(config) { 535 | return handleAction('INCREMENT_ALL', config, ['value'], this.closedState.state); 536 | } 537 | INCREMENT_IN(config) { 538 | return handleAction('INCREMENT_IN', config, ['value', 'in'], this.closedState.state); 539 | } 540 | INCREMENT(config) { 541 | return handleAction('INCREMENT', config, ['value'], this.closedState.state); 542 | } 543 | DECREMENT_ALL(config) { 544 | return handleAction('DECREMENT_ALL', config, ['value'], this.closedState.state); 545 | } 546 | DECREMENT_IN(config) { 547 | return handleAction('DECREMENT_IN', config, ['value', 'in'], this.closedState.state); 548 | } 549 | DECREMENT(config) { 550 | return handleAction('DECREMENT', config, ['value'], this.closedState.state); 551 | } 552 | TOGGLE_ALL(config) { 553 | return handleAction('TOGGLE_ALL', config, [], this.closedState.state); 554 | } 555 | TOGGLE_IN(config) { 556 | return handleAction('TOGGLE_IN', config, ['in'], this.closedState.state); 557 | } 558 | TOGGLE(config) { 559 | return handleAction('TOGGLE', config, [], this.closedState.state); 560 | } 561 | ADD_TO(config) { 562 | return handleAction('ADD_TO', config, ['value'], this.closedState.state); 563 | } 564 | ADD(config) { 565 | return handleAction('ADD', config, ['value'], this.closedState.state); 566 | } 567 | INSERT_IN(config) { 568 | return handleAction('INSERT_IN', config, ['value', 'in'], this.closedState.state); 569 | } 570 | INSERT(config) { 571 | return handleAction('INSERT', config, ['value'], this.closedState.state); 572 | } 573 | UPDATE_IN(config) { 574 | return handleAction('UPDATE_IN', config, ['in'], this.closedState.state); 575 | } 576 | REMOVE_ALL(config) { 577 | return handleAction('REMOVE_ALL', config, [], this.closedState.state); 578 | } 579 | REMOVE_IN(config) { 580 | return handleAction('REMOVE_IN', config, ['in'], this.closedState.state); 581 | } 582 | REMOVE(config) { 583 | return handleAction('REMOVE', config, [], this.closedState.state); 584 | } 585 | MERGE_ALL(config) { 586 | return handleAction('MERGE_ALL', config, ['value'], this.closedState.state); 587 | } 588 | MERGE_IN(config) { 589 | return handleAction('MERGE_IN', config, ['value', 'in'], this.closedState.state); 590 | } 591 | MERGE(config) { 592 | return handleAction('MERGE', config, ['value'], this.closedState.state); 593 | } 594 | } 595 | 596 | class Container { 597 | constructor() { 598 | this.closedState = {}; 599 | this.deduce = (reducer) => { 600 | const initialState = reducer(undefined, {}) 601 | const shouldNormalize = checkForEntities(initialState) 602 | let proxied = shouldNormalize && processState(initialState) 603 | return (state, action) => { 604 | let update = reducer(state, action); 605 | proxied = shouldNormalize ? proxied[replace](update) : update 606 | this.closedState.state = proxied; 607 | if (proxied !== state) return proxied; 608 | if (typeof state === 'number') { 609 | this.closedState.state = switch_number(state, action) 610 | return this.closedState.state; 611 | } 612 | if (typeof state === 'boolean') { 613 | this.closedState.state = switch_boolean(state, action); 614 | return this.closedState.state; 615 | } 616 | if (typeof state === 'string') { 617 | this.closedState.state = switch_string(state, action); 618 | return this.closedState.state; 619 | } 620 | if (Array.isArray(state)) { 621 | this.closedState.state = switch_array(state, action); 622 | return this.closedState.state; 623 | } 624 | if (typeof state === 'object' && state !== null) { 625 | this.closedState.state = switch_object(state, action); 626 | return this.closedState.state; 627 | } 628 | } 629 | }; 630 | this.actions = new Actions(this.closedState); 631 | } 632 | } 633 | 634 | const container = new Container(); 635 | module.exports = { 636 | deduce: container.deduce, 637 | D: container.actions 638 | }; -------------------------------------------------------------------------------- /src/normalizedDataCreation.js: -------------------------------------------------------------------------------- 1 | const { entity, id, foreignKey, proxy } = require('./symbols') 2 | const Validator = require('./validators') 3 | const V = new Validator(entity) 4 | 5 | class NormalizedDataCreator { 6 | constructor(schema, rootEntity) { 7 | this._schema = schema 8 | this._rootEntity = rootEntity 9 | this._normalized = newTables(schema) 10 | } 11 | 12 | addToTable(data) { 13 | const entityType = V.getName(data[entity]) 14 | const newId = this._schema[entityType][id]++ 15 | data[id] = newId 16 | this._normalized[entityType][newId] = data 17 | 18 | return { [foreignKey]: newId, entityType } 19 | } 20 | 21 | addProxy(data, proxyData) { 22 | const proxyId = this._schema[proxy][id]++ 23 | data[proxy] = proxyId 24 | this._normalized[proxy][proxyId] = { ...proxyData, [id]: proxyId } 25 | return proxyId 26 | } 27 | 28 | getNormalizedData() { 29 | return { 30 | normalizedData: this._normalized, 31 | rootEntity: this._rootEntity, 32 | schema: this._schema, 33 | } 34 | } 35 | } 36 | 37 | function newTables(schema) { 38 | let newTables = {} 39 | Reflect.ownKeys(schema).forEach(name => newTables[name] = {}) 40 | return newTables 41 | } 42 | 43 | //exported 44 | function normalizeState(schema, state) { 45 | const normalizer = new NormalizedDataCreator(schema, state) //performs side effects 46 | process(state, { parents: {}, path: [] }) 47 | return normalizer.getNormalizedData() 48 | 49 | function process(state, { parents, path }) { 50 | const elements = V.getElements(state) 51 | if (!elements) return state 52 | if (isVacantArray(state)) return [] 53 | const foreignKey = V.isEntity(state) && normalizer.addToTable(state) //side effect 54 | if (foreignKey) path = [foreignKey] 55 | const proxyId = normalizer.addProxy(state, { parents, path }) //side effect 56 | 57 | parents = { ...parents, [proxyId]: true } 58 | elements.forEach(([key, value]) => { 59 | state[key] = process(value, { parents, path: [...path, key] }) 60 | }) 61 | 62 | return foreignKey || state 63 | } 64 | } 65 | 66 | function isVacantArray(collection) { 67 | return V.typeOf(collection === 'array') && V.isVacentTag(collection[0]) 68 | } 69 | 70 | module.exports = normalizeState 71 | -------------------------------------------------------------------------------- /src/normalizedDataReplacement.js: -------------------------------------------------------------------------------- 1 | const { entity, id, foreignKey, proxy } = require('./symbols') 2 | const secretSymbols = [entity, id, foreignKey, proxy] 3 | const Validator = require('./validators') 4 | const V = new Validator(entity) 5 | 6 | 7 | class NormalizedDataReplacement { 8 | constructor(oldNormalizedData, oldRoot) { 9 | this._old = oldNormalizedData 10 | this._new = {} 11 | this._oldRoot = oldRoot 12 | this._proxyRemovalList = new Set() 13 | } 14 | 15 | getEntity(entityType, fk) { 16 | return this._old[entityType][fk] 17 | } 18 | 19 | getPath(target) { 20 | const proxyId = target[proxy] 21 | return this._old[proxy][proxyId].path 22 | } 23 | 24 | addNewEntity(newEntity) { 25 | const type = V.getName(newEntity[entity]) 26 | const primaryKey = newEntity[id] 27 | if (!this._new[type]) this._new[type] = {} 28 | this._new[type][primaryKey] = newEntity 29 | } 30 | 31 | mergeAtPath(path, oldValue, newValue) { 32 | if (path.length === 0) return this.merge(oldValue, newValue) 33 | this.addToProxyRemovalList(oldValue) 34 | let [key, ...remainingPath] = path 35 | const merged = insertAt(key, oldValue, this.mergeAtPath(remainingPath, oldValue[key], newValue)) 36 | if (V.isEntity(merged)) this.addNewEntity(merged) 37 | else return merged 38 | } 39 | 40 | merge(oldValue, newValue) { 41 | if (isForeignKey(oldValue)) { 42 | const nextEntity = this.getEntity(oldValue.entityType, oldValue[foreignKey]) 43 | this.merge(nextEntity, newValue) 44 | return oldValue 45 | } 46 | 47 | const elements = V.getElements(newValue) 48 | if (!elements) return newValue 49 | if (this.getEntity(proxy, oldValue[proxy]).proxy === newValue) { 50 | return oldValue 51 | } 52 | const ownSecretSymbols = Object.getOwnPropertySymbols(oldValue).filter(s => secretSymbols.includes(s)) 53 | ownSecretSymbols.forEach(s => newValue[s] = oldValue[s]) 54 | elements.forEach( 55 | ([key, value]) => newValue[key] = this.merge(oldValue[key], value) 56 | ) 57 | this.addToProxyRemovalList(newValue) 58 | if (V.isEntity(newValue)) this.addNewEntity(newValue) 59 | else return newValue 60 | 61 | } 62 | 63 | addToProxyRemovalList(entity) { 64 | this._proxyRemovalList.add(entity[proxy]) 65 | } 66 | 67 | addParentProxies(proxyId) { 68 | const parents = this.getProxyParents(proxyId) 69 | parents.forEach(pId => { 70 | if (!this._proxyRemovalList.has(pId)) { 71 | this._proxyRemovalList.add(pId) 72 | this.addParentProxies(pId) 73 | } 74 | }) 75 | } 76 | 77 | getProxyParents(proxyId) { 78 | const parents = this._old[proxy][proxyId].parents 79 | return Object.keys(parents) 80 | } 81 | 82 | clearProxies() { 83 | let proxiesToRemove = this._proxyRemovalList.values() 84 | for (let pId of proxiesToRemove) this.addParentProxies(pId) 85 | proxiesToRemove = this._proxyRemovalList.values() 86 | const newProxyTable = {} 87 | Object.entries(this._old[proxy]).forEach(([key, table]) => newProxyTable[key] = { ...table }) 88 | for (let pId of proxiesToRemove) newProxyTable[pId].proxy = undefined 89 | this._new[proxy] = newProxyTable 90 | } 91 | 92 | mergeInOldData() { 93 | const tables = Object.entries(this._old) 94 | tables.forEach(([tableName, table]) => { 95 | if (!this._new[tableName]) this._new[tableName] = table 96 | else this._new[tableName] = { ...table, ...this._new[tableName] } 97 | }) 98 | } 99 | 100 | getNewRoot() { 101 | const type = this._oldRoot[entity] 102 | const primaryKey = this._oldRoot[id] 103 | return this._new[type][primaryKey] 104 | } 105 | 106 | getNewData() { 107 | return this._new 108 | } 109 | } 110 | 111 | //export 112 | function replace(oldNormalizedData, oldRoot, target, value) { 113 | const newNorm = new NormalizedDataReplacement(oldNormalizedData, oldRoot) 114 | const [{ [foreignKey]: entityId, entityType }, ...path] = newNorm.getPath(target) 115 | const targetEntity = newNorm.getEntity(entityType, entityId) 116 | newNorm.mergeAtPath(path, targetEntity, value) 117 | newNorm.clearProxies() 118 | newNorm.mergeInOldData() 119 | const newNormData = newNorm.getNewData() 120 | const newRoot = newNorm.getNewRoot() 121 | return { normalizedData: newNormData, rootEntity: newRoot } 122 | } 123 | 124 | function insertAt(key, collection, value) { 125 | if (V.typeOf(collection) === 'object') return { ...collection, [key]: value } 126 | else if (V.typeOf(collection) === 'array') { 127 | const firstHalf = collection.slice(0, key) 128 | const secondHalf = collection.slice(key + 1) 129 | return [...firstHalf, value, ...secondHalf] 130 | } 131 | } 132 | 133 | function isForeignKey(data) { 134 | if (V.typeOf(data) !== 'object') return false 135 | const syms = Object.getOwnPropertySymbols(data) 136 | return syms.includes(foreignKey) 137 | } 138 | 139 | module.exports = replace -------------------------------------------------------------------------------- /src/normalizedState.js: -------------------------------------------------------------------------------- 1 | const { entity, id, foreignKey, proxy } = require('./symbols') 2 | const Validator = require('./validators') 3 | const V = new Validator(entity) 4 | const replace = require('./normalizedDataReplacement') 5 | 6 | 7 | class NormalizedState { 8 | constructor({ schema, normalizedData, rootEntity }) { 9 | this.schema = schema 10 | this.normalizedData = normalizedData 11 | this.rootEntity = rootEntity 12 | } 13 | 14 | getEntity(type, primaryKey) { 15 | return this.normalizedData[type][primaryKey] 16 | } 17 | 18 | getSchema() { 19 | return this.schema 20 | } 21 | 22 | getRoot() { 23 | return this.rootEntity 24 | } 25 | 26 | getNormalizedData() { 27 | return this.normalizedData 28 | } 29 | 30 | getProxyFromCache(obj) { 31 | const primaryKey = obj[proxy] 32 | const proxyRecord = this.normalizedData[proxy][primaryKey] 33 | return proxyRecord.proxy 34 | } 35 | 36 | setProxyInCache(obj, proxObj) { 37 | const primaryKey = obj[proxy] 38 | const proxyRecord = this.normalizedData[proxy][primaryKey] 39 | proxyRecord.proxy = proxObj 40 | } 41 | 42 | replace(target, value) { 43 | const { normalizedData, rootEntity } = replace(this.normalizedData, this.rootEntity, target, value) 44 | const schema = this.schema 45 | return new NormalizedState({ normalizedData, rootEntity, schema }) 46 | } 47 | } 48 | 49 | module.exports = NormalizedState -------------------------------------------------------------------------------- /src/proxy.js: -------------------------------------------------------------------------------- 1 | //hidden property access symbols 2 | const { entity, id, foreignKey, getNormalized, getSchema, getRootData, replace, proxy, dataProxy, schemaProxy, normalizedProxy } = require('./symbols') 3 | const secretSymbols = [entity, id, foreignKey, proxy, getNormalized, normalizedProxy, getSchema, schemaProxy, getRootData, dataProxy, replace] 4 | const allowedKeyList = { 5 | data: [], 6 | schema: [entity], 7 | normalized: [id, foreignKey] 8 | } 9 | 10 | const Validator = require('./validators') 11 | const V = new Validator(entity) 12 | 13 | const createSchema = require('./createSchema') 14 | const NormalizedState = require('./normalizedState') 15 | const normalizeState = require('./normalizedDataCreation') 16 | 17 | //exported 18 | function processState(state) { 19 | const schema = createSchema(state) 20 | const data = normalizeState(schema, state) 21 | const normalData = new NormalizedState(data) 22 | return new StateProxy(normalData) 23 | } 24 | 25 | class StateProxy { 26 | constructor(normalizedData, proxyType = 'data') { 27 | this._proxyType = proxyType 28 | this._normalizedData = normalizedData 29 | if (proxyType === 'data') return this._getProxy(normalizedData.getRoot()) 30 | if (proxyType === 'normalized') return this._getProxy(normalizedData.getNormalizedData()) 31 | if (proxyType === 'schema') return this._getProxy(normalizedData.getSchema()) 32 | } 33 | 34 | _getProxy(obj) { 35 | if (this._proxyType !== 'data') return this._createProxy(obj) 36 | else { 37 | let cachedProxy = this._normalizedData.getProxyFromCache(obj) 38 | if (!cachedProxy) { 39 | cachedProxy = this._createProxy(obj) 40 | this._normalizedData.setProxyInCache(obj, cachedProxy) 41 | } 42 | return cachedProxy 43 | } 44 | } 45 | 46 | _createProxy(obj) { 47 | const allowedKeys = allowedKeyList[this._proxyType] 48 | const handler = { 49 | get: (target, prop) => { 50 | if (prop === getNormalized) { 51 | return new StateProxy(this._normalizedData, 'normalized') 52 | } 53 | if (prop === getSchema) { 54 | return new StateProxy(this._normalizedData, 'schema') 55 | } 56 | if (prop === getRootData) return this._getProxy(this._normalizedData.getRoot()) 57 | if (prop === replace) return this._updateFn(target) 58 | if (allowedKeys.includes(prop)) return this._lookupValue(target[prop]) 59 | if (secretSymbols.includes(prop)) return undefined 60 | return this._lookupValue(target[prop]) 61 | }, 62 | ownKeys(target) { 63 | const hiddenKeys = secretSymbols.filter(x => !allowedKeys.includes(x)) 64 | return Reflect.ownKeys(target).filter(x => !hiddenKeys.includes(x)) 65 | } 66 | } 67 | return new Proxy(obj, handler) 68 | } 69 | 70 | _updateFn(target) { 71 | return (newValue) => { 72 | const newNormalData = this._normalizedData.replace(target, newValue) 73 | return new StateProxy(newNormalData) 74 | } 75 | } 76 | 77 | _lookupValue(value) { 78 | const type = V.typeOf(value) 79 | if (type !== 'object' && type !== 'array') return value 80 | if (type === 'object' && this._proxyType === 'data') { 81 | const symbols = Object.getOwnPropertySymbols(value) 82 | if (symbols.includes(foreignKey)) { 83 | const linkedEntity = this._normalizedData.getEntity(value.entityType, value[foreignKey]) 84 | return this._getProxy(linkedEntity) 85 | } 86 | } 87 | return this._getProxy(value) 88 | } 89 | } 90 | 91 | module.exports = processState 92 | 93 | 94 | 95 | //TESTING 96 | const state = { 97 | numbers: [5, 7, 9], 98 | thing: { prop1: 'hello', prop2: 'world' }, 99 | } 100 | // const proxied = processState(state) 101 | // proxied.numbers[0] 102 | // const newState2 = proxied.thing[replace]({ prop1: 'goodbye', prop2: 'world' }) 103 | // console.log('\nreplaced state\n', newState2) 104 | // console.log('\nnormal data before lookup\n', proxied[getNormalized].ROOT) 105 | // console.log('\nproxied first\n', proxied[getNormalized].ROOT) 106 | // newState2.numbers[0] 107 | // console.log(proxied[getSchema]) 108 | // console.log(proxied.thing === proxied.thing) 109 | // console.log('\nproxied after\n', proxied[getNormalized].ROOT) 110 | // console.log('\nproxied after\n', proxied) 111 | // console.log('\nnormal data after lookup\n', proxied[getNormalized].ROOT) 112 | // proxied.numbers[0] 113 | // newState2.thing.prop1 114 | // console.log(proxied.thing) 115 | // const newState1 = proxied.numbers[replace]([1, 2]) 116 | // console.log(newState1) 117 | // newState1.thing.prop1 118 | // console.log('\nnew state 1 is \n', newState1) 119 | // const newState2 = proxied.nu[replace]({ ...proxied.thing, prop1: 'goodbye' }) 120 | // console.log('\nnew state 2 is \n', newState2) 121 | // console.log('\nthe origional state is \n', proxied) -------------------------------------------------------------------------------- /src/replace.js: -------------------------------------------------------------------------------- 1 | const { entity, id, proxy, foreignKey } = require('./symbols') 2 | const specialSymbols = [entity, id, proxy, foreignKey] 3 | const Validator = require('./validators') 4 | const V = new Validator(entity) 5 | 6 | function replaceEntity(oldValue, newValue, normalizedData, rootEntity) { 7 | const proxyKey = oldValue[proxy] 8 | let path = normalizedData[proxy][proxyKey].path 9 | const entityType = path[0].entityType 10 | const entityId = path[0][foreignKey] 11 | const oldTable = normalizedData[entityType] 12 | const oldEntity = oldTable[entityId] 13 | path = path.slice(1) 14 | const newEntity = deepUpdateAt(path, oldEntity, newValue, normalizedData) 15 | const newTable = { ...oldTable, [entityId]: newEntity } 16 | const newProxyTable = clearProxyAndParentProxies(proxyKey, normalizedData) 17 | const newNormalized = { ...normalizedData, [entityType]: newTable, [proxy]: newProxyTable } 18 | return { 19 | newNormalized, 20 | newRoot: rootEntity === oldEntity ? newEntity : rootEntity 21 | } 22 | } 23 | 24 | function deepUpdateAt(path, oldValue, newValue, normalizedData) { 25 | if (path.length === 0) return deepMerge(oldValue, newValue, normalizedData) 26 | let key = path[0] 27 | if (V.typeOf(oldValue) === 'object') 28 | return { 29 | ...oldValue, 30 | [key]: deepUpdateAt(path.slice(1), oldValue[key], newValue, normalizedData) 31 | } 32 | else if (V.typeOf(oldValue) === 'array') { 33 | const newElement = deepUpdateAt(path.slice(1), oldValue[key], newValue, normalizedData) 34 | const firstHalf = oldValue.slice(0, key) 35 | const secondHalf = oldValue.slice(key + 1) 36 | return [...firstHalf, newElement, ...secondHalf] 37 | } 38 | } 39 | 40 | function deepMerge(oldValue, newValue, normalizedData) { 41 | //check for entity boundry 42 | if (getProxy(oldValue, normalizedData) === newValue) return oldValue 43 | 44 | let symbols = Object.getOwnPropertySymbols(oldValue) 45 | .filter(s => specialSymbols.includes(s)) 46 | symbols.forEach(s => newValue[s] = oldValue[s]) 47 | 48 | 49 | return newValue 50 | } 51 | 52 | function getProxy(obj, normalizedData) { 53 | const proxyId = obj[proxy] 54 | return normalizedData[proxy][proxyId] 55 | } 56 | 57 | function clearProxyAndParentProxies(proxyId, normalizedData) { 58 | const oldProxyTable = normalizedData[proxy] 59 | const newProxyTable = { ...oldProxyTable } 60 | const parentIds = getAllParents(oldProxyTable, proxyId) 61 | for (let pId of parentIds) { 62 | const proxyRecordCopy = { ...newProxyTable[pId] } 63 | delete proxyRecordCopy.proxy 64 | newProxyTable[pId] = proxyRecordCopy 65 | } 66 | return newProxyTable 67 | } 68 | 69 | function getAllParents(proxies, proxyId) { 70 | const proxy = proxies[proxyId] 71 | const parents = Object.keys(proxy.parents) 72 | let parentList = [proxyId] 73 | parents.forEach(primaryKey => { 74 | parentList = parentList.concat(getAllParents(proxies, primaryKey)) 75 | }) 76 | return parentList 77 | } 78 | 79 | module.exports = replaceEntity -------------------------------------------------------------------------------- /src/symbols.js: -------------------------------------------------------------------------------- 1 | //internal symbols 2 | const id = Symbol('id') 3 | const foreignKey = Symbol('foreign key') 4 | const proxy = Symbol('proxy') 5 | 6 | //tagging symbol 7 | const entity = Symbol('entity') 8 | 9 | //data access symbols 10 | const getNormalized = Symbol('get-normalized') 11 | const getSchema = Symbol('get-schema') 12 | const getRootData = Symbol('root data') 13 | 14 | //method access symbols 15 | const replace = Symbol('replace') 16 | 17 | module.exports = { 18 | entity, 19 | id, 20 | foreignKey, 21 | getNormalized, 22 | getSchema, 23 | getRootData, 24 | replace, 25 | proxy, 26 | } 27 | -------------------------------------------------------------------------------- /src/validators.js: -------------------------------------------------------------------------------- 1 | const deepEqual = require('deep-equal') 2 | 3 | class Validator { 4 | constructor(entity) { 5 | this._entity = entity 6 | } 7 | 8 | typeOf(value) { 9 | const type = typeof value 10 | if (type !== 'object') return type 11 | if (value === null) return 'null' 12 | if (Array.isArray(value)) return 'array' 13 | return 'object' 14 | } 15 | 16 | getElements(data) { 17 | if (this.typeOf(data) === 'object') return Object.entries(data) 18 | else if (this.typeOf(data) === 'array') return data.map((e, i) => [i, e]) 19 | } 20 | 21 | getName(name) { 22 | if (this.typeOf(name) === 'string') return name 23 | else return (name.name) 24 | } 25 | 26 | isEntity(val) { 27 | return !!(this.typeOf(val) === 'object' && val[this._entity]) 28 | } 29 | 30 | isVacentTag(obj) { 31 | if (!this.isEntity(obj)) return false 32 | const keysAndSymbols = Object.getOwnPropertySymbols(obj).concat(Object.getOwnPropertyNames(obj)) 33 | return keysAndSymbols.length === 1 && keysAndSymbols[0] === this._entity 34 | } 35 | 36 | validateTag(tag) { 37 | if (this.typeOf(tag) === 'string') { 38 | return validateStringTag(tag) 39 | } else if (this.typeOf(tag) === 'object') { 40 | if (this.typeOf(tag.name) === 'string') return validateStringTag(tag.name) 41 | else throw new Error(`Invalid Tag. \n 42 | ${JSON.stringify(tag)} \n 43 | Tag must contain a 'name' property with a string value`) 44 | } 45 | throw new Error(`Invalid Tag. \n 46 | ${tag} \n 47 | Tag must be a string or an object with a 'name' property and a string value`) 48 | 49 | function validateStringTag(tag) { 50 | if (tag[0].toUpperCase() === tag[0]) return 51 | const validPrimitives = [ 52 | 'number', 53 | 'string', 54 | 'boolean', 55 | 'function', 56 | 'null', 57 | 'undefined', 58 | 'object', 59 | 'array' 60 | ] 61 | if (!validPrimitives.includes(tag)) 62 | throw new Error(`Invalid Tag. ${tag} is not a valid primitive.`) 63 | } 64 | } 65 | 66 | checkForInconsistencies(entity1, entity2, name) { 67 | if (!entity1 || !entity2) return 68 | if (!deepEqual(entity1, entity2, { strict: true })) 69 | throw new Error(`Conflicting Schema definitions for ${name}. \n 70 | ${JSON.stringify(entity1)} \n 71 | does not match \n 72 | ${JSON.stringify(entity2)}`) 73 | } 74 | 75 | checkForUndefinedEntities(entities) { 76 | Object.entries(entities).forEach(([key, value]) => { 77 | if (value === undefined) throw new Error(`Could not infer Schema on ${key}`) 78 | }) 79 | } 80 | } 81 | 82 | 83 | module.exports = Validator -------------------------------------------------------------------------------- /test/Array/base.js: -------------------------------------------------------------------------------- 1 | describe('Array', () => { 2 | let reducer; 3 | 4 | before(() => { 5 | reducer = (state = {}, action) => { 6 | switch (action.type) { 7 | default: 8 | return state; 9 | } 10 | }; 11 | reducer = deduce(reducer); 12 | }); 13 | 14 | it('ADD', () => { 15 | expect(reducer( 16 | deepFreeze([true, false]), 17 | { type: 'ADD', value: true } 18 | )).toEqual([true, false, true]); 19 | expect(reducer( 20 | deepFreeze([1, 2, 3]), 21 | { type: 'ADD', value: 4 } 22 | )).toEqual([1, 2, 3, 4]); 23 | expect(reducer( 24 | deepFreeze([{ a: 1 }]), 25 | { type: 'ADD', value: { b: 2 } } 26 | )).toEqual([{ a: 1 }, { b: 2 }]); 27 | expect(reducer( 28 | deepFreeze(['1', '2']), 29 | { type: 'ADD', value: '3' } 30 | )).toEqual(['1', '2', '3']); 31 | }); 32 | it('CONCAT', () => { 33 | expect(reducer( 34 | deepFreeze([true, false]), 35 | { type: 'CONCAT', value: [true, false] } 36 | )).toEqual([true, false, true, false]); 37 | expect(reducer( 38 | deepFreeze([1, 2, 3]), 39 | { type: 'CONCAT', value: [4, 5] } 40 | )).toEqual([1, 2, 3, 4, 5]); 41 | expect(reducer( 42 | deepFreeze([{ a: 1 }]), 43 | { type: 'CONCAT', value: [{ b: 2 }, { c: 3 }] } 44 | )).toEqual([{ a: 1 }, { b: 2 }, { c: 3 }]); 45 | expect(reducer( 46 | deepFreeze(['1', '2', '3']), 47 | { type: 'CONCAT', value: ['4', '5'] } 48 | )).toEqual(['1', '2', '3', '4', '5']); 49 | }); 50 | it('SET_ALL', () => { 51 | expect(reducer( 52 | deepFreeze([false, true, false]), 53 | { type: 'SET_ALL', value: true } 54 | )).toEqual([true, true, true]); 55 | expect(reducer( 56 | deepFreeze([1, 2, 3]), 57 | { type: 'SET_ALL', value: 2 } 58 | )).toEqual([2, 2, 2]); 59 | expect(reducer( 60 | deepFreeze([{ b: 2 }, { c: 3 }]), 61 | { type: 'SET_ALL', value: { d: 4 } } 62 | )).toEqual([{ d: 4 }, { d: 4 }]); 63 | expect(reducer( 64 | deepFreeze(['1', '2', '3']), 65 | { type: 'SET_ALL', value: '2' } 66 | )).toEqual(['2', '2', '2']); 67 | }); 68 | it('SET_IN', () => { 69 | expect(reducer( 70 | deepFreeze([false, false, true]), 71 | { type: 'SET_IN', index: 2, value: false } 72 | )).toEqual([false, false, false]); 73 | expect(reducer( 74 | deepFreeze([false, true]), 75 | { type: 'SET_IN', value: true, where: (val) => val === false } 76 | )).toEqual([true, true]); 77 | expect(reducer( 78 | deepFreeze([1, 2, 3]), 79 | { type: 'SET_IN', index: 0, value: 3 } 80 | )).toEqual([3, 2, 3]); 81 | expect(reducer( 82 | deepFreeze([1, 2, 3]), 83 | { type: 'SET_IN', where: (val) => val === 2, value: 3 } 84 | )).toEqual([1, 3, 3]); 85 | expect(reducer( 86 | deepFreeze([{ b: 2 }, { c: 3 }]), 87 | { type: 'SET_IN', index: 0, value: { d: 4 } } 88 | )).toEqual([{ d: 4 }, { c: 3 }]); 89 | expect(reducer( 90 | deepFreeze([{ b: 2 }, { c: 3 }]), 91 | { type: 'SET_IN', where: (val) => val.hasOwnProperty('c'), value: { d: 4 } } 92 | )).toEqual([{ b: 2 }, { d: 4 }]); 93 | expect(reducer( 94 | deepFreeze(['1', '2', '3']), 95 | { type: 'SET_IN', index: 0, value: '3' } 96 | )).toEqual(['3', '2', '3']); 97 | expect(reducer( 98 | deepFreeze(['1', '2', '3']), 99 | { type: 'SET_IN', where: (val) => val === '2', value: '3' } 100 | )).toEqual(['1', '3', '3']); 101 | }); 102 | it('INSERT', () => { 103 | expect(reducer( 104 | deepFreeze([false, true]), 105 | { type: 'INSERT', value: true, index: 1 } 106 | )).toEqual([false, true, true]); 107 | expect(reducer( 108 | deepFreeze([1, 2, 3]), 109 | { type: 'INSERT', value: 4, index: 1 } 110 | )).toEqual([1, 4, 2, 3]); 111 | expect(reducer( 112 | deepFreeze([{ a: 1 }, { c: 3 }]), 113 | { type: 'INSERT', value: { b: 2 }, index: 1 } 114 | )).toEqual([{ a: 1 }, { b: 2 }, { c: 3 }]); 115 | expect(reducer( 116 | deepFreeze(['1', '2', '3']), 117 | { type: 'INSERT', value: '4', index: 1 } 118 | )).toEqual(['1', '4', '2', '3']); 119 | }); 120 | it('REMOVE_ALL', () => { 121 | expect(reducer( 122 | deepFreeze([true, false]), 123 | { type: 'REMOVE_ALL' } 124 | )).toEqual([]); 125 | expect(reducer( 126 | deepFreeze([1, 2, 3]), 127 | { type: 'REMOVE_ALL' } 128 | )).toEqual([]); 129 | expect(reducer( 130 | deepFreeze([{ a: 1 }, { b: 2 }, { c: 3 }]), 131 | { type: 'REMOVE_ALL' } 132 | )).toEqual([]); 133 | expect(reducer( 134 | deepFreeze(['1', '2', '3']), 135 | { type: 'REMOVE_ALL' } 136 | )).toEqual([]); 137 | }); 138 | it('REMOVE_IN', () => { 139 | expect(reducer( 140 | deepFreeze([true, false]), 141 | { type: 'REMOVE_IN', index: 0 } 142 | )).toEqual([false]); 143 | expect(reducer( 144 | deepFreeze([true, false]), 145 | { type: 'REMOVE_IN', where: value => value === false } 146 | )).toEqual([true]); 147 | expect(reducer( 148 | deepFreeze([true, false]), 149 | { type: 'REMOVE_IN' } 150 | )).toEqual([true]); 151 | 152 | expect(reducer( 153 | deepFreeze([1, 2, 3]), 154 | { type: 'REMOVE_IN' } 155 | )).toEqual([1, 2]); 156 | expect(reducer( 157 | deepFreeze([1, 2, 3]), 158 | { type: 'REMOVE_IN', index: 0 } 159 | )).toEqual([2, 3]); 160 | expect(reducer( 161 | deepFreeze([1, 2, 3]), 162 | { type: 'REMOVE_IN', where: (val) => val === 2 } 163 | )).toEqual([1, 3]); 164 | 165 | expect(reducer( 166 | deepFreeze([{ a: 1 }, { b: 2 }, { c: 3 }]), 167 | { type: 'REMOVE_IN' } 168 | )).toEqual([{ a: 1 }, { b: 2 }]); 169 | expect(reducer( 170 | deepFreeze([{ a: 1 }, { b: 2 }, { c: 3 }]), 171 | { type: 'REMOVE_IN', index: 0 } 172 | )).toEqual([{ b: 2 }, { c: 3 }]); 173 | expect(reducer( 174 | deepFreeze([{ a: 1 }, { b: 2 }, { c: 3 }]), 175 | { type: 'REMOVE_IN', where: (val) => Object.values(val).includes(2) } 176 | )).toEqual([{ a: 1 }, { c: 3 }]); 177 | 178 | expect(reducer( 179 | deepFreeze(['1', '2', '3']), 180 | { type: 'REMOVE_IN' } 181 | )).toEqual(['1', '2']); 182 | expect(reducer( 183 | deepFreeze(['1', '2', '3']), 184 | { type: 'REMOVE_IN', index: 0 } 185 | )).toEqual(['2', '3']); 186 | expect(reducer( 187 | deepFreeze(['1', '2', '3']), 188 | { type: 'REMOVE_IN', where: (val) => val === '2' } 189 | )).toEqual(['1', '3']); 190 | }); 191 | }); -------------------------------------------------------------------------------- /test/Array/bool.js: -------------------------------------------------------------------------------- 1 | describe('Array_Boolean', () => { 2 | let reducer; 3 | 4 | before(() => { 5 | reducer = (state = {}, action) => { 6 | switch (action.type) { 7 | default: 8 | return state; 9 | } 10 | }; 11 | reducer = deduce(reducer); 12 | }); 13 | it('TOGGLE_ALL', () => { 14 | expect(reducer( 15 | deepFreeze([true, false]), 16 | { type: 'TOGGLE_ALL' } 17 | )).toEqual([false, true]); 18 | }); 19 | it('TOGGLE_IN', () => { 20 | expect(reducer( 21 | deepFreeze([true, false]), 22 | { type: 'TOGGLE_IN', index: 0 } 23 | )).toEqual([false, false]); 24 | expect(reducer( 25 | deepFreeze([true, false]), 26 | { type: 'TOGGLE_IN', where: (el) => el === false } 27 | )).toEqual([true, true]); 28 | }); 29 | }); -------------------------------------------------------------------------------- /test/Array/number.js: -------------------------------------------------------------------------------- 1 | describe('Array_Number', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | 13 | it('INCREMENT_ALL', () => { 14 | expect(reducer( 15 | deepFreeze([1, 2, 3]), 16 | { type: 'INCREMENT_ALL', value: 4 } 17 | )).toEqual([5, 6, 7]); 18 | }); 19 | it('INCREMENT_IN', () => { 20 | expect(reducer( 21 | deepFreeze([1, 2, 3]), 22 | { type: 'INCREMENT_IN', value: 4, index: 1 } 23 | )).toEqual([1, 6, 3]); 24 | expect(reducer( 25 | deepFreeze([1, 2, 3]), 26 | { type: 'INCREMENT_IN', value: 4, where: value => value === 2 } 27 | )).toEqual([1, 6, 3]); 28 | }); 29 | it('DECREMENT_ALL', () => { 30 | expect(reducer( 31 | deepFreeze([1, 2, 3]), 32 | { type: 'DECREMENT_ALL',value: 4 } 33 | )).toEqual([-3, -2, -1]); 34 | }); 35 | it('DECREMENT_IN', () => { 36 | expect(reducer( 37 | deepFreeze([1, 6, 3]), 38 | { type: 'DECREMENT_IN', value: 4, index: 1 } 39 | )).toEqual([1, 2, 3]); 40 | expect(reducer( 41 | deepFreeze([1, 6, 3]), 42 | { type: 'DECREMENT_IN', value: 4, where: value => value === 6 } 43 | )).toEqual([1, 2, 3]); 44 | }); 45 | }); -------------------------------------------------------------------------------- /test/Array/object.js: -------------------------------------------------------------------------------- 1 | describe('Array_Object', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | 13 | it('MERGE_ALL', () => { 14 | expect(reducer( 15 | deepFreeze([{ a: 1 }, { b: 2 }, { c: 3 }]), 16 | { type: 'MERGE_ALL', value: { d: 4 } } 17 | )).toEqual([{ a: 1, d: 4 }, { b: 2, d: 4 }, { c: 3, d: 4 }]); 18 | }); 19 | it('MERGE_IN', () => { 20 | expect(reducer( 21 | deepFreeze([{ a: 1 }, { b: 2 }, { c: 3 }]), 22 | { type: 'MERGE_IN', value: { d: 4 }, index: 0 } 23 | )).toEqual([{ a: 1, d: 4 }, { b: 2 }, { c: 3 }]); 24 | expect(reducer( 25 | deepFreeze([{ a: 1 }, { b: 2 }, { c: 3 }]), 26 | { type: 'MERGE_IN', value: { d: 4 }, where: (val) => val.c === 3 } 27 | )).toEqual([{ a: 1 }, { b: 2 }, { c: 3, d: 4 }]); 28 | }); 29 | }); -------------------------------------------------------------------------------- /test/Array/string.js: -------------------------------------------------------------------------------- 1 | describe('Array_String', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | }); -------------------------------------------------------------------------------- /test/Object/array.js: -------------------------------------------------------------------------------- 1 | describe('Object_Array', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | it('ADD_TO_PATH', () => { 13 | expect(reducer( 14 | deepFreeze({ counts: [1, 2] }), 15 | { type: 'ADD_TO_COUNTS', value: 3 } 16 | )).toEqual({ counts: [1, 2, 3] }); 17 | }); 18 | it('INSERT_IN_PATH', () => { 19 | expect(reducer( 20 | deepFreeze({ counts: [1, 3] }), 21 | { type: 'INSERT_IN_COUNTS', value: 2, index: 1 } 22 | )).toEqual({ counts: [1, 2, 3] }); 23 | }); 24 | it('REMOVE_ALL_PATH', () => { 25 | expect(reducer( 26 | deepFreeze({ counts: [1, 3] }), 27 | { type: 'REMOVE_ALL_COUNTS', value: 2, index: 1 } 28 | )).toEqual({ counts: [] }); 29 | }); 30 | it('REMOVE_FROM_PATH', () => { 31 | expect(reducer( 32 | deepFreeze({ counts: [1, 3] }), 33 | { type: 'REMOVE_FROM_COUNTS', index: 0 } 34 | )).toEqual({ counts: [3] }); 35 | expect(reducer( 36 | deepFreeze({ counts: [1, 3] }), 37 | { type: 'REMOVE_FROM_COUNTS', where: (value) => value === 3 } 38 | )).toEqual({ counts: [1] }); 39 | }); 40 | it('SET_ALL_PATH', () => { 41 | expect(reducer( 42 | deepFreeze({ counts: [1, 3] }), 43 | { type: 'SET_ALL_COUNTS', value: 0 } 44 | )).toEqual({ counts: [0, 0] }); 45 | }); 46 | it('SET_IN_PATH', () => { 47 | expect(reducer( 48 | deepFreeze({ counts: [1, 3] }), 49 | { type: 'SET_IN_COUNTS', value: 0, index: 0 } 50 | )).toEqual({ counts: [0, 3] }); 51 | expect(reducer( 52 | deepFreeze({ counts: [1, 3] }), 53 | { type: 'SET_IN_COUNTS', value: 0, where: value => value === 3 } 54 | )).toEqual({ counts: [1, 0] }); 55 | }); 56 | it('TOGGLE_ALL_PATH', () => { 57 | expect(reducer( 58 | deepFreeze({ bools: [false, true] }), 59 | { type: 'TOGGLE_ALL_BOOLS' } 60 | )).toEqual({ bools: [true, false] }); 61 | }); 62 | it('TOGGLE_IN_PATH', () => { 63 | expect(reducer( 64 | deepFreeze({ bools: [false, true] }), 65 | { type: 'TOGGLE_IN_BOOLS', index: 0 } 66 | )).toEqual({ bools: [true, true] }); 67 | expect(reducer( 68 | deepFreeze({ bools: [false, true] }), 69 | { type: 'TOGGLE_IN_BOOLS', where: value => value === false } 70 | )).toEqual({ bools: [true, true] }); 71 | }); 72 | it('INCREMENT_ALL_PATH', () => { 73 | expect(reducer( 74 | deepFreeze({ numbers: [0, 1, 2] }), 75 | { type: 'INCREMENT_ALL_NUMBERS', value: 1 } 76 | )).toEqual({ numbers: [1, 2, 3] }); 77 | }); 78 | it('INCREMENT_IN_PATH', () => { 79 | expect(reducer( 80 | deepFreeze({ numbers: [0, 1, 2] }), 81 | { type: 'INCREMENT_IN_NUMBERS', index: 1, value: 1 } 82 | )).toEqual({ numbers: [0, 2, 2] }); 83 | expect(reducer( 84 | deepFreeze({ numbers: [0, 1, 2] }), 85 | { type: 'INCREMENT_IN_NUMBERS', where: value => value === 2, value: 1 } 86 | )).toEqual({ numbers: [0, 1, 3] }); 87 | }); 88 | it('DECREMENT_IN_PATH', () => { 89 | expect(reducer( 90 | deepFreeze({ numbers: [0, 1, 2] }), 91 | { type: 'DECREMENT_IN_NUMBERS', index: 1, value: 1 } 92 | )).toEqual({ numbers: [0, 0, 2] }); 93 | expect(reducer( 94 | deepFreeze({ numbers: [0, 1, 2] }), 95 | { type: 'DECREMENT_IN_NUMBERS', where: value => value === 2, value: 1 } 96 | )).toEqual({ numbers: [0, 1, 1] }); 97 | }); 98 | }); -------------------------------------------------------------------------------- /test/Object/base.js: -------------------------------------------------------------------------------- 1 | describe('Object', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | it('SET_PATH', () => { 13 | expect(reducer( 14 | deepFreeze({ 15 | items:[], 16 | author:'', 17 | text: '' 18 | }), { type: 'SET_IN_AUTHOR', value: 'Daniel'})).toEqual({ 19 | items:[], 20 | author:'Daniel', 21 | text: '' 22 | }) 23 | }); 24 | it('SET_ALL', () => { 25 | expect(reducer( 26 | deepFreeze({ a: true, b: false }), 27 | { type: 'SET_ALL', value: true } 28 | )).toEqual({ a: true, b: true }); 29 | }); 30 | it('SET_IN', () => { 31 | expect(reducer( 32 | deepFreeze({ a: true, b: false }), 33 | { type: 'SET_IN', key: 'c', value: false } 34 | )).toEqual({ a: true, b: false, c: false }); 35 | expect(reducer( 36 | deepFreeze({ a: true, b: false }), 37 | { type: 'SET_IN', key: 'b', value: true } 38 | )).toEqual({ a: true, b: true }); 39 | expect(reducer( 40 | deepFreeze({ a: true, b: false }), 41 | { type: 'SET_IN', where: (key, val) => { return (key === 'b') && (val === false) }, value: true } 42 | )).toEqual({ a: true, b: true }); 43 | }); 44 | it('MERGE', () => { 45 | expect(reducer( 46 | deepFreeze({ a: true, b: false }), 47 | { type: 'MERGE', value: { b: true, a: false } } 48 | )).toEqual({ a: false, b: true }); 49 | }); 50 | it('REMOVE_ALL', () => { 51 | expect(reducer( 52 | deepFreeze({ a: true, b: false }), 53 | { type: 'REMOVE_ALL' } 54 | )).toEqual({}); 55 | }); 56 | it('REMOVE_IN', () => { 57 | expect(reducer( 58 | deepFreeze({ a: true, b: false }), 59 | { type: 'REMOVE_IN', key: 'a' } 60 | )).toEqual({ b: false }); 61 | expect(reducer( 62 | deepFreeze({ a: true, b: false }), 63 | { type: 'REMOVE_IN', where: (key, value) => key === 'a' && value === true } 64 | )).toEqual({ b: false }); 65 | }); 66 | }); -------------------------------------------------------------------------------- /test/Object/bool.js: -------------------------------------------------------------------------------- 1 | describe('Object_Boolean', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | it('SET_PATH', () => { 13 | expect(reducer( 14 | deepFreeze({ a: true, b: false }), 15 | { type: 'SET_IN_A', value: false } 16 | )).toEqual({ a: false, b: false }); 17 | expect(reducer( 18 | deepFreeze({ test: { a: true, b: false } }), 19 | { type: 'SET_IN_TEST', value: false, key: 'a' } 20 | )).toEqual({ test: { a: false, b: false } }); 21 | expect(reducer( 22 | deepFreeze({ test: { a: true, b: false } }), 23 | { type: 'SET_IN_TEST', value: true, where: (key, value) => key === 'b' && value === false } 24 | )).toEqual({ test: { a: true, b: true } }); 25 | }); 26 | it('TOGGLE_PATH', () => { 27 | expect(reducer( 28 | deepFreeze({ a: true, b: false }), 29 | { type: 'TOGGLE_A' } 30 | )).toEqual({ a: false, b: false }); 31 | expect(reducer( 32 | deepFreeze({ a: true, b: false }), 33 | { type: 'TOGGLE_B' } 34 | )).toEqual({ a: true, b: true }); 35 | }); 36 | it('TOGGLE_IN_PATH', () => { 37 | expect(reducer( 38 | deepFreeze({ test: { a: true, b: false } }), 39 | { type: 'TOGGLE_IN_TEST', value: false, key: 'a' } 40 | )).toEqual({ test: { a: false, b: false } }); 41 | expect(reducer( 42 | deepFreeze({ test: { a: true, b: false } }), 43 | { type: 'TOGGLE_IN_TEST', value: false, where: (key, value) => key === 'a' && value === true } 44 | )).toEqual({ test: { a: false, b: false } }); 45 | }); 46 | it('TOGGLE_IN', () => { 47 | expect(reducer( 48 | {a:true, b:false}, 49 | { type: 'TOGGLE_IN', key: 'a' } 50 | )).toEqual({a:false, b:false}); 51 | expect(reducer( 52 | {a:true, b:false}, 53 | { type: 'TOGGLE_IN', where: (k, v) => v === false} 54 | )).toEqual({a:true, b:true}); 55 | }); 56 | it('TOGGLE_ALL', () => { 57 | expect(reducer( 58 | {a:true, b:false}, 59 | { type: 'TOGGLE_ALL' } 60 | )).toEqual({a:false, b:true}); 61 | }); 62 | }); -------------------------------------------------------------------------------- /test/Object/number.js: -------------------------------------------------------------------------------- 1 | describe('Object_Number', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | it('INCREMENT_IN', () => { 13 | expect(reducer( 14 | deepFreeze({ a: 1, b: 2 }), 15 | { type: 'INCREMENT_IN', value: 3, key: 'b' } 16 | )).toEqual({ a: 1, b: 5 }); 17 | }); 18 | it('DECREMENT_IN', () => { 19 | expect(reducer( 20 | deepFreeze({ a: 1, b: 2 }), 21 | { type: 'DECREMENT_IN', value: 3, key: 'b' } 22 | )).toEqual({ a: 1, b: -1 }); 23 | }); 24 | it('INCREMENT_PATH', () => { 25 | expect(reducer( 26 | deepFreeze({ a: 1, b: 2 }), 27 | { type: 'INCREMENT_A', value: 3 } 28 | )).toEqual({ a: 4, b: 2 }); 29 | expect(reducer( 30 | deepFreeze({ a: 1, b: 2 }), 31 | { type: 'INCREMENT_B', value: 3 } 32 | )).toEqual({ a: 1, b: 5 }); 33 | expect(reducer( 34 | deepFreeze({ A: 1, b: 2 }), 35 | { type: 'INCREMENT_A', value: 3 } 36 | )).toEqual({ A: 4, b: 2 }); 37 | }); 38 | }); -------------------------------------------------------------------------------- /test/Object/object.js: -------------------------------------------------------------------------------- 1 | describe('Object_Object', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | it('MERGE_ALL', () => { 13 | expect(reducer( 14 | deepFreeze({ 15 | 0: { text: 'Make todo list', completed: false }, 16 | 1: { text: 'Check todo list', completed: false } 17 | }), 18 | { type: 'MERGE_ALL', value: { completed: true } } 19 | )).toEqual({ 20 | 0: { text: 'Make todo list', completed: true }, 21 | 1: { text: 'Check todo list', completed: true } 22 | }); 23 | }); 24 | it('MERGE_IN', () => { 25 | expect(reducer( 26 | deepFreeze({ 27 | 0: { text: 'Make todo list', completed: false }, 28 | 1: { text: 'Check todo list', completed: false } 29 | }), 30 | { type: 'MERGE_IN', value: { completed: true } } 31 | )).toEqual({ 32 | 0: { text: 'Make todo list', completed: true }, 33 | 1: { text: 'Check todo list', completed: true } 34 | }); 35 | expect(reducer( 36 | deepFreeze({ a: { x: 0 }, b: { y: 0 }, c: { z: 0 } }), 37 | { type: 'MERGE_IN', value: { d: 4 }, key: 'a' } 38 | )).toEqual({ a: { x: 0, d: 4 }, b: { y: 0 }, c: { z: 0 } }); 39 | expect(reducer( 40 | deepFreeze({ a: { x: 0 }, b: { y: 0 }, c: { z: 0 } }), 41 | { type: 'MERGE_IN', value: { d: 4 }, where: (k, v) => k === 'a' } 42 | )).toEqual({ a: { x: 0, d: 4 }, b: { y: 0 }, c: { z: 0 } }); 43 | // expect(reducer( 44 | // deepFreeze({ 45 | // 0 : {text: 'Make todo list', completed: false }, 46 | // 1 : {text: 'Check todo list', completed: false } 47 | // }), 48 | // {type: 'MERGE_IN', value: { completed: true }, where: (key, value) => key === 0 && value.completed === false } 49 | // )).toEqual({ 50 | // 0 : {text: 'Make todo list', completed: true }, 51 | // 1 : {text: 'Check todo list', completed: true } 52 | // }); 53 | }); 54 | it('MERGE_ALL_PATH', () => { 55 | expect(reducer( 56 | deepFreeze({ 57 | root: { 58 | todos: { 59 | 0: { text: 'Make todo list', completed: false }, 60 | 1: { text: 'Check todo list', completed: false } 61 | } 62 | } 63 | }), 64 | { type: 'MERGE_ALL_ROOT_TODOS', value: { completed: true } } 65 | )).toEqual( 66 | { 67 | root: { 68 | todos: { 69 | 0: { text: 'Make todo list', completed: true }, 70 | 1: { text: 'Check todo list', completed: true } 71 | } 72 | } 73 | }); 74 | }); 75 | it('MERGE_IN_PATH', () => { 76 | expect(reducer( 77 | deepFreeze({ 78 | root: { todos: { 1: { text: 'Make todo list', completed: false } } } 79 | }), 80 | { type: 'MERGE_IN_ROOT_TODOS', value: { completed: true }, key: 1 } 81 | )).toEqual({ root: { todos: { 1: { text: 'Make todo list', completed: true } } } }); 82 | }); 83 | it('SET_IN_PATH', () => { 84 | expect(reducer( 85 | deepFreeze({ counts: {m1: 1, m2: 3} }), 86 | { type: 'SET_IN_COUNTS', value: 0, key: 'm1' } 87 | )).toEqual({ counts: {m1: 0, m2: 3} }); 88 | expect(reducer( 89 | deepFreeze({ counts: {m1: 1, m2: 3} }), 90 | { type: 'SET_IN_COUNTS', value: 0, where: (k, v) => v === 3 } 91 | )).toEqual({ counts: {m1: 1, m2: 0} }); 92 | }); 93 | }); -------------------------------------------------------------------------------- /test/Object/path.js: -------------------------------------------------------------------------------- 1 | describe('Object_Array', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | it('ADD_TO_PATH', () => { 13 | expect(reducer( 14 | deepFreeze({ counts: [1, 2] }), 15 | { type: 'ADD_TO_COUNTS', value: 3 } 16 | )).toEqual({ counts: [1, 2, 3] }); 17 | }); 18 | it('INSERT_IN_PATH', () => { 19 | expect(reducer( 20 | deepFreeze({ counts: [1, 3] }), 21 | { type: 'INSERT_IN_COUNTS', value: 2, index: 1 } 22 | )).toEqual({ counts: [1, 2, 3] }); 23 | }); 24 | it('REMOVE_ALL_PATH', () => { 25 | expect(reducer( 26 | deepFreeze({ counts: [1, 3] }), 27 | { type: 'REMOVE_ALL_COUNTS', value: 2, index: 1 } 28 | )).toEqual({ counts: [] }); 29 | }); 30 | it('REMOVE_FROM_PATH', () => { 31 | expect(reducer( 32 | deepFreeze({ counts: [1, 3] }), 33 | { type: 'REMOVE_FROM_COUNTS', index: 0 } 34 | )).toEqual({ counts: [3] }); 35 | expect(reducer( 36 | deepFreeze({ counts: [1, 3] }), 37 | { type: 'REMOVE_FROM_COUNTS', where: (value) => value === 3 } 38 | )).toEqual({ counts: [1] }); 39 | }); 40 | it('SET_ALL_PATH', () => { 41 | expect(reducer( 42 | deepFreeze({ counts: [1, 3] }), 43 | { type: 'SET_ALL_COUNTS', value: 0 } 44 | )).toEqual({ counts: [0, 0] }); 45 | }); 46 | it('SET_IN_PATH', () => { 47 | expect(reducer( 48 | deepFreeze({ counts: [1, 3] }), 49 | { type: 'SET_IN_COUNTS', value: 0, index: 0 } 50 | )).toEqual({ counts: [0, 3] }); 51 | expect(reducer( 52 | deepFreeze({ counts: [1, 3] }), 53 | { type: 'SET_IN_COUNTS', value: 0, where: value => value === 3 } 54 | )).toEqual({ counts: [1, 0] }); 55 | }); 56 | it('TOGGLE_ALL_PATH', () => { 57 | expect(reducer( 58 | deepFreeze({ bools: [false, true] }), 59 | { type: 'TOGGLE_ALL_BOOLS' } 60 | )).toEqual({ bools: [true, false] }); 61 | }); 62 | it('TOGGLE_IN_PATH', () => { 63 | expect(reducer( 64 | deepFreeze({ bools: [false, true] }), 65 | { type: 'TOGGLE_IN_BOOLS', index: 0 } 66 | )).toEqual({ bools: [true, true] }); 67 | expect(reducer( 68 | deepFreeze({ bools: [false, true] }), 69 | { type: 'TOGGLE_IN_BOOLS', where: value => value === false } 70 | )).toEqual({ bools: [true, true] }); 71 | }); 72 | it('INCREMENT_ALL_PATH', () => { 73 | expect(reducer( 74 | deepFreeze({ numbers: [0, 1, 2] }), 75 | { type: 'INCREMENT_ALL_NUMBERS', value: 1 } 76 | )).toEqual({ numbers: [1, 2, 3] }); 77 | }); 78 | it('INCREMENT_IN_PATH', () => { 79 | expect(reducer( 80 | deepFreeze({ numbers: [0, 1, 2] }), 81 | { type: 'INCREMENT_IN_NUMBERS', index: 1, value: 1 } 82 | )).toEqual({ numbers: [0, 2, 2] }); 83 | expect(reducer( 84 | deepFreeze({ numbers: [0, 1, 2] }), 85 | { type: 'INCREMENT_IN_NUMBERS', where: value => value === 2, value: 1 } 86 | )).toEqual({ numbers: [0, 1, 3] }); 87 | }); 88 | it('DECREMENT_IN_PATH', () => { 89 | expect(reducer( 90 | deepFreeze({ numbers: [0, 1, 2] }), 91 | { type: 'DECREMENT_IN_NUMBERS', index: 1, value: 1 } 92 | )).toEqual({ numbers: [0, 0, 2] }); 93 | expect(reducer( 94 | deepFreeze({ numbers: [0, 1, 2] }), 95 | { type: 'DECREMENT_IN_NUMBERS', where: value => value === 2, value: 1 } 96 | )).toEqual({ numbers: [0, 1, 1] }); 97 | }); 98 | it('MERGE_{PATH}', () => { 99 | expect(reducer( 100 | deepFreeze( 101 | { numbers: { "counts": [0, 1, 2] } } 102 | ), 103 | { type: 'MERGE_NUMBERS', value: { "test": 1 } } 104 | )).toEqual({ numbers: { "counts": [0, 1, 2], "test": 1 } }); 105 | }); 106 | it('MERGE_ALL with PATH', () => { 107 | expect(reducer( 108 | deepFreeze( 109 | { numbers: { "counts": [0, 1, 2] } } 110 | ), 111 | { type: 'MERGE_ALL_NUMBERS', value: { "test": 1 } } 112 | )).toEqual({ numbers: { "counts": [0, 1, 2] } }); 113 | }); 114 | it('MERGE with double PATH', () => { 115 | expect(reducer( 116 | deepFreeze( 117 | { "Country": { "US": { "New York": "NY" } } } 118 | ), 119 | { type: 'MERGE_COUNTRY_US', value: { "North Carolina": "NC" } } 120 | )).toEqual({ 121 | "Country": { 122 | "US": { 123 | "New York": "NY", 124 | "North Carolina": "NC" 125 | } 126 | } 127 | }); 128 | }); 129 | it('MERGE with double PATH', () => { 130 | expect(reducer( 131 | deepFreeze( 132 | { "Country": { "US": { "New York": "NY" } } } 133 | ), 134 | { type: 'MERGE_COUNTRY_US', value: { "North Carolina": "NC" } } 135 | )).toEqual({ 136 | "Country": { 137 | "US": { 138 | "New York": "NY", 139 | "North Carolina": "NC" 140 | } 141 | } 142 | }); 143 | }); 144 | }); -------------------------------------------------------------------------------- /test/Object/string.js: -------------------------------------------------------------------------------- 1 | describe('Object_String', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | it('SET_IN', () => { 13 | expect(reducer( 14 | deepFreeze({ a: 'a', b: 'b' }), 15 | { type: 'SET_IN', value: 'c', key: 'b' } 16 | )).toEqual({ a: 'a', b: 'c' }); 17 | }); 18 | }); -------------------------------------------------------------------------------- /test/action_creator.js: -------------------------------------------------------------------------------- 1 | const { D, dedux } = require('../src/index.js'); 2 | 3 | describe('Action creators', () => { 4 | describe('On Objects', () => { 5 | let reducer; 6 | before(() => { 7 | let initialState = { 8 | a: 1, 9 | b: 2 10 | } 11 | reducer = (state = initialState, action) => { 12 | switch (action.type) { 13 | default: 14 | return state; 15 | } 16 | }; 17 | reducer = deduce(reducer); 18 | reducer(undefined, { type: '@@INIT' }) 19 | }); 20 | it('SET', () => { 21 | expect(D.SET({ path: 'A', value: 3 })).toEqual({ type: 'SET_A', value: 3 }); 22 | }); 23 | }); 24 | describe('On shallow types', () => { 25 | before(() => { 26 | let initialState = 5; 27 | reducer = (state = initialState, action) => { 28 | switch (action.type) { 29 | default: 30 | return state; 31 | } 32 | }; 33 | reducer = deduce(reducer); 34 | reducer(undefined, { type: '@@INIT' }) 35 | }); 36 | it('SET', () => { 37 | expect(D.SET({ value: 3 })).toEqual({ type: 'SET', value: 3 }); 38 | }); 39 | }); 40 | describe('Objects on arrays', () => { 41 | before(() => { 42 | let initialState = { 43 | array: [ {"player": "dan", "score": 3}, {"player": "alex", "score": 5} ] 44 | }; 45 | reducer = (state = initialState, action) => { 46 | switch (action.type) { 47 | default: 48 | return state; 49 | } 50 | }; 51 | reducer = deduce(reducer); 52 | reducer(undefined, { type: '@@INIT' }) 53 | }); 54 | it('SET', () => { 55 | let state = reducer(undefined, { type: '@@INIT' }) 56 | expect(D.MERGE_IN({ path: "ARRAY", value: {"score": 5}, index: 0})).toEqual({"index": 0, "type": "MERGE_IN_ARRAY", "value": {"score": 5}}); 57 | }); 58 | }) 59 | }); -------------------------------------------------------------------------------- /test/boolean.js: -------------------------------------------------------------------------------- 1 | describe('Boolean', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | it('SET', () => { 13 | expect(reducer( 14 | false, 15 | { type: 'SET', value: true } 16 | )).toEqual(true); 17 | }); 18 | it('TOGGLE', () => { 19 | expect(reducer( 20 | false, 21 | { type: 'TOGGLE' } 22 | )).toEqual(true); 23 | }); 24 | it('TOGGLE_PATH', () => { 25 | expect(reducer( 26 | {a:true, b:false}, 27 | { type: 'TOGGLE_A' } 28 | )).toEqual({a:false, b:false}); 29 | }); 30 | }); -------------------------------------------------------------------------------- /test/functions.js: -------------------------------------------------------------------------------- 1 | const actionTypeParser = require('../src/functions/actionTypeParser'); 2 | const updateAtPath = require('../src/functions/updateAtPath'); 3 | const findPath = require('../src/functions/findPath'); 4 | const deepFreeze = require('deep-freeze'); 5 | const checkForEntities = require('../src/functions/checkForEntities') 6 | const { entity } = require('../src/symbols') 7 | 8 | 9 | describe('actionTypeParser', () => { 10 | it('Should parse shallow paths', () => { 11 | let { verb, path } = actionTypeParser('SET_A'); 12 | expect(verb).toEqual('SET'); 13 | expect(path).toEqual('A'); 14 | }); 15 | it('Should parse deep paths', () => { 16 | let { verb, path } = actionTypeParser('SET_ROOT_OBJ_A'); 17 | expect(verb).toEqual('SET'); 18 | expect(path).toEqual('ROOT_OBJ_A'); 19 | }); 20 | it('Should parse actions with compound verbs', () => { 21 | let { verb, path } = actionTypeParser('ADD_TO_A'); 22 | expect(verb).toEqual('ADD_TO'); 23 | expect(path).toEqual('A'); 24 | }); 25 | it('Should error for non-existent action', () => { 26 | let { verb, path } = actionTypeParser('REMOVE_TO'); 27 | }); 28 | }); 29 | 30 | describe('updateAtPath', () => { 31 | it('Should set at shallow paths in simple objects', () => { 32 | let state = deepFreeze({ a: 1 }); 33 | state = updateAtPath(['A'], state, (obj) => { return 2 }); 34 | expect(state).toEqual({ a: 2 }) 35 | }); 36 | it('Should set at shallow paths in nested objects', () => { 37 | let state = deepFreeze({ root: { a: 1 } }); 38 | state = updateAtPath(['ROOT'], state, (obj) => { return { ...obj, a: 2 } }); 39 | expect(state).toEqual({ root: { a: 2 } }) 40 | }); 41 | it('Should set at nested paths', () => { 42 | let state = deepFreeze({ root: { a: 1 } }); 43 | state = updateAtPath(['ROOT', 'A'], state, (obj) => { return 2 }); 44 | expect(state).toEqual({ root: { a: 2 } }) 45 | }); 46 | }); 47 | describe('findPath', () => { 48 | it('Should find shallow paths in simple objects', () => { 49 | const state = { root: '1' }; 50 | expect(findPath('ROOT', state)).toEqual(['ROOT']); 51 | }); 52 | it('Should find shallow paths in simple objects', () => { 53 | const state = { a: '1', b: '2' }; 54 | expect(findPath('B', state)).toEqual(['B']); 55 | }); 56 | it('Should find shallow paths in nested objects', () => { 57 | const state = { root: { a: 1, b: 2 } }; 58 | expect(findPath('ROOT', state)).toEqual(['ROOT']); 59 | }); 60 | it('Should find deep paths in nested objects', () => { 61 | const state = { root: { a: 1, b: 2 } }; 62 | expect(findPath('B', state)).toEqual(['ROOT_B']); 63 | }); 64 | it('Should find deep paths in nested objects', () => { 65 | const state = { root: { secondA: { a: 1, b: 2 }, secondB: { a: 1, b: 2 } } }; 66 | expect(findPath('SECONDA', state)).toEqual(['ROOT_SECONDA']); 67 | expect(findPath('A', state)).toEqual(['ROOT_SECONDA_A', 'ROOT_SECONDB_A']); 68 | }); 69 | it('Should find paths at multiple nesting levels', () => { 70 | const state = { root: { a: 1, secondA: { a: 1, b: 2 }, secondB: { a: 1, c: 2 } } }; 71 | expect(findPath('SECONDA', state)).toEqual(['ROOT_SECONDA']); 72 | expect(findPath('A', state)).toEqual(['ROOT_A', 'ROOT_SECONDA_A', 'ROOT_SECONDB_A']); 73 | expect(findPath('C', state)).toEqual(['ROOT_SECONDB_C']); 74 | }); 75 | it('Should not break on incorrent path', () => { 76 | const state = { root: { a: 1, secondA: { a: 1, b: 2 }, secondB: { a: 1, b: 2 } } }; 77 | expect(findPath('SECONDA', state)).toEqual(['ROOT_SECONDA']); 78 | expect(findPath('C', state)).toEqual([]); 79 | }); 80 | it('Long hand paths should work', () => { 81 | const state = { root: { a: 1, secondA: { a: 1, b: 2 }, secondB: { a: 1, b: 2 } } }; 82 | expect(findPath('SECONDA', state)).toEqual(['ROOT_SECONDA']); 83 | expect(findPath('ROOT_SECONDA', state)).toEqual(['ROOT_SECONDA']); 84 | }); 85 | it('Should not traverse arrays', () => { 86 | const state = { 87 | "example": [{ "test": true }], 88 | "test": true 89 | }; 90 | expect(findPath("TEST", state)).toEqual(['TEST']) 91 | }) 92 | }) 93 | 94 | describe('checkForEntities', () => { 95 | it('returns true when there is an entity tag in the state', () => { 96 | const state = { 97 | foo: [1, 2, 3], 98 | bar: { [entity]: 'Test', it: 'other' } 99 | } 100 | expect(checkForEntities(state)).toEqual(true) 101 | }) 102 | it('returns false when there is no entity tag in the state', () => { 103 | const state = { 104 | foo: [1, 2, 3], 105 | bar: { it: 'other' } 106 | } 107 | expect(checkForEntities(state)).toEqual(false) 108 | }) 109 | }) -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | deduce = require('../src/index.js')['deduce']; 2 | expect = require('expect'); 3 | deepFreeze = require('deep-freeze'); 4 | 5 | require('./functions'); 6 | require('./number'); 7 | require('./boolean'); 8 | require('./string'); 9 | require('./action_creator'); 10 | require('./Array/base'); 11 | require('./Object/base'); 12 | require('./Array/number'); 13 | require('./Array/bool'); 14 | require('./Array/string'); 15 | require('./Array/object'); 16 | require('./Object/number'); 17 | require('./Object/bool'); 18 | require('./Object/string'); 19 | require('./Object/array'); 20 | require('./Object/object'); 21 | require('./Object/path'); 22 | 23 | describe('Custom Reducers', () => { 24 | let reducer; 25 | before(() => { 26 | reducer = (state = {}, action) => { 27 | switch (action.type) { 28 | default: 29 | return state; 30 | } 31 | }; 32 | reducer = deduce(reducer); 33 | }); 34 | it('No change for non-existent action type.', () => { 35 | expect(reducer( 36 | 0, 37 | { type: 'NOT_AN_ACTION' } 38 | )).toEqual(0); 39 | }); 40 | }); -------------------------------------------------------------------------------- /test/number.js: -------------------------------------------------------------------------------- 1 | describe('Number', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | it('SET', () => { 13 | expect(reducer( 14 | 0, 15 | { type: 'SET', value: 6 } 16 | )).toEqual(6); 17 | }); 18 | it('INCREMENT', () => { 19 | expect(reducer( 20 | 0, 21 | { type: 'INCREMENT', value: 1 } 22 | )).toEqual(1); 23 | expect(reducer( 24 | 1, 25 | { type: 'INCREMENT', value: 1 } 26 | )).toEqual(2); 27 | expect(reducer( 28 | 1, 29 | { type: 'INCREMENT', value: 2 } 30 | )).toEqual(3); 31 | }); 32 | it('DECREMENT', () => { 33 | expect(reducer( 34 | 0, 35 | { type: 'DECREMENT', value: 1 } 36 | )).toEqual(-1); 37 | expect(reducer( 38 | 2, 39 | { type: 'DECREMENT', value: 1 } 40 | )).toEqual(1); 41 | expect(reducer( 42 | 2, 43 | { type: 'DECREMENT', value: 2 } 44 | )).toEqual(0); 45 | }); 46 | it('INCREMENT_PATH', () => { 47 | expect(reducer( 48 | {a:1, b:2}, 49 | { type: 'INCREMENT_A', value: 1 } 50 | )).toEqual({a:2, b:2}); 51 | }); 52 | it('DECREMENT_PATH', () => { 53 | expect(reducer( 54 | {a:1, b:2}, 55 | { type: 'DECREMENT_B', value: 1 } 56 | )).toEqual({a:1, b:1}); 57 | }); 58 | }); -------------------------------------------------------------------------------- /test/string.js: -------------------------------------------------------------------------------- 1 | describe('String', () => { 2 | let reducer; 3 | before(() => { 4 | reducer = (state = {}, action) => { 5 | switch (action.type) { 6 | default: 7 | return state; 8 | } 9 | }; 10 | reducer = deduce(reducer); 11 | }); 12 | it('SET', () => { 13 | expect(reducer( 14 | 'Old String', 15 | { type: 'SET', value: 'New String' } 16 | )).toEqual('New String'); 17 | }); 18 | }); -------------------------------------------------------------------------------- /test/testForEntities.js: -------------------------------------------------------------------------------- 1 | const { entity } = require('../src/symbols') 2 | 3 | describe('states with entity symbols', () => { 4 | let reducer; 5 | const initialState = { 6 | [entity]: 'Trainer', 7 | name: 'Bill', 8 | age: 27, 9 | pokemon: [{ [entity]: 'Pokemon', name: 'Pikachu' }] 10 | } 11 | before(() => { 12 | reducer = (state = initialState, action) => { 13 | switch (action.type) { 14 | default: 15 | return state; 16 | } 17 | }; 18 | reducer = deduce(reducer); 19 | }) 20 | it('get converted to proxied objects', () => { 21 | reducer(initialState, {}) 22 | }) 23 | }) -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const libraryName = 'dedux'; 4 | const outputFile = libraryName + '.js' 5 | 6 | let config = { 7 | entry: __dirname + '/src/index.js', 8 | devtool: 'source-map', 9 | output: { 10 | path: path.resolve(__dirname, 'build'), 11 | filename: outputFile, 12 | library: libraryName, 13 | libraryTarget: 'umd', 14 | umdNamedDefine: true, 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | use: 'babel-loader', 21 | exclude: /node_modules/, 22 | } 23 | ] 24 | } 25 | } 26 | 27 | module.exports = config; --------------------------------------------------------------------------------