├── .eslintrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── OSSMETADATA ├── README.md ├── flow-typed └── npm │ ├── chai_v4.x.x.js │ └── mocha_v3.1.x.js ├── package.json ├── src ├── factories.js ├── index.d.ts ├── index.js └── merge.js └── test ├── .eslintrc ├── factories-test.js └── merge-test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // eslint: recommended automatically enables most/all rules from the 3 | // possible errors section and more: 4 | // http://eslint.org/docs/rules/#possible-errors 5 | "extends": "eslint:recommended", 6 | "plugins": [ 7 | "@lrowe/flow-remove-types" 8 | ], 9 | "env": { 10 | "browser": false, 11 | "node": true, 12 | "es6": true 13 | }, 14 | "rules": { 15 | // possible errors 16 | "no-cond-assign": [ 2 ], 17 | "no-constant-condition": [ 2 ], 18 | "no-control-regex": [ 2 ], 19 | "no-debugger": [ 2 ], 20 | "no-dupe-args": [ 2 ], 21 | "no-dupe-keys": [ 2 ], 22 | "no-duplicate-case": [ 2 ], 23 | "no-empty": [ 2 ], 24 | "no-empty-character-class": [ 2 ], 25 | "no-ex-assign": [ 2 ], 26 | "no-extra-boolean-cast": [ 2 ], 27 | "no-extra-semi": [ 2 ], 28 | "no-func-assign": [ 2 ], 29 | // this is for variable hoisting, not necessary if we use block scoped declarations 30 | // "no-inner-declarations": [ 2, "both" ], 31 | "no-invalid-regexp": [ 2 ], 32 | "no-irregular-whitespace": [ 2 ], 33 | "no-negated-in-lhs": [ 2 ], 34 | "no-reserved-keys": [ 0 ], 35 | "no-regex-spaces": [ 2 ], 36 | "no-sparse-arrays": [ 2 ], 37 | "no-unreachable": [ 2 ], 38 | "use-isnan": [ 2 ], 39 | "valid-typeof": [ 2 ], 40 | "valid-jsdoc": [ 2, { 41 | "requireReturnDescription": false 42 | }], 43 | 44 | // best practices 45 | "array-callback-return": [ 2 ], 46 | "block-scoped-var": [ 2 ], 47 | "complexity": [ 1 ], 48 | "consistent-return": [ 2 ], 49 | "curly": [ 2 ], 50 | "default-case": [ 2 ], 51 | "dot-notation": [ 2, { "allowKeywords": true } ], 52 | "eqeqeq": [ 2 ], 53 | "guard-for-in": [ 2 ], 54 | "no-alert": [ 2 ], 55 | "no-caller": [ 2 ], 56 | "no-case-declarations": [ 2 ], 57 | "no-div-regex": [ 2 ], 58 | "no-empty-function": [ 2 ], 59 | "no-empty-pattern": [ 2 ], 60 | "no-eq-null": [ 2 ], 61 | "no-eval": [ 2 ], 62 | "no-extend-native": [ 2 ], 63 | "no-extra-bind": [ 2 ], 64 | "no-extra-label": [ 2 ], 65 | "no-fallthrough": [ 2 ], 66 | "no-floating-decimal": [ 2 ], 67 | "no-implicit-coercion": [ 2 ], 68 | "no-implied-eval": [ 2 ], 69 | "no-iterator": [ 2 ], 70 | "no-labels": [ 2 ], 71 | "no-lone-blocks": [ 2 ], 72 | "no-loop-func": [ 2 ], 73 | "no-magic-numbers": [ 0 ], 74 | "no-multi-spaces": [ 0 ], 75 | "no-native-reassign": [ 2 ], 76 | "no-new": [ 2 ], 77 | "no-new-func": [ 2 ], 78 | "no-new-wrappers": [ 2 ], 79 | "no-octal": [ 2 ], 80 | "no-octal-escape": [ 2 ], 81 | "no-param-reassign": [ 2 ], 82 | "no-proto": [ 2 ], 83 | "no-redeclare": [ 2 ], 84 | "no-return-assign": [ 2 ], 85 | "no-script-url": [ 2 ], 86 | "no-self-assign": [ 2 ], 87 | "no-self-compare": [ 2 ], 88 | "no-sequences": [ 2 ], 89 | "no-throw-literal": [ 2 ], 90 | "no-unmodified-loop-condition": [ 2 ], 91 | "no-unused-expressions": [ 2 ], 92 | "no-unused-labels": [ 2 ], 93 | "no-useless-call": [ 2 ], 94 | "no-useless-concat": [ 2 ], 95 | "no-void": [ 2 ], 96 | //"no-warning-comments": [ 1 ], // XXX still in development 97 | "no-with": [ 2 ], 98 | "wrap-iife": [ 2 ], 99 | "yoda": [ 2, "never" ], 100 | 101 | // strict mode 102 | "strict": [ 2, "global" ], 103 | 104 | // variables 105 | "no-var": [ 2 ], 106 | "no-catch-shadow": [ 2 ], 107 | "no-delete-var": [ 2 ], 108 | "no-shadow": [ 2 ], 109 | "no-shadow-restricted-names": [ 2 ], 110 | "no-undef": [ 2 ], 111 | "no-undef-init": [ 2 ], 112 | // "no-undefined": [ 2 ], 113 | "no-unused-vars": [ 2, { "vars": "all", "args": "none" } ], 114 | "no-use-before-define": [ 2, "nofunc" ], 115 | 116 | // node.js 117 | "callback-return": [ 2, [ "callback", "cb", "cb1", "cb2", "cb3", "next", "innerCb", "done" ]], 118 | "global-require": [ 2 ], 119 | "handle-callback-err": [ 2, "^.*(e|E)rr" ], 120 | "no-mixed-requires": [ 2 ], 121 | "no-new-require": [ 2 ], 122 | "no-path-concat": [ 2 ], 123 | "no-process-exit": [ 2 ], 124 | 125 | // stylistic 126 | // turn on the few on that aren't handled by JSCS. 127 | "consistent-this": [ 2, "self" ], 128 | "no-array-constructor": [ 2 ], 129 | "no-nested-ternary": [ 2 ], 130 | "no-new-object": [ 2 ], 131 | 132 | // es6 133 | "no-class-assign": [ 2 ], 134 | "no-dupe-class-members": [ 2 ], 135 | "no-new-symbol": [ 2 ], 136 | "no-const-assign": [ 2 ] 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /lib/.* 3 | /node_modules/conventional-changelog-core/test/.* 4 | /node_modules/flow-coverage-report/.* 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | .DS_Store 4 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | before_install: 5 | - npm install -g npm@5 6 | script: 7 | - npm test 8 | - npm run flow 9 | - npm run lint 10 | - npm run check-formatting 11 | - npm run flow-coverage 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 3.1.0 (2019-01-14) 3 | 4 | 5 | #### Features 6 | 7 | * **merge:** add errors merge (#15) ([979ba92f](https://github.com/Netflix/falcor-json-graph.git/commit/979ba92f)) 8 | 9 | 10 | 11 | ## 3.0.0 (2019-01-08) 12 | 13 | 14 | #### Bug Fixes 15 | 16 | * **merge:** merge paths even if one side is missing (#14) BREAKING CHANGE: paths will be pre ([794b0baf](https://github.com/Netflix/falcor-json-graph.git/commit/794b0baf)) 17 | 18 | 19 | #### Breaking Changes 20 | 21 | * paths will be present if any of the sides have paths in the envelope, earlier it was skipped if any of them were missing 22 | 23 | ([794b0baf](https://github.com/Netflix/falcor-json-graph.git/commit/794b0baf)) 24 | 25 | 26 | # Change Log 27 | 28 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 29 | 30 | 31 | ## [2.2.2](https://github.com/Netflix/falcor-json-graph/compare/v2.2.1...v2.2.2) (2018-08-24) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * **flow:** make key range types discriminable. ([c7924c8](https://github.com/Netflix/falcor-json-graph/commit/c7924c8)) 37 | 38 | 39 | 40 | 41 | ## [2.2.1](https://github.com/Netflix/falcor-json-graph/compare/v2.2.0...v2.2.1) (2018-01-03) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * Make JsonGraphLeaf types invariant for compatibility with upcoming AtomOrError type. ([231107e](https://github.com/Netflix/falcor-json-graph/commit/231107e)) 47 | 48 | 49 | 50 | 51 | # [2.2.0](https://github.com/Netflix/falcor-json-graph/compare/v2.1.1...v2.2.0) (2017-12-22) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * **types:** IDisposable.isDisposed should be read only. ([57d7d94](https://github.com/Netflix/falcor-json-graph/commit/57d7d94)) 57 | 58 | 59 | ### Features 60 | 61 | * mergeJsonGraph, mergeJsonGraphEnvelope ([774b5f5](https://github.com/Netflix/falcor-json-graph/commit/774b5f5)) 62 | 63 | 64 | 65 | 66 | ## [2.1.1](https://github.com/Netflix/falcor-json-graph/compare/v2.1.0...v2.1.1) (2017-12-21) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * JsonMap and JsonGraph types should not assume values are present. ([2bade2d](https://github.com/Netflix/falcor-json-graph/commit/2bade2d)) 72 | 73 | 74 | 75 | 76 | # [2.1.0](https://github.com/Netflix/falcor-json-graph/compare/v2.0.0...v2.1.0) (2017-12-12) 77 | 78 | 79 | ### Features 80 | 81 | * add Flow type definitions and expose undefined atom constructor as undefinedAtom. ([5305f93](https://github.com/Netflix/falcor-json-graph/commit/5305f93)) 82 | 83 | 84 | 85 | 86 | # [2.0.0](https://github.com/Netflix/falcor-json-graph/compare/v1.1.5...v2.0.0) (2015-10-03) 87 | 88 | 89 | * Placeholder for v2.0.0 ([8e65543](https://github.com/Netflix/falcor-json-graph/commit/8e65543)) 90 | 91 | ### BREAKING CHANGES 92 | 93 | * Removed falcor-path-syntax dependency. Paths must now be specified using array syntax. 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /OSSMETADATA: -------------------------------------------------------------------------------- 1 | osslifecycle=active 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # falcor-json-graph 2 | 3 | A set of factory functions for creating JSON Graph values. 4 | 5 | ## API 6 | 7 | ```JavaScript 8 | var jsonGraph = require('falcor-json-graph'); 9 | 10 | // { $type: "atom", value: "a string wrapped in an atom" } 11 | var atom = jsonGraph.atom("a string wrapped in an atom"); 12 | 13 | // { $type: "atom" } 14 | var undefinedAtom = jsonGraph.undefinedAtom(); 15 | 16 | // { $type: "ref", value: ["todos", 0, "name"] } 17 | var ref = jsonGraph.ref(["todos", 0, "name"]); 18 | 19 | // { $type: "error", value: "something bad happened." } 20 | var error = jsonGraph.error("something bad happened."); 21 | 22 | // { path: [ 'user', 'age' ], value: 25 } 23 | var pathValue = jsonGraph.pathValue(["user", "age"], 25); 24 | 25 | // { path: [ 'user', 'age' ], invalidated: true } 26 | var pathValue = jsonGraph.pathInvalidation(["user", "age"]) 27 | ``` 28 | -------------------------------------------------------------------------------- /flow-typed/npm/chai_v4.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: ab80ff7a1ea79db9ff159445aa3ca9b0 2 | // flow-typed version: 18b0a77361/chai_v4.x.x/flow_>=v0.48.0 3 | 4 | declare module "chai" { 5 | 6 | declare type ExpectChain = { 7 | and: ExpectChain, 8 | at: ExpectChain, 9 | be: ExpectChain, 10 | been: ExpectChain, 11 | have: ExpectChain, 12 | has: ExpectChain, 13 | is: ExpectChain, 14 | of: ExpectChain, 15 | same: ExpectChain, 16 | that: ExpectChain, 17 | to: ExpectChain, 18 | which: ExpectChain, 19 | with: ExpectChain, 20 | 21 | not: ExpectChain, 22 | deep: ExpectChain, 23 | any: ExpectChain, 24 | all: ExpectChain, 25 | 26 | a: ExpectChain & (type: string) => ExpectChain, 27 | an: ExpectChain & (type: string) => ExpectChain, 28 | 29 | include: ExpectChain & (value: mixed) => ExpectChain, 30 | includes: ExpectChain & (value: mixed) => ExpectChain, 31 | contain: ExpectChain & (value: mixed) => ExpectChain, 32 | contains: ExpectChain & (value: mixed) => ExpectChain, 33 | 34 | eql: (value: T) => ExpectChain, 35 | equal: (value: T) => ExpectChain, 36 | equals: (value: T) => ExpectChain, 37 | 38 | above: (value: T & number) => ExpectChain, 39 | least: (value: T & number) => ExpectChain, 40 | below: (value: T & number) => ExpectChain, 41 | most: (value: T & number) => ExpectChain, 42 | within: (start: T & number, finish: T & number) => ExpectChain, 43 | 44 | instanceof: (constructor: mixed) => ExpectChain, 45 | property: ( 46 |

(name: string, value?: P) => ExpectChain

47 | & (name: string) => ExpectChain 48 | ), 49 | 50 | length: (value: number) => ExpectChain | ExpectChain, 51 | lengthOf: (value: number) => ExpectChain, 52 | 53 | match: (regex: RegExp) => ExpectChain, 54 | string: (string: string) => ExpectChain, 55 | 56 | key: (key: string) => ExpectChain, 57 | keys: (key: string | Array, ...keys: Array) => ExpectChain, 58 | 59 | throw: ( 60 | err?: Class | Error | RegExp | string, 61 | errMsgMatcher?: RegExp | string, 62 | msg?: string) => ExpectChain, 63 | 64 | respondTo: (method: string) => ExpectChain, 65 | itself: ExpectChain, 66 | 67 | satisfy: (method: (value: T) => bool) => ExpectChain, 68 | 69 | closeTo: (expected: T & number, delta: number) => ExpectChain, 70 | 71 | members: (set: mixed) => ExpectChain, 72 | oneOf: (list: Array) => ExpectChain, 73 | 74 | change: (obj: mixed, key: string) => ExpectChain, 75 | increase: (obj: mixed, key: string) => ExpectChain, 76 | decrease: (obj: mixed, key: string) => ExpectChain, 77 | 78 | // dirty-chai 79 | ok: () => ExpectChain, 80 | true: () => ExpectChain, 81 | false: () => ExpectChain, 82 | null: () => ExpectChain, 83 | undefined: () => ExpectChain, 84 | exist: () => ExpectChain, 85 | empty: () => ExpectChain, 86 | 87 | extensible: () => ExpectChain, 88 | sealed: () => ExpectChain, 89 | frozen: () => ExpectChain, 90 | 91 | // chai-immutable 92 | size: (n: number) => ExpectChain, 93 | 94 | // sinon-chai 95 | called: () => ExpectChain, 96 | callCount: (n: number) => ExpectChain, 97 | calledOnce: () => ExpectChain, 98 | calledTwice: () => ExpectChain, 99 | calledThrice: () => ExpectChain, 100 | calledBefore: (spy: mixed) => ExpectChain, 101 | calledAfter: (spy: mixed) => ExpectChain, 102 | calledWith: (...args: Array) => ExpectChain, 103 | calledWithMatch: (...args: Array) => ExpectChain, 104 | calledWithExactly: (...args: Array) => ExpectChain, 105 | 106 | // chai-as-promised 107 | eventually: ExpectChain, 108 | resolvedWith: (value: mixed) => Promise & ExpectChain, 109 | resolved: () => Promise & ExpectChain, 110 | rejectedWith: (value: mixed) => Promise & ExpectChain, 111 | rejected: () => Promise & ExpectChain, 112 | notify: (callback: () => mixed) => ExpectChain, 113 | 114 | // chai-subset 115 | containSubset: (obj: Object | Object[]) => ExpectChain 116 | }; 117 | 118 | declare function expect(actual: T): ExpectChain; 119 | 120 | declare function use(plugin: (chai: Object, utils: Object) => void): void; 121 | 122 | declare class assert { 123 | static(expression: mixed, message?: string): void; 124 | static fail(actual: mixed, expected: mixed, message?: string, operator?: string): void; 125 | 126 | static isOk(object: mixed, message?: string): void; 127 | static isNotOk(object: mixed, message?: string): void; 128 | 129 | static equal(actual: mixed, expected: mixed, message?: string): void; 130 | static notEqual(actual: mixed, expected: mixed, message?: string): void; 131 | 132 | static strictEqual(act: mixed, exp: mixed, msg?: string): void; 133 | static notStrictEqual(act: mixed, exp: mixed, msg?: string): void; 134 | 135 | static deepEqual(act: mixed, exp: mixed, msg?: string): void; 136 | static notDeepEqual(act: mixed, exp: mixed, msg?: string): void; 137 | 138 | static ok(val: mixed, msg?: string): void; 139 | static isTrue(val: mixed, msg?: string): void; 140 | static isNotTrue(val: mixed, msg?: string): void; 141 | static isFalse(val: mixed, msg?: string): void; 142 | static isNotFalse(val: mixed, msg?: string): void; 143 | 144 | static isNull(val: mixed, msg?: string): void; 145 | static isNotNull(val: mixed, msg?: string): void; 146 | 147 | static isUndefined(val: mixed, msg?: string): void; 148 | static isDefined(val: mixed, msg?: string): void; 149 | 150 | static isNaN(val: mixed, msg?: string): void; 151 | static isNotNaN(val: mixed, msg?: string): void; 152 | 153 | static isAbove(val: number, abv: number, msg?: string): void; 154 | static isBelow(val: number, blw: number, msg?: string): void; 155 | 156 | static isAtMost(val: number, atmst: number, msg?: string): void; 157 | static isAtLeast(val: number, atlst: number, msg?: string): void; 158 | 159 | static isFunction(val: mixed, msg?: string): void; 160 | static isNotFunction(val: mixed, msg?: string): void; 161 | 162 | static isObject(val: mixed, msg?: string): void; 163 | static isNotObject(val: mixed, msg?: string): void; 164 | 165 | static isArray(val: mixed, msg?: string): void; 166 | static isNotArray(val: mixed, msg?: string): void; 167 | 168 | static isString(val: mixed, msg?: string): void; 169 | static isNotString(val: mixed, msg?: string): void; 170 | 171 | static isNumber(val: mixed, msg?: string): void; 172 | static isNotNumber(val: mixed, msg?: string): void; 173 | 174 | static isBoolean(val: mixed, msg?: string): void; 175 | static isNotBoolean(val: mixed, msg?: string): void; 176 | 177 | static typeOf(val: mixed, type: string, msg?: string): void; 178 | static notTypeOf(val: mixed, type: string, msg?: string): void; 179 | 180 | static instanceOf(val: mixed, constructor: Function, msg?: string): void; 181 | static notInstanceOf(val: mixed, constructor: Function, msg?: string): void; 182 | 183 | static include(exp: string, inc: mixed, msg?: string): void; 184 | static include(exp: Array, inc: T, msg?: string): void; 185 | 186 | static notInclude(exp: string, inc: mixed, msg?: string): void; 187 | static notInclude(exp: Array, inc: T, msg?: string): void; 188 | 189 | static match(exp: mixed, re: RegExp, msg?: string): void; 190 | static notMatch(exp: mixed, re: RegExp, msg?: string): void; 191 | 192 | static property(obj: Object, prop: string, msg?: string): void; 193 | static notProperty(obj: Object, prop: string, msg?: string): void; 194 | static deepProperty(obj: Object, prop: string, msg?: string): void; 195 | static notDeepProperty(obj: Object, prop: string, msg?: string): void; 196 | 197 | static propertyVal(obj: Object, prop: string, val: mixed, msg?: string): void; 198 | static propertyNotVal(obj: Object, prop: string, val: mixed, msg?: string): void; 199 | 200 | static deepPropertyVal(obj: Object, prop: string, val: mixed, msg?: string): void; 201 | static deepPropertyNotVal(obj: Object, prop: string, val: mixed, msg?: string): void; 202 | 203 | static lengthOf(exp: mixed, len: number, msg?: string): void; 204 | 205 | static throws( 206 | func: () => any, 207 | err?: Class | Error | RegExp | string, 208 | errorMsgMatcher?: string | RegExp, 209 | msg?: string): void; 210 | static doesNotThrow( 211 | func: () => any, 212 | err?: Class | Error | RegExp | string, 213 | errorMsgMatcher?: string | RegExp, 214 | msg?: string): void; 215 | } 216 | 217 | declare var config: { 218 | includeStack: boolean, 219 | showDiff: boolean, 220 | truncateThreshold: number 221 | }; 222 | } 223 | -------------------------------------------------------------------------------- /flow-typed/npm/mocha_v3.1.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 58fb316c623a4f7918b0e2529256be8c 2 | // flow-typed version: 0ef6a9a08b/mocha_v3.1.x/flow_>=v0.28.x 3 | 4 | declare interface $npm$mocha$SetupOptions { 5 | slow?: number; 6 | timeout?: number; 7 | ui?: string; 8 | globals?: Array; 9 | reporter?: any; 10 | bail?: boolean; 11 | ignoreLeaks?: boolean; 12 | grep?: any; 13 | } 14 | 15 | declare type $npm$mocha$done = (error?: any) => any; 16 | 17 | // declare interface $npm$mocha$SuiteCallbackContext { 18 | // timeout(ms: number): void; 19 | // retries(n: number): void; 20 | // slow(ms: number): void; 21 | // } 22 | 23 | // declare interface $npm$mocha$TestCallbackContext { 24 | // skip(): void; 25 | // timeout(ms: number): void; 26 | // retries(n: number): void; 27 | // slow(ms: number): void; 28 | // [index: string]: any; 29 | // } 30 | 31 | declare interface $npm$mocha$Suite { 32 | parent: $npm$mocha$Suite; 33 | title: string; 34 | fullTitle(): string; 35 | } 36 | 37 | declare interface $npm$mocha$ContextDefinition { 38 | (description: string, callback: (/* this: $npm$mocha$SuiteCallbackContext */) => void): $npm$mocha$Suite; 39 | only(description: string, callback: (/* this: $npm$mocha$SuiteCallbackContext */) => void): $npm$mocha$Suite; 40 | skip(description: string, callback: (/* this: $npm$mocha$SuiteCallbackContext */) => void): void; 41 | timeout(ms: number): void; 42 | } 43 | 44 | declare interface $npm$mocha$TestDefinition { 45 | (expectation: string, callback?: (/* this: $npm$mocha$TestCallbackContext, */ done: $npm$mocha$done) => mixed): $npm$mocha$Test; 46 | only(expectation: string, callback?: (/* this: $npm$mocha$TestCallbackContext, */ done: $npm$mocha$done) => mixed): $npm$mocha$Test; 47 | skip(expectation: string, callback?: (/* this: $npm$mocha$TestCallbackContext, */ done: $npm$mocha$done) => mixed): void; 48 | timeout(ms: number): void; 49 | state: 'failed' | 'passed'; 50 | } 51 | 52 | declare interface $npm$mocha$Runner {} 53 | 54 | declare class $npm$mocha$BaseReporter { 55 | stats: { 56 | suites: number; 57 | tests: number; 58 | passes: number; 59 | pending: number; 60 | failures: number; 61 | }; 62 | 63 | constructor(runner: $npm$mocha$Runner): $npm$mocha$BaseReporter; 64 | } 65 | 66 | declare class $npm$mocha$DocReporter extends $npm$mocha$BaseReporter {} 67 | declare class $npm$mocha$DotReporter extends $npm$mocha$BaseReporter {} 68 | declare class $npm$mocha$HTMLReporter extends $npm$mocha$BaseReporter {} 69 | declare class $npm$mocha$HTMLCovReporter extends $npm$mocha$BaseReporter {} 70 | declare class $npm$mocha$JSONReporter extends $npm$mocha$BaseReporter {} 71 | declare class $npm$mocha$JSONCovReporter extends $npm$mocha$BaseReporter {} 72 | declare class $npm$mocha$JSONStreamReporter extends $npm$mocha$BaseReporter {} 73 | declare class $npm$mocha$LandingReporter extends $npm$mocha$BaseReporter {} 74 | declare class $npm$mocha$ListReporter extends $npm$mocha$BaseReporter {} 75 | declare class $npm$mocha$MarkdownReporter extends $npm$mocha$BaseReporter {} 76 | declare class $npm$mocha$MinReporter extends $npm$mocha$BaseReporter {} 77 | declare class $npm$mocha$NyanReporter extends $npm$mocha$BaseReporter {} 78 | declare class $npm$mocha$ProgressReporter extends $npm$mocha$BaseReporter { 79 | constructor(runner: $npm$mocha$Runner, options?: { 80 | open?: string; 81 | complete?: string; 82 | incomplete?: string; 83 | close?: string; 84 | }): $npm$mocha$ProgressReporter; 85 | } 86 | declare class $npm$mocha$SpecReporter extends $npm$mocha$BaseReporter {} 87 | declare class $npm$mocha$TAPReporter extends $npm$mocha$BaseReporter {} 88 | declare class $npm$mocha$XUnitReporter extends $npm$mocha$BaseReporter { 89 | constructor(runner: $npm$mocha$Runner, options?: any): $npm$mocha$XUnitReporter; 90 | } 91 | 92 | declare class $npm$mocha$Mocha { 93 | currentTest: $npm$mocha$TestDefinition; 94 | constructor(options?: { 95 | grep?: RegExp; 96 | ui?: string; 97 | reporter?: string; 98 | timeout?: number; 99 | reporterOptions?: any; 100 | slow?: number; 101 | bail?: boolean; 102 | }): $npm$mocha$Mocha; 103 | setup(options: $npm$mocha$SetupOptions): this; 104 | bail(value?: boolean): this; 105 | addFile(file: string): this; 106 | reporter(name: string): this; 107 | reporter(reporter: (runner: $npm$mocha$Runner, options: any) => any): this; 108 | ui(value: string): this; 109 | grep(value: string): this; 110 | grep(value: RegExp): this; 111 | invert(): this; 112 | ignoreLeaks(value: boolean): this; 113 | checkLeaks(): this; 114 | throwError(error: Error): void; 115 | growl(): this; 116 | globals(value: string): this; 117 | globals(values: Array): this; 118 | useColors(value: boolean): this; 119 | useInlineDiffs(value: boolean): this; 120 | timeout(value: number): this; 121 | slow(value: number): this; 122 | enableTimeouts(value: boolean): this; 123 | asyncOnly(value: boolean): this; 124 | noHighlighting(value: boolean): this; 125 | run(onComplete?: (failures: number) => void): $npm$mocha$Runner; 126 | 127 | static reporters: { 128 | Doc: $npm$mocha$DocReporter, 129 | Dot: $npm$mocha$DotReporter, 130 | HTML: $npm$mocha$HTMLReporter, 131 | HTMLCov: $npm$mocha$HTMLCovReporter, 132 | JSON: $npm$mocha$JSONReporter, 133 | JSONCov: $npm$mocha$JSONCovReporter, 134 | JSONStream: $npm$mocha$JSONStreamReporter, 135 | Landing: $npm$mocha$LandingReporter, 136 | List: $npm$mocha$ListReporter, 137 | Markdown: $npm$mocha$MarkdownReporter, 138 | Min: $npm$mocha$MinReporter, 139 | Nyan: $npm$mocha$NyanReporter, 140 | Progress: $npm$mocha$ProgressReporter, 141 | }; 142 | } 143 | 144 | // declare interface $npm$mocha$HookCallbackContext { 145 | // skip(): void; 146 | // timeout(ms: number): void; 147 | // [index: string]: any; 148 | // } 149 | 150 | declare interface $npm$mocha$Runnable { 151 | title: string; 152 | fn: Function; 153 | async: boolean; 154 | sync: boolean; 155 | timedOut: boolean; 156 | } 157 | 158 | declare interface $npm$mocha$Test extends $npm$mocha$Runnable { 159 | parent: $npm$mocha$Suite; 160 | pending: boolean; 161 | state: 'failed' | 'passed' | void; 162 | fullTitle(): string; 163 | } 164 | 165 | // declare interface $npm$mocha$BeforeAndAfterContext extends $npm$mocha$HookCallbackContext { 166 | // currentTest: $npm$mocha$Test; 167 | // } 168 | 169 | declare var mocha: $npm$mocha$Mocha; 170 | declare var describe: $npm$mocha$ContextDefinition; 171 | declare var xdescribe: $npm$mocha$ContextDefinition; 172 | declare var context: $npm$mocha$ContextDefinition; 173 | declare var suite: $npm$mocha$ContextDefinition; 174 | declare var it: $npm$mocha$TestDefinition; 175 | declare var xit: $npm$mocha$TestDefinition; 176 | declare var test: $npm$mocha$TestDefinition; 177 | declare var specify: $npm$mocha$TestDefinition; 178 | 179 | declare function run(): void; 180 | 181 | declare function setup(callback: (/* this: $npm$mocha$BeforeAndAfterContext, */ done: $npm$mocha$done) => mixed): void; 182 | declare function teardown(callback: (/* this: $npm$mocha$BeforeAndAfterContext, */ done: $npm$mocha$done) => mixed): void; 183 | declare function suiteSetup(callback: (/* this: $npm$mocha$HookCallbackContext, */ done: $npm$mocha$done) => mixed): void; 184 | declare function suiteTeardown(callback: (/* this: $npm$mocha$HookCallbackContext, */ done: $npm$mocha$done) => mixed): void; 185 | declare function before(callback: (/* this: $npm$mocha$HookCallbackContext, */ done: $npm$mocha$done) => mixed): void; 186 | declare function before(description: string, callback: (/* this: $npm$mocha$HookCallbackContext, */ done: $npm$mocha$done) => mixed): void; 187 | declare function after(callback: (/* this: $npm$mocha$HookCallbackContext, */ done: $npm$mocha$done) => mixed): void; 188 | declare function after(description: string, callback: (/* this: $npm$mocha$HookCallbackContext, */ done: $npm$mocha$done) => mixed): void; 189 | declare function beforeEach(callback: (/* this: $npm$mocha$BeforeAndAfterContext, */ done: $npm$mocha$done) => mixed): void; 190 | declare function beforeEach(description: string, callback: (/* this: $npm$mocha$BeforeAndAfterContext, */ done: $npm$mocha$done) => mixed): void; 191 | declare function afterEach(callback: (/* this: $npm$mocha$BeforeAndAfterContext, */ done: $npm$mocha$done) => mixed): void; 192 | declare function afterEach(description: string, callback: (/* this: $npm$mocha$BeforeAndAfterContext, */ done: $npm$mocha$done) => mixed): void; 193 | 194 | declare module "mocha" { 195 | declare export var mocha: typeof mocha; 196 | declare export var describe: typeof describe; 197 | declare export var xdescribe: typeof xdescribe; 198 | declare export var context: typeof context; 199 | declare export var suite: typeof suite; 200 | declare export var it: typeof it; 201 | declare export var xit: typeof xit; 202 | declare export var test: typeof test; 203 | declare export var specify: typeof specify; 204 | 205 | declare export var run: typeof run; 206 | 207 | declare export var setup: typeof setup; 208 | declare export var teardown: typeof teardown; 209 | declare export var suiteSetup: typeof suiteSetup; 210 | declare export var suiteTeardown: typeof suiteTeardown; 211 | declare export var before: typeof before; 212 | declare export var before: typeof before; 213 | declare export var after: typeof after; 214 | declare export var after: typeof after; 215 | declare export var beforeEach: typeof beforeEach; 216 | declare export var beforeEach: typeof beforeEach; 217 | declare export var afterEach: typeof afterEach; 218 | declare export var afterEach: typeof afterEach; 219 | 220 | declare export default $npm$mocha$Mocha; 221 | } 222 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "falcor-json-graph", 3 | "version": "3.2.1", 4 | "description": "A set of factory functions for creating JSON Graph values.", 5 | "main": "./lib/index.js", 6 | "types": "./src/index.d.ts", 7 | "scripts": { 8 | "flow": "flow", 9 | "flow-coverage": "flow-coverage-report", 10 | "lint": "eslint src/ test/", 11 | "prepare": "test -e lib && rm -r lib; flow-remove-types --out-dir lib/ src/ && (cd src/; find * -type f -name '*.js' -exec cp {} ../lib/{}.flow \\;)", 12 | "prettier": "prettier --write src/**.js test/**.js", 13 | "check-formatting": "prettier --list-different src/**.js test/**.js", 14 | "test": "mocha -r flow-remove-types/register", 15 | "release": "standard-version" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/Netflix/falcor-json-graph.git" 20 | }, 21 | "author": "Jafar Husain ", 22 | "license": "Apache 2.0", 23 | "bugs": { 24 | "url": "https://github.com/Netflix/falcor-json-graph/issues" 25 | }, 26 | "keywords": [ 27 | "falcorjs", 28 | "Observable", 29 | "JSON", 30 | "JSON Graph" 31 | ], 32 | "homepage": "https://github.com/Netflix/falcor-json-graph", 33 | "dependencies": {}, 34 | "devDependencies": { 35 | "@lrowe/eslint-plugin-flow-remove-types": "^0.0.1", 36 | "chai": "^4.2.0", 37 | "eslint": "^4.19.1", 38 | "flow-bin": "^0.62.0", 39 | "flow-coverage-report": "^0.4.1", 40 | "flow-remove-types": "^1.2.3", 41 | "mocha": "^4.1.0", 42 | "prettier": "^1.19.1", 43 | "standard-version": "^7.1.0" 44 | }, 45 | "flow-coverage-report": { 46 | "includeGlob": [ 47 | "src/**/*.js" 48 | ] 49 | }, 50 | "publishConfig": { 51 | "access": "public", 52 | "registry": "https://registry.npmjs.org/" 53 | } 54 | } -------------------------------------------------------------------------------- /src/factories.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | "use strict"; 3 | import type { 4 | JsonGraphAtomDefined, 5 | JsonGraphAtomUndefined, 6 | JsonGraphError, 7 | JsonGraphLeaf, 8 | JsonGraphMetadata, 9 | JsonGraphRef, 10 | JsonValue, 11 | Path, 12 | PathInvalidation, 13 | PathSet, 14 | PathValue 15 | } from "."; 16 | 17 | declare function atom( 18 | value: T, 19 | props?: JsonGraphMetadata 20 | ): JsonGraphAtomDefined; 21 | 22 | declare function atom( 23 | value?: void, 24 | props?: JsonGraphMetadata 25 | ): JsonGraphAtomUndefined; 26 | 27 | function atom(value, props) { 28 | const result = 29 | typeof value === "undefined" 30 | ? { $type: "atom" } 31 | : { $type: "atom", value: value }; 32 | return props ? Object.assign({}, props, result) : result; 33 | } 34 | 35 | function undefinedAtom(): JsonGraphAtomUndefined { 36 | return { $type: "atom" }; 37 | } 38 | 39 | module.exports = { 40 | ref: function ref(path: Path, props?: JsonGraphMetadata): JsonGraphRef { 41 | const result = { $type: "ref", value: path }; 42 | return props ? Object.assign({}, props, result) : result; 43 | }, 44 | atom: atom, 45 | undefinedAtom: undefinedAtom, 46 | error: function error( 47 | errorValue: string, 48 | props?: JsonGraphMetadata 49 | ): JsonGraphError { 50 | const result = { $type: "error", value: errorValue }; 51 | return props ? Object.assign({}, props, result) : result; 52 | }, 53 | pathValue: function pathValue( 54 | path: PathSet, 55 | value: JsonGraphLeaf 56 | ): PathValue { 57 | return { path: path, value: value }; 58 | }, 59 | pathInvalidation: function pathInvalidation(path: PathSet): PathInvalidation { 60 | return { path: path, invalidated: true }; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | export type Primitive = string | number | boolean | null; 2 | export type JsonValue = Primitive | JsonMap | (Primitive | JsonMap)[]; 3 | export type JsonMap = { [key: string]: JsonValue | void }; 4 | 5 | export type Key = Primitive; 6 | export type KeyRangeTo = { from?: number, to: number }; 7 | export type KeyRangeLength = { from?: number, length: number }; 8 | export type KeyRange = KeyRangeTo | KeyRangeLength; 9 | export type Path = Key[]; 10 | export type KeySet = Key | KeyRange | Array; 11 | export type PathSet = KeySet[]; 12 | 13 | export type JsonGraph = { [key: string]: JsonGraphNode | void }; 14 | export type JsonGraphNode = JsonGraph | JsonGraphLeaf; 15 | export type JsonGraphLeaf = 16 | | JsonGraphAtom 17 | | JsonGraphError 18 | | JsonGraphRef 19 | | Primitive; 20 | 21 | export type JsonGraphAtom = JsonGraphMetadata & { 22 | $type: "atom", 23 | value?: JsonValue, 24 | }; 25 | 26 | export type JsonGraphError = JsonGraphMetadata & { 27 | $type: "error", 28 | value: JsonValue, 29 | }; 30 | 31 | export type JsonGraphRef = JsonGraphMetadata & { 32 | $type: "ref", 33 | value: Path, 34 | }; 35 | 36 | export type JsonGraphMetadata = { 37 | $expires?: number, 38 | $size?: number, 39 | $timestamp?: number, 40 | }; 41 | 42 | export type JsonGraphEnvelope = { 43 | errors?: Error[], 44 | jsonGraph: JsonGraph, 45 | paths?: PathSet[], 46 | invalidated?: PathSet[], 47 | context?: JsonGraph, 48 | }; 49 | 50 | export type PathValue = { 51 | path: PathSet, 52 | value: JsonGraphLeaf, 53 | }; 54 | 55 | export type PathInvalidation = { 56 | path: PathSet, 57 | invalidated: true, 58 | }; 59 | 60 | export function ref(path: Path, props?: JsonGraphMetadata): JsonGraphRef; 61 | export function atom(value?: JsonValue, props?: JsonGraphMetadata): JsonGraphAtom; 62 | export function undefinedAtom(): JsonGraphAtom; 63 | export function error(errorValue: string, props?: JsonGraphMetadata): JsonGraphError; 64 | export function pathValue(path: PathSet, value: JsonGraphLeaf): PathValue; 65 | export function pathInvalidation(path: PathSet): PathInvalidation; 66 | export function mergeJsonGraph(left: JsonGraph, right: JsonGraph): JsonGraph; 67 | export function mergeJsonGraphEnvelope(left: JsonGraphEnvelope, right: JsonGraphEnvelope): JsonGraphEnvelope; 68 | export function mergeJsonGraphNode(left: JsonGraphNode, right: JsonGraphNode): JsonGraphNode; 69 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | "use strict"; 3 | export type Primitive = string | number | boolean | null; 4 | export type JsonValue = Primitive | JsonMap | JsonValue[]; 5 | export type JsonMap = { [key: string]: JsonValue | void }; 6 | 7 | export type Key = Primitive; 8 | export type KeyRangeTo = { from?: number, to: number, +length?: empty }; 9 | export type KeyRangeLength = { from?: number, length: number, +to?: empty }; 10 | export type KeyRange = KeyRangeTo | KeyRangeLength; 11 | export type Path = Key[]; 12 | export type KeySet = Key | KeyRange | Array; 13 | export type PathSet = KeySet[]; 14 | 15 | export type JsonGraph = { [key: string]: JsonGraphNode | void, +$type?: empty }; 16 | export type JsonGraphNode = JsonGraph | JsonGraphLeaf; 17 | export type JsonGraphLeaf = 18 | | JsonGraphAtom 19 | | JsonGraphError 20 | | JsonGraphRef 21 | | Primitive; 22 | 23 | export type JsonGraphAtomDefined = JsonGraphMetadata & { 24 | +$type: "atom", 25 | +value: T 26 | }; 27 | 28 | export type JsonGraphAtomUndefined = JsonGraphMetadata & { 29 | +$type: "atom", 30 | +value?: void 31 | }; 32 | 33 | export type JsonGraphAtom = JsonGraphMetadata & { 34 | +$type: "atom", 35 | +value?: ?JsonValue 36 | }; 37 | 38 | export type JsonGraphError = JsonGraphMetadata & { 39 | +$type: "error", 40 | +value: JsonValue 41 | }; 42 | export type JsonGraphRef = JsonGraphMetadata & { 43 | +$type: "ref", 44 | +value: Path 45 | }; 46 | 47 | export type JsonGraphMetadata = { 48 | +$expires?: number, 49 | +$size?: number, 50 | +$timestamp?: number 51 | }; 52 | 53 | export type JsonGraphEnvelope = { 54 | errors?: Error[], 55 | jsonGraph: JsonGraph, 56 | paths?: PathSet[], 57 | invalidated?: PathSet[], 58 | context?: JsonGraph 59 | }; 60 | 61 | export type PathValue = { 62 | path: PathSet, 63 | value: JsonGraphLeaf, 64 | invalidated?: empty, 65 | jsonGraph?: empty 66 | }; 67 | 68 | export type PathInvalidation = { 69 | path: PathSet, 70 | value?: empty, 71 | invalidated: true, 72 | jsonGraph?: empty 73 | }; 74 | 75 | const { 76 | ref, 77 | atom, 78 | undefinedAtom, 79 | error, 80 | pathValue, 81 | pathInvalidation 82 | } = require("./factories"); 83 | 84 | const { 85 | mergeJsonGraph, 86 | mergeJsonGraphEnvelope, 87 | mergeJsonGraphNode 88 | } = require("./merge"); 89 | 90 | module.exports = { 91 | ref, 92 | atom, 93 | undefinedAtom, 94 | undefined: undefinedAtom, 95 | error, 96 | pathValue, 97 | pathInvalidation, 98 | mergeJsonGraph, 99 | mergeJsonGraphEnvelope, 100 | mergeJsonGraphNode 101 | }; 102 | -------------------------------------------------------------------------------- /src/merge.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | "use strict"; 3 | import type { JsonGraph, JsonGraphNode, JsonGraphEnvelope } from "."; 4 | 5 | function mergeJsonGraphNode( 6 | left: JsonGraphNode, 7 | right: JsonGraphNode 8 | ): JsonGraphNode { 9 | if (right === null || typeof right !== "object" || right.$type) { 10 | return right; 11 | } 12 | if (left === null || typeof left !== "object" || left.$type) { 13 | return left; 14 | } 15 | return mergeJsonGraph(left, right); 16 | } 17 | 18 | function mergeJsonGraph(left: JsonGraph, right: JsonGraph): JsonGraph { 19 | if (left === right) { 20 | return right; 21 | } 22 | const acc: JsonGraph = Object.assign({}, left); 23 | for (const key in right) { 24 | if (Object.prototype.hasOwnProperty.call(right, key)) { 25 | const rightValue = right[key]; 26 | if (typeof rightValue !== "undefined") { 27 | const leftValue = acc[key]; 28 | if (leftValue !== rightValue) { 29 | acc[key] = 30 | typeof leftValue !== "undefined" 31 | ? mergeJsonGraphNode(leftValue, rightValue) 32 | : rightValue; 33 | } 34 | } 35 | } 36 | } 37 | return acc; 38 | } 39 | 40 | function mergeJsonGraphEnvelope( 41 | left: JsonGraphEnvelope, 42 | right: JsonGraphEnvelope 43 | ): JsonGraphEnvelope { 44 | if ( 45 | left === right || 46 | (left.paths && 47 | left.paths.length === 0 && 48 | !left.invalidated && 49 | !left.context) 50 | ) { 51 | return right; 52 | } 53 | const result: JsonGraphEnvelope = { 54 | jsonGraph: right.jsonGraph 55 | ? mergeJsonGraph(left.jsonGraph, right.jsonGraph) 56 | : left.jsonGraph 57 | }; 58 | 59 | tentativeMerge(result, left, right, "paths"); 60 | tentativeMerge(result, left, right, "errors"); 61 | 62 | if (right.invalidated) { 63 | result.invalidated = left.invalidated 64 | ? left.invalidated.concat(right.invalidated) 65 | : right.invalidated; 66 | } else if (left.invalidated) { 67 | result.invalidated = left.invalidated; 68 | } 69 | if (right.context) { 70 | result.context = left.context 71 | ? mergeJsonGraph(left.context, right.context) 72 | : right.context; 73 | } else if (left.context) { 74 | result.context = left.context; 75 | } 76 | return result; 77 | } 78 | 79 | // Only add properties to JSONGraph if they have any value 80 | // it is needed for feature parity with older versions 81 | function tentativeMerge( 82 | result: JsonGraphEnvelope, 83 | left: JsonGraphEnvelope, 84 | right: JsonGraphEnvelope, 85 | property: string 86 | ): void { 87 | const leftValues = left[property] || []; 88 | const rightValues = right[property] || []; 89 | 90 | if (Array.isArray(left[property]) || Array.isArray(right[property])) { 91 | if (leftValues.length && !rightValues.length) { 92 | result[property] = leftValues; 93 | } else if (!leftValues.length && rightValues.length) { 94 | result[property] = rightValues; 95 | } else { 96 | result[property] = leftValues.concat(rightValues); 97 | } 98 | } 99 | } 100 | 101 | module.exports = { mergeJsonGraph, mergeJsonGraphEnvelope, mergeJsonGraphNode }; 102 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/factories-test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | "use strict"; 3 | const { expect } = require("chai"); 4 | const { 5 | atom, 6 | undefinedAtom, 7 | ref, 8 | error, 9 | pathValue, 10 | pathInvalidation 11 | } = require("../src"); 12 | 13 | describe("atom", function() { 14 | it("undefined atom", () => expect(atom()).to.deep.equal({ $type: "atom" })); 15 | it("atom of string", () => 16 | expect(atom("foo")).to.deep.equal({ $type: "atom", value: "foo" })); 17 | it("atom with props", () => 18 | expect(atom("foo", { $expires: 0 })).to.deep.equal({ 19 | $type: "atom", 20 | value: "foo", 21 | $expires: 0 22 | })); 23 | }); 24 | 25 | describe("undefinedAtom", function() { 26 | it("undefined atom", () => 27 | expect(undefinedAtom()).to.deep.equal({ $type: "atom" })); 28 | }); 29 | 30 | describe("ref", function() { 31 | it("ref", () => 32 | expect(ref(["foo"])).to.deep.equal({ $type: "ref", value: ["foo"] })); 33 | it("ref with props", () => 34 | expect(ref(["foo"], { $expires: 0 })).to.deep.equal({ 35 | $type: "ref", 36 | value: ["foo"], 37 | $expires: 0 38 | })); 39 | }); 40 | 41 | describe("error", function() { 42 | it("error", () => 43 | expect(error("foo")).to.deep.equal({ $type: "error", value: "foo" })); 44 | it("error with props", () => 45 | expect(error("foo", { $expires: 0 })).to.deep.equal({ 46 | $type: "error", 47 | value: "foo", 48 | $expires: 0 49 | })); 50 | }); 51 | 52 | describe("pathValue", function() { 53 | it("pathValue of string", () => 54 | expect(pathValue(["foo"], "bar")).to.deep.equal({ 55 | path: ["foo"], 56 | value: "bar" 57 | })); 58 | it("pathValue of atom of string", () => 59 | expect(pathValue(["foo"], atom("bar"))).to.deep.equal({ 60 | path: ["foo"], 61 | value: { $type: "atom", value: "bar" } 62 | })); 63 | }); 64 | 65 | describe("pathInvalidation", function() { 66 | it("pathInvalidation", () => 67 | expect(pathInvalidation(["foo"])).to.deep.equal({ 68 | path: ["foo"], 69 | invalidated: true 70 | })); 71 | }); 72 | -------------------------------------------------------------------------------- /test/merge-test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | "use strict"; 3 | const { mergeJsonGraphEnvelope } = require("../src"); 4 | const { expect } = require("chai"); 5 | 6 | describe("mergeJsonGraphEnvelope", function() { 7 | it("merges jsonGraph", function() { 8 | const left = { 9 | jsonGraph: { 10 | foo: 1 11 | } 12 | }; 13 | const right = { 14 | jsonGraph: { 15 | bar: 2 16 | } 17 | }; 18 | const expected = { 19 | jsonGraph: { 20 | foo: 1, 21 | bar: 2 22 | } 23 | }; 24 | const result = mergeJsonGraphEnvelope(left, right); 25 | expect(result).to.deep.equal(expected); 26 | }); 27 | 28 | it("merges jsonGraph paths", function() { 29 | const left = { 30 | paths: [["foo"]] 31 | }; 32 | const right = { 33 | paths: [["bar"]], 34 | jsonGraph: { 35 | bar: 2 36 | } 37 | }; 38 | const expected = { 39 | paths: [["foo"], ["bar"]], 40 | jsonGraph: { 41 | bar: 2 42 | } 43 | }; 44 | const result = mergeJsonGraphEnvelope((left: any), right); 45 | expect(result).to.deep.equal(expected); 46 | }); 47 | 48 | it("merges with missing left jsonGraph paths", function() { 49 | const left = {}; 50 | const right = { 51 | paths: [["bar"]], 52 | jsonGraph: { 53 | bar: 2 54 | } 55 | }; 56 | const expected = { 57 | paths: [["bar"]], 58 | jsonGraph: { 59 | bar: 2 60 | } 61 | }; 62 | const result = mergeJsonGraphEnvelope((left: any), right); 63 | expect(result).to.deep.equal(expected); 64 | }); 65 | 66 | it("merges with missing right jsonGraph paths", function() { 67 | const left = { 68 | paths: [["bar"]] 69 | }; 70 | const right = { 71 | jsonGraph: { 72 | bar: 2 73 | } 74 | }; 75 | const expected = { 76 | paths: [["bar"]], 77 | jsonGraph: { 78 | bar: 2 79 | } 80 | }; 81 | const result = mergeJsonGraphEnvelope((left: any), right); 82 | expect(result).to.deep.equal(expected); 83 | }); 84 | 85 | it("merges jsonGraph errors", function() { 86 | const err1 = new Error("My Error 1"); 87 | const err2 = new Error("My Error 2"); 88 | const left = { 89 | errors: [err1] 90 | }; 91 | const right = { 92 | errors: [err2], 93 | jsonGraph: { 94 | bar: 2 95 | } 96 | }; 97 | const expected = { 98 | errors: [err1, err2], 99 | jsonGraph: { 100 | bar: 2 101 | } 102 | }; 103 | const result = mergeJsonGraphEnvelope((left: any), right); 104 | expect(result).to.deep.equal(expected); 105 | }); 106 | 107 | it("ignores missing left jsonGraph", function() { 108 | const left = {}; 109 | const right = { 110 | jsonGraph: { 111 | bar: 2 112 | } 113 | }; 114 | const expected = { 115 | jsonGraph: { 116 | bar: 2 117 | } 118 | }; 119 | const result = mergeJsonGraphEnvelope((left: any), right); 120 | expect(result).to.deep.equal(expected); 121 | }); 122 | 123 | it("ignores missing right jsonGraph", function() { 124 | const left = { 125 | jsonGraph: { 126 | foo: 1 127 | } 128 | }; 129 | const right = {}; 130 | const expected = { 131 | jsonGraph: { 132 | foo: 1 133 | } 134 | }; 135 | const result = mergeJsonGraphEnvelope(left, (right: any)); 136 | expect(result).to.deep.equal(expected); 137 | }); 138 | 139 | it("merges invalidated", function() { 140 | const left = { 141 | jsonGraph: {}, 142 | invalidated: [["foo"]] 143 | }; 144 | const right = { 145 | jsonGraph: {}, 146 | invalidated: [["bar"]] 147 | }; 148 | const expected = { 149 | jsonGraph: {}, 150 | invalidated: [["foo"], ["bar"]] 151 | }; 152 | const result = mergeJsonGraphEnvelope(left, right); 153 | expect(result).to.deep.equal(expected); 154 | }); 155 | 156 | it("merges context when paths are empty", function() { 157 | const left = { 158 | jsonGraph: {}, 159 | paths: [], 160 | context: { 161 | foo: 1 162 | } 163 | }; 164 | const right = { 165 | jsonGraph: {}, 166 | paths: [], 167 | context: { 168 | bar: 2 169 | } 170 | }; 171 | const expected = { 172 | jsonGraph: {}, 173 | paths: [], 174 | context: { 175 | foo: 1, 176 | bar: 2 177 | } 178 | }; 179 | const result = mergeJsonGraphEnvelope(left, right); 180 | expect(result).to.deep.equal(expected); 181 | }); 182 | 183 | it("merges context using jsonGraph merge", function() { 184 | const left = { 185 | jsonGraph: {}, 186 | context: { 187 | branch: { 188 | foo: 1, 189 | atom: { 190 | $type: "atom", 191 | value: { 192 | foo: 1 193 | } 194 | } 195 | } 196 | } 197 | }; 198 | const right = { 199 | jsonGraph: {}, 200 | context: { 201 | branch: { 202 | bar: 2, 203 | atom: { 204 | $type: "atom", 205 | value: { 206 | bar: 2 207 | } 208 | } 209 | } 210 | } 211 | }; 212 | const expected = { 213 | jsonGraph: {}, 214 | context: { 215 | branch: { 216 | foo: 1, 217 | bar: 2, 218 | atom: { 219 | $type: "atom", 220 | value: { 221 | bar: 2 222 | } 223 | } 224 | } 225 | } 226 | }; 227 | const result = mergeJsonGraphEnvelope(left, right); 228 | expect(result).to.deep.equal(expected); 229 | }); 230 | }); 231 | --------------------------------------------------------------------------------