├── .github └── workflows │ └── joinery.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.clj ├── cljfmt.edn ├── deps.edn ├── doc └── intro.md ├── package-lock.json ├── package.json ├── pom.xml ├── resources └── .keep ├── shadow-cljs.edn ├── src └── cjsauer │ └── joinery.cljc └── test └── cjsauer └── joinery_test.cljc /.github/workflows/joinery.yml: -------------------------------------------------------------------------------- 1 | name: Joinery CI/CD Workflow 2 | on: 3 | push: 4 | branches: 5 | - main 6 | defaults: 7 | run: 8 | shell: bash 9 | jobs: 10 | joinery: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: Prepare java 18 | uses: actions/setup-java@v2 19 | with: 20 | distribution: 'adopt' 21 | java-version: '8' 22 | - name: Install clojure tools 23 | uses: DeLaGuardo/setup-clojure@3.5 24 | with: 25 | cli: 1.10.3.1020 26 | - uses: actions/setup-node@v2 27 | with: 28 | node-version: '14' 29 | - run: npm install 30 | - name: Cache clojure deps 31 | uses: actions/cache@v2 32 | env: 33 | cache-name: cache-joinery 34 | with: 35 | path: | 36 | ~/.m2 37 | ~/.gitlibs 38 | key: ${{ env.cache-name }}-${{ hashFiles('deps.edn') }} 39 | - name: Run cljs tests 40 | run: npx shadow-cljs release :test 41 | - name: Run clj tests and build 42 | run: clojure -T:build ci 43 | - name: Clojars deploy 44 | run: clojure -T:build deploy 45 | env: 46 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 47 | CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .calva/output-window/ 2 | .classpath 3 | .clj-kondo/.cache 4 | .cpcache 5 | .eastwood 6 | .factorypath 7 | .hg/ 8 | .hgignore 9 | .java-version 10 | .lein-* 11 | .lsp/.cache 12 | .lsp/sqlite.db 13 | .nrepl-history 14 | .nrepl-port 15 | .project 16 | .rebel_readline_history 17 | .settings 18 | .socket-repl-port 19 | .sw* 20 | .vscode 21 | *.class 22 | *.jar 23 | *.swp 24 | *~ 25 | /checkouts 26 | /classes 27 | /target 28 | node_modules/ 29 | \.shadow-cljs/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # joinery 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/net.clojars.cjsauer/joinery.svg)](https://clojars.org/net.clojars.cjsauer/joinery) 4 | 5 | A library to enable traversal of in-memory graph-like data structures using Clojure(Script) 6 | map protocols. 7 | 8 | ## Quickstart 9 | 10 | ```clojure 11 | (require '[cjsauer.joinery :refer [joined-map]]) 12 | 13 | ;; Assume you have a local normalized data source. 14 | ;; joinery includes support for "table-like" sources out of the box: 15 | (def db 16 | {:person/id {1 {:person/name "Calvin" 17 | :person/friends [[:person/id 2]] 18 | :person/pet [:pet/id 1]} 19 | 2 {:person/name "Derek" 20 | :person/friends [[:person/id 1]]}} 21 | :pet/id {1 {:pet/name "Malcolm" 22 | :pet/species :dog 23 | :pet/owner [:person/id 1]}}}) 24 | 25 | ;; Create a joined-map interface to the db 26 | (def jm (joined-map db)) 27 | 28 | ;; Use it like a normal map, and observe "joins" resolved on-demand 29 | (get-in jm [:person/id 1 :person/pet]) 30 | ;;=> #:pet{:name "Malcolm", :owner [:person/id 1], :species :dog} 31 | 32 | ;; Cardinality-many joins are resolved recursively 33 | (get-in jm [:person/id 1 :person/friends]) 34 | ;; => [#:person{:name "Derek", :friends [[:person/id 1]]}] 35 | 36 | ;; Cycles are possible 37 | (get-in jm [:person/id 1 :person/pet :pet/owner]) 38 | ;; => #:person{:name "Calvin", :friends [[:person/id 2]], :pet [:pet/id 1]} 39 | 40 | ;; Most of the expected map functions are implemented 41 | ;; assoc, dissoc, find, seq, reduce, etc... 42 | (def jm' (assoc-in jm [:person/id 1 :person/best-friend] [:pet/id 1])) 43 | (get-in jm' [:person/id 1]) 44 | ;; => #:person{:name "Calvin", :best-friend [:pet/id 1], ...} 45 | 46 | ;; Let's follow the newly added edge 47 | (get-in jm' [:person/id 1 :person/best-friend]) 48 | ;; => #:pet{:name "Malcolm", :owner [:person/id 1], :species :dog} 49 | ``` 50 | 51 | ## Advanced usage 52 | 53 | The `joined-map` constructor accepts a couple more helpful options that provide 54 | additional means of customization. Firstly, by default, `joined-map` will use 55 | the provided `db` as the starting point of traversal. One can optionally provide 56 | a "starting entity" that will become the "current" value of the joined map: 57 | 58 | ```clojure 59 | (def db 60 | {:person/id {1 {:person/name "Calvin" 61 | :person/friends [[:person/id 2]] 62 | :person/pet [:pet/id 1]} 63 | 2 {:person/name "Derek" 64 | :person/friends [[:person/id 1]]}} 65 | :pet/id {1 {:pet/name "Malcolm" 66 | :pet/species :dog 67 | :pet/owner [:person/id 1]}}}) 68 | 69 | ;; Use the db as the backing source, but start at a different entity 70 | (def jm (joined-map db {:ui.selected/user [:person/id 1]})) 71 | jm 72 | ;; => #:ui.selected{:user [:person/id 1]} 73 | 74 | ;; Joins are resolved just as before 75 | (:ui.selected/user jm) 76 | ;; => #:person{:friends [[:person/id 2]], :name "Calvin", :pet [:pet/id 1]} 77 | ``` 78 | 79 | In addition, one can customize the functionality of joins by implementing the 80 | `Joinery` protocol. Here is the default table-join implementation that ships 81 | with `joinery`: 82 | 83 | ```clojure 84 | (deftype TableIdentJoinery [] 85 | Joinery 86 | (is-join? [_ v] (and (vector? v) 87 | (= 2 (count v)) 88 | (keyword? (first v)))) 89 | (join [_ table v] (get-in table v))) 90 | 91 | ;; Create a joined-map using a specific Joinery implementation: 92 | (joined-map db entity (TableIdentJoinery.)) 93 | ``` 94 | 95 | We can see above that the protocol is quite simple. We need to provide two things: 96 | how to identify what values should be treated as "links", and, given we've reached 97 | one of these links, how do we "resolve" it. With these two functions, we can obtain 98 | a map-like interface to a myriad of different normalized sources. 99 | 100 | ## Prior Art 101 | 102 | `joinery` came about while experimenting with the latest Clojure trend of 103 | in-memory databases, namely: 104 | 105 | - [*Pyramid's](https://github.com/lilactown/pyramid) normalized structure 106 | - [*Fulcro's](https://github.com/fulcrologic/fulcro) app state 107 | - [Asami's](https://github.com/threatgrid/asami) `entity` function 108 | - [DataScript's](https://github.com/tonsky/datascript) `entity` function 109 | - [Datomic's](https://docs.datomic.com/on-prem/clojure/index.html#datomic.api/entity) `entity` function 110 | 111 | While not a database per se, [Pathom3's Smart Map](https://pathom3.wsscode.com/docs/smart-maps) 112 | is another great source of inspiration. Pathom's scope is more broad in that smart 113 | map access can trigger arbitrary code to run (even network access), while `joinery` 114 | is mainly focused on local data structures only. 115 | 116 | _* joinery will work out of the box with these libraries_ 117 | 118 | ## Development 119 | 120 | Run the project's tests: 121 | 122 | $ clojure -T:build test 123 | 124 | Run the project's CI pipeline and build a JAR: 125 | 126 | $ clojure -T:build ci 127 | 128 | This will produce an updated `pom.xml` file with synchronized dependencies 129 | inside the `META-INF` directory inside `target/classes` and the JAR in `target`. 130 | You can update the version (and SCM tag) information in generated `pom.xml` by 131 | updating `build.clj`. 132 | 133 | Install it locally (requires the `ci` task be run first): 134 | 135 | $ clojure -T:build install 136 | 137 | Deploy it to Clojars -- needs `CLOJARS_USERNAME` and `CLOJARS_PASSWORD` environment 138 | variables (requires the `ci` task be run first): 139 | 140 | $ clojure -T:build deploy 141 | 142 | ## License 143 | 144 | Copyright © 2021 Calvin Sauer 145 | 146 | _EPLv1.0 is just the default for projects generated by `clj-new`: you are not_ 147 | _required to open source this project, nor are you required to use EPLv1.0!_ 148 | _Feel free to remove or change the `LICENSE` file and remove or update this_ 149 | _section of the `README.md` file!_ 150 | 151 | Distributed under the Eclipse Public License version 1.0. 152 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:refer-clojure :exclude [test]) 3 | (:require [clojure.tools.build.api :as b] ; for b/git-count-revs 4 | [org.corfield.build :as bb])) 5 | 6 | (def lib 'net.clojars.cjsauer/joinery) 7 | (def version (format "0.1.%s" (b/git-count-revs nil))) 8 | 9 | (defn test "Run the tests." [opts] 10 | (bb/run-tests opts)) 11 | 12 | (defn ci "Run the CI pipeline of tests (and build the JAR)." [opts] 13 | (-> opts 14 | (assoc :lib lib :version version) 15 | (bb/run-tests) 16 | (bb/clean) 17 | (bb/jar))) 18 | 19 | (defn install "Install the JAR locally." [opts] 20 | (-> opts 21 | (assoc :lib lib :version version) 22 | (bb/install))) 23 | 24 | (defn deploy "Deploy the JAR to Clojars." [opts] 25 | (-> opts 26 | (assoc :lib lib :version version) 27 | (bb/deploy))) 28 | -------------------------------------------------------------------------------- /cljfmt.edn: -------------------------------------------------------------------------------- 1 | {:remove-surrounding-whitespace? true 2 | :remove-trailing-whitespace? true 3 | :remove-consecutive-blank-lines? false 4 | :insert-missing-whitespace? false 5 | :align-associative? false 6 | :indents ^:replace {#"^\w" [[:inner 0]]} 7 | :test-code 8 | (concat [2] 9 | (map #(inc (* % 2)) 10 | (filter #(aget sieved %) 11 | (range 1 hn))))} -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {org.clojure/clojure {:mvn/version "1.10.3"}} 3 | :aliases {:cljs {:extra-deps {thheller/shadow-cljs {:mvn/version "2.16.0"}}} 4 | :build {:deps {io.github.seancorfield/build-clj {:git/tag "v0.4.0" 5 | :git/sha "54e39ae"}} 6 | :ns-default build} 7 | :test {:extra-paths ["test"] 8 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.0"} 9 | io.github.cognitect-labs/test-runner {:git/tag "v0.5.0" 10 | :git/sha "48c3c67"}}}}} 11 | -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to joinery 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "asn1.js": { 6 | "version": "5.4.1", 7 | "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", 8 | "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", 9 | "dev": true, 10 | "requires": { 11 | "bn.js": "^4.0.0", 12 | "inherits": "^2.0.1", 13 | "minimalistic-assert": "^1.0.0", 14 | "safer-buffer": "^2.1.0" 15 | }, 16 | "dependencies": { 17 | "bn.js": { 18 | "version": "4.12.0", 19 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 20 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", 21 | "dev": true 22 | } 23 | } 24 | }, 25 | "assert": { 26 | "version": "1.5.0", 27 | "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", 28 | "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", 29 | "dev": true, 30 | "requires": { 31 | "object-assign": "^4.1.1", 32 | "util": "0.10.3" 33 | }, 34 | "dependencies": { 35 | "util": { 36 | "version": "0.10.3", 37 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", 38 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", 39 | "dev": true, 40 | "requires": { 41 | "inherits": "2.0.1" 42 | } 43 | } 44 | } 45 | }, 46 | "base64-js": { 47 | "version": "1.5.1", 48 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 49 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 50 | "dev": true 51 | }, 52 | "bn.js": { 53 | "version": "5.2.0", 54 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", 55 | "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", 56 | "dev": true 57 | }, 58 | "brorand": { 59 | "version": "1.1.0", 60 | "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", 61 | "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", 62 | "dev": true 63 | }, 64 | "browserify-aes": { 65 | "version": "1.2.0", 66 | "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", 67 | "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", 68 | "dev": true, 69 | "requires": { 70 | "buffer-xor": "^1.0.3", 71 | "cipher-base": "^1.0.0", 72 | "create-hash": "^1.1.0", 73 | "evp_bytestokey": "^1.0.3", 74 | "inherits": "^2.0.1", 75 | "safe-buffer": "^5.0.1" 76 | } 77 | }, 78 | "browserify-cipher": { 79 | "version": "1.0.1", 80 | "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", 81 | "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", 82 | "dev": true, 83 | "requires": { 84 | "browserify-aes": "^1.0.4", 85 | "browserify-des": "^1.0.0", 86 | "evp_bytestokey": "^1.0.0" 87 | } 88 | }, 89 | "browserify-des": { 90 | "version": "1.0.2", 91 | "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", 92 | "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", 93 | "dev": true, 94 | "requires": { 95 | "cipher-base": "^1.0.1", 96 | "des.js": "^1.0.0", 97 | "inherits": "^2.0.1", 98 | "safe-buffer": "^5.1.2" 99 | } 100 | }, 101 | "browserify-rsa": { 102 | "version": "4.1.0", 103 | "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", 104 | "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", 105 | "dev": true, 106 | "requires": { 107 | "bn.js": "^5.0.0", 108 | "randombytes": "^2.0.1" 109 | } 110 | }, 111 | "browserify-sign": { 112 | "version": "4.2.1", 113 | "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", 114 | "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", 115 | "dev": true, 116 | "requires": { 117 | "bn.js": "^5.1.1", 118 | "browserify-rsa": "^4.0.1", 119 | "create-hash": "^1.2.0", 120 | "create-hmac": "^1.1.7", 121 | "elliptic": "^6.5.3", 122 | "inherits": "^2.0.4", 123 | "parse-asn1": "^5.1.5", 124 | "readable-stream": "^3.6.0", 125 | "safe-buffer": "^5.2.0" 126 | }, 127 | "dependencies": { 128 | "inherits": { 129 | "version": "2.0.4", 130 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 131 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 132 | "dev": true 133 | }, 134 | "readable-stream": { 135 | "version": "3.6.0", 136 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 137 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 138 | "dev": true, 139 | "requires": { 140 | "inherits": "^2.0.3", 141 | "string_decoder": "^1.1.1", 142 | "util-deprecate": "^1.0.1" 143 | } 144 | } 145 | } 146 | }, 147 | "browserify-zlib": { 148 | "version": "0.2.0", 149 | "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", 150 | "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", 151 | "dev": true, 152 | "requires": { 153 | "pako": "~1.0.5" 154 | } 155 | }, 156 | "buffer": { 157 | "version": "4.9.2", 158 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", 159 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 160 | "dev": true, 161 | "requires": { 162 | "base64-js": "^1.0.2", 163 | "ieee754": "^1.1.4", 164 | "isarray": "^1.0.0" 165 | } 166 | }, 167 | "buffer-xor": { 168 | "version": "1.0.3", 169 | "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", 170 | "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", 171 | "dev": true 172 | }, 173 | "builtin-status-codes": { 174 | "version": "3.0.0", 175 | "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", 176 | "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", 177 | "dev": true 178 | }, 179 | "cipher-base": { 180 | "version": "1.0.4", 181 | "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", 182 | "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", 183 | "dev": true, 184 | "requires": { 185 | "inherits": "^2.0.1", 186 | "safe-buffer": "^5.0.1" 187 | } 188 | }, 189 | "console-browserify": { 190 | "version": "1.2.0", 191 | "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", 192 | "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", 193 | "dev": true 194 | }, 195 | "constants-browserify": { 196 | "version": "1.0.0", 197 | "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", 198 | "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", 199 | "dev": true 200 | }, 201 | "core-util-is": { 202 | "version": "1.0.3", 203 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 204 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 205 | "dev": true 206 | }, 207 | "create-ecdh": { 208 | "version": "4.0.4", 209 | "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", 210 | "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", 211 | "dev": true, 212 | "requires": { 213 | "bn.js": "^4.1.0", 214 | "elliptic": "^6.5.3" 215 | }, 216 | "dependencies": { 217 | "bn.js": { 218 | "version": "4.12.0", 219 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 220 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", 221 | "dev": true 222 | } 223 | } 224 | }, 225 | "create-hash": { 226 | "version": "1.2.0", 227 | "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", 228 | "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", 229 | "dev": true, 230 | "requires": { 231 | "cipher-base": "^1.0.1", 232 | "inherits": "^2.0.1", 233 | "md5.js": "^1.3.4", 234 | "ripemd160": "^2.0.1", 235 | "sha.js": "^2.4.0" 236 | } 237 | }, 238 | "create-hmac": { 239 | "version": "1.1.7", 240 | "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", 241 | "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", 242 | "dev": true, 243 | "requires": { 244 | "cipher-base": "^1.0.3", 245 | "create-hash": "^1.1.0", 246 | "inherits": "^2.0.1", 247 | "ripemd160": "^2.0.0", 248 | "safe-buffer": "^5.0.1", 249 | "sha.js": "^2.4.8" 250 | } 251 | }, 252 | "crypto-browserify": { 253 | "version": "3.12.0", 254 | "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", 255 | "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", 256 | "dev": true, 257 | "requires": { 258 | "browserify-cipher": "^1.0.0", 259 | "browserify-sign": "^4.0.0", 260 | "create-ecdh": "^4.0.0", 261 | "create-hash": "^1.1.0", 262 | "create-hmac": "^1.1.0", 263 | "diffie-hellman": "^5.0.0", 264 | "inherits": "^2.0.1", 265 | "pbkdf2": "^3.0.3", 266 | "public-encrypt": "^4.0.0", 267 | "randombytes": "^2.0.0", 268 | "randomfill": "^1.0.3" 269 | } 270 | }, 271 | "des.js": { 272 | "version": "1.0.1", 273 | "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", 274 | "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", 275 | "dev": true, 276 | "requires": { 277 | "inherits": "^2.0.1", 278 | "minimalistic-assert": "^1.0.0" 279 | } 280 | }, 281 | "diffie-hellman": { 282 | "version": "5.0.3", 283 | "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", 284 | "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", 285 | "dev": true, 286 | "requires": { 287 | "bn.js": "^4.1.0", 288 | "miller-rabin": "^4.0.0", 289 | "randombytes": "^2.0.0" 290 | }, 291 | "dependencies": { 292 | "bn.js": { 293 | "version": "4.12.0", 294 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 295 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", 296 | "dev": true 297 | } 298 | } 299 | }, 300 | "domain-browser": { 301 | "version": "1.2.0", 302 | "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", 303 | "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", 304 | "dev": true 305 | }, 306 | "elliptic": { 307 | "version": "6.5.4", 308 | "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", 309 | "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", 310 | "dev": true, 311 | "requires": { 312 | "bn.js": "^4.11.9", 313 | "brorand": "^1.1.0", 314 | "hash.js": "^1.0.0", 315 | "hmac-drbg": "^1.0.1", 316 | "inherits": "^2.0.4", 317 | "minimalistic-assert": "^1.0.1", 318 | "minimalistic-crypto-utils": "^1.0.1" 319 | }, 320 | "dependencies": { 321 | "bn.js": { 322 | "version": "4.12.0", 323 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 324 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", 325 | "dev": true 326 | }, 327 | "inherits": { 328 | "version": "2.0.4", 329 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 330 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 331 | "dev": true 332 | } 333 | } 334 | }, 335 | "events": { 336 | "version": "3.3.0", 337 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", 338 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", 339 | "dev": true 340 | }, 341 | "evp_bytestokey": { 342 | "version": "1.0.3", 343 | "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", 344 | "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", 345 | "dev": true, 346 | "requires": { 347 | "md5.js": "^1.3.4", 348 | "safe-buffer": "^5.1.1" 349 | } 350 | }, 351 | "hash-base": { 352 | "version": "3.1.0", 353 | "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", 354 | "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", 355 | "dev": true, 356 | "requires": { 357 | "inherits": "^2.0.4", 358 | "readable-stream": "^3.6.0", 359 | "safe-buffer": "^5.2.0" 360 | }, 361 | "dependencies": { 362 | "inherits": { 363 | "version": "2.0.4", 364 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 365 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 366 | "dev": true 367 | }, 368 | "readable-stream": { 369 | "version": "3.6.0", 370 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 371 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 372 | "dev": true, 373 | "requires": { 374 | "inherits": "^2.0.3", 375 | "string_decoder": "^1.1.1", 376 | "util-deprecate": "^1.0.1" 377 | } 378 | } 379 | } 380 | }, 381 | "hash.js": { 382 | "version": "1.1.7", 383 | "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", 384 | "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", 385 | "dev": true, 386 | "requires": { 387 | "inherits": "^2.0.3", 388 | "minimalistic-assert": "^1.0.1" 389 | }, 390 | "dependencies": { 391 | "inherits": { 392 | "version": "2.0.4", 393 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 394 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 395 | "dev": true 396 | } 397 | } 398 | }, 399 | "hmac-drbg": { 400 | "version": "1.0.1", 401 | "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", 402 | "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", 403 | "dev": true, 404 | "requires": { 405 | "hash.js": "^1.0.3", 406 | "minimalistic-assert": "^1.0.0", 407 | "minimalistic-crypto-utils": "^1.0.1" 408 | } 409 | }, 410 | "https-browserify": { 411 | "version": "1.0.0", 412 | "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", 413 | "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", 414 | "dev": true 415 | }, 416 | "ieee754": { 417 | "version": "1.2.1", 418 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 419 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 420 | "dev": true 421 | }, 422 | "inherits": { 423 | "version": "2.0.1", 424 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", 425 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", 426 | "dev": true 427 | }, 428 | "isarray": { 429 | "version": "1.0.0", 430 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 431 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 432 | "dev": true 433 | }, 434 | "isexe": { 435 | "version": "2.0.0", 436 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 437 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 438 | "dev": true 439 | }, 440 | "md5.js": { 441 | "version": "1.3.5", 442 | "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", 443 | "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", 444 | "dev": true, 445 | "requires": { 446 | "hash-base": "^3.0.0", 447 | "inherits": "^2.0.1", 448 | "safe-buffer": "^5.1.2" 449 | } 450 | }, 451 | "miller-rabin": { 452 | "version": "4.0.1", 453 | "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", 454 | "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", 455 | "dev": true, 456 | "requires": { 457 | "bn.js": "^4.0.0", 458 | "brorand": "^1.0.1" 459 | }, 460 | "dependencies": { 461 | "bn.js": { 462 | "version": "4.12.0", 463 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 464 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", 465 | "dev": true 466 | } 467 | } 468 | }, 469 | "minimalistic-assert": { 470 | "version": "1.0.1", 471 | "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", 472 | "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", 473 | "dev": true 474 | }, 475 | "minimalistic-crypto-utils": { 476 | "version": "1.0.1", 477 | "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", 478 | "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", 479 | "dev": true 480 | }, 481 | "node-libs-browser": { 482 | "version": "2.2.1", 483 | "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", 484 | "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", 485 | "dev": true, 486 | "requires": { 487 | "assert": "^1.1.1", 488 | "browserify-zlib": "^0.2.0", 489 | "buffer": "^4.3.0", 490 | "console-browserify": "^1.1.0", 491 | "constants-browserify": "^1.0.0", 492 | "crypto-browserify": "^3.11.0", 493 | "domain-browser": "^1.1.1", 494 | "events": "^3.0.0", 495 | "https-browserify": "^1.0.0", 496 | "os-browserify": "^0.3.0", 497 | "path-browserify": "0.0.1", 498 | "process": "^0.11.10", 499 | "punycode": "^1.2.4", 500 | "querystring-es3": "^0.2.0", 501 | "readable-stream": "^2.3.3", 502 | "stream-browserify": "^2.0.1", 503 | "stream-http": "^2.7.2", 504 | "string_decoder": "^1.0.0", 505 | "timers-browserify": "^2.0.4", 506 | "tty-browserify": "0.0.0", 507 | "url": "^0.11.0", 508 | "util": "^0.11.0", 509 | "vm-browserify": "^1.0.1" 510 | } 511 | }, 512 | "object-assign": { 513 | "version": "4.1.1", 514 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 515 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 516 | "dev": true 517 | }, 518 | "os-browserify": { 519 | "version": "0.3.0", 520 | "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", 521 | "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", 522 | "dev": true 523 | }, 524 | "pako": { 525 | "version": "1.0.11", 526 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", 527 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", 528 | "dev": true 529 | }, 530 | "parse-asn1": { 531 | "version": "5.1.6", 532 | "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", 533 | "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", 534 | "dev": true, 535 | "requires": { 536 | "asn1.js": "^5.2.0", 537 | "browserify-aes": "^1.0.0", 538 | "evp_bytestokey": "^1.0.0", 539 | "pbkdf2": "^3.0.3", 540 | "safe-buffer": "^5.1.1" 541 | } 542 | }, 543 | "path-browserify": { 544 | "version": "0.0.1", 545 | "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", 546 | "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", 547 | "dev": true 548 | }, 549 | "pbkdf2": { 550 | "version": "3.1.2", 551 | "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", 552 | "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", 553 | "dev": true, 554 | "requires": { 555 | "create-hash": "^1.1.2", 556 | "create-hmac": "^1.1.4", 557 | "ripemd160": "^2.0.1", 558 | "safe-buffer": "^5.0.1", 559 | "sha.js": "^2.4.8" 560 | } 561 | }, 562 | "process": { 563 | "version": "0.11.10", 564 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 565 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", 566 | "dev": true 567 | }, 568 | "process-nextick-args": { 569 | "version": "2.0.1", 570 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 571 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 572 | "dev": true 573 | }, 574 | "public-encrypt": { 575 | "version": "4.0.3", 576 | "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", 577 | "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", 578 | "dev": true, 579 | "requires": { 580 | "bn.js": "^4.1.0", 581 | "browserify-rsa": "^4.0.0", 582 | "create-hash": "^1.1.0", 583 | "parse-asn1": "^5.0.0", 584 | "randombytes": "^2.0.1", 585 | "safe-buffer": "^5.1.2" 586 | }, 587 | "dependencies": { 588 | "bn.js": { 589 | "version": "4.12.0", 590 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 591 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", 592 | "dev": true 593 | } 594 | } 595 | }, 596 | "punycode": { 597 | "version": "1.4.1", 598 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 599 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", 600 | "dev": true 601 | }, 602 | "querystring": { 603 | "version": "0.2.0", 604 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 605 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 606 | "dev": true 607 | }, 608 | "querystring-es3": { 609 | "version": "0.2.1", 610 | "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", 611 | "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", 612 | "dev": true 613 | }, 614 | "randombytes": { 615 | "version": "2.1.0", 616 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 617 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 618 | "dev": true, 619 | "requires": { 620 | "safe-buffer": "^5.1.0" 621 | } 622 | }, 623 | "randomfill": { 624 | "version": "1.0.4", 625 | "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", 626 | "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", 627 | "dev": true, 628 | "requires": { 629 | "randombytes": "^2.0.5", 630 | "safe-buffer": "^5.1.0" 631 | } 632 | }, 633 | "readable-stream": { 634 | "version": "2.3.7", 635 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 636 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 637 | "dev": true, 638 | "requires": { 639 | "core-util-is": "~1.0.0", 640 | "inherits": "~2.0.3", 641 | "isarray": "~1.0.0", 642 | "process-nextick-args": "~2.0.0", 643 | "safe-buffer": "~5.1.1", 644 | "string_decoder": "~1.1.1", 645 | "util-deprecate": "~1.0.1" 646 | }, 647 | "dependencies": { 648 | "inherits": { 649 | "version": "2.0.4", 650 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 651 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 652 | "dev": true 653 | }, 654 | "safe-buffer": { 655 | "version": "5.1.2", 656 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 657 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 658 | "dev": true 659 | }, 660 | "string_decoder": { 661 | "version": "1.1.1", 662 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 663 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 664 | "dev": true, 665 | "requires": { 666 | "safe-buffer": "~5.1.0" 667 | } 668 | } 669 | } 670 | }, 671 | "readline-sync": { 672 | "version": "1.4.10", 673 | "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", 674 | "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", 675 | "dev": true 676 | }, 677 | "ripemd160": { 678 | "version": "2.0.2", 679 | "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", 680 | "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", 681 | "dev": true, 682 | "requires": { 683 | "hash-base": "^3.0.0", 684 | "inherits": "^2.0.1" 685 | } 686 | }, 687 | "safe-buffer": { 688 | "version": "5.2.1", 689 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 690 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 691 | "dev": true 692 | }, 693 | "safer-buffer": { 694 | "version": "2.1.2", 695 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 696 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 697 | "dev": true 698 | }, 699 | "setimmediate": { 700 | "version": "1.0.5", 701 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 702 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", 703 | "dev": true 704 | }, 705 | "sha.js": { 706 | "version": "2.4.11", 707 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 708 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 709 | "dev": true, 710 | "requires": { 711 | "inherits": "^2.0.1", 712 | "safe-buffer": "^5.0.1" 713 | } 714 | }, 715 | "shadow-cljs": { 716 | "version": "2.16.0", 717 | "resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-2.16.0.tgz", 718 | "integrity": "sha512-nLFFrBhoC3m2xV//L1RGowAtnxbDrS736QaVty3/FIAcrbA6bU1xx46GJ1eA+bgoXJ2xvhZketkjGSy15T/QCA==", 719 | "dev": true, 720 | "requires": { 721 | "node-libs-browser": "^2.2.1", 722 | "readline-sync": "^1.4.7", 723 | "shadow-cljs-jar": "1.3.2", 724 | "source-map-support": "^0.4.15", 725 | "which": "^1.3.1", 726 | "ws": "^7.4.6" 727 | } 728 | }, 729 | "shadow-cljs-jar": { 730 | "version": "1.3.2", 731 | "resolved": "https://registry.npmjs.org/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz", 732 | "integrity": "sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg==", 733 | "dev": true 734 | }, 735 | "source-map": { 736 | "version": "0.5.7", 737 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 738 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 739 | "dev": true 740 | }, 741 | "source-map-support": { 742 | "version": "0.4.18", 743 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", 744 | "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", 745 | "dev": true, 746 | "requires": { 747 | "source-map": "^0.5.6" 748 | } 749 | }, 750 | "stream-browserify": { 751 | "version": "2.0.2", 752 | "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", 753 | "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", 754 | "dev": true, 755 | "requires": { 756 | "inherits": "~2.0.1", 757 | "readable-stream": "^2.0.2" 758 | } 759 | }, 760 | "stream-http": { 761 | "version": "2.8.3", 762 | "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", 763 | "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", 764 | "dev": true, 765 | "requires": { 766 | "builtin-status-codes": "^3.0.0", 767 | "inherits": "^2.0.1", 768 | "readable-stream": "^2.3.6", 769 | "to-arraybuffer": "^1.0.0", 770 | "xtend": "^4.0.0" 771 | } 772 | }, 773 | "string_decoder": { 774 | "version": "1.3.0", 775 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 776 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 777 | "dev": true, 778 | "requires": { 779 | "safe-buffer": "~5.2.0" 780 | } 781 | }, 782 | "timers-browserify": { 783 | "version": "2.0.12", 784 | "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", 785 | "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", 786 | "dev": true, 787 | "requires": { 788 | "setimmediate": "^1.0.4" 789 | } 790 | }, 791 | "to-arraybuffer": { 792 | "version": "1.0.1", 793 | "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", 794 | "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", 795 | "dev": true 796 | }, 797 | "tty-browserify": { 798 | "version": "0.0.0", 799 | "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", 800 | "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", 801 | "dev": true 802 | }, 803 | "url": { 804 | "version": "0.11.0", 805 | "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", 806 | "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", 807 | "dev": true, 808 | "requires": { 809 | "punycode": "1.3.2", 810 | "querystring": "0.2.0" 811 | }, 812 | "dependencies": { 813 | "punycode": { 814 | "version": "1.3.2", 815 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 816 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", 817 | "dev": true 818 | } 819 | } 820 | }, 821 | "util": { 822 | "version": "0.11.1", 823 | "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", 824 | "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", 825 | "dev": true, 826 | "requires": { 827 | "inherits": "2.0.3" 828 | }, 829 | "dependencies": { 830 | "inherits": { 831 | "version": "2.0.3", 832 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 833 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 834 | "dev": true 835 | } 836 | } 837 | }, 838 | "util-deprecate": { 839 | "version": "1.0.2", 840 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 841 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 842 | "dev": true 843 | }, 844 | "vm-browserify": { 845 | "version": "1.1.2", 846 | "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", 847 | "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", 848 | "dev": true 849 | }, 850 | "which": { 851 | "version": "1.3.1", 852 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 853 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 854 | "dev": true, 855 | "requires": { 856 | "isexe": "^2.0.0" 857 | } 858 | }, 859 | "ws": { 860 | "version": "7.5.5", 861 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", 862 | "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", 863 | "dev": true 864 | }, 865 | "xtend": { 866 | "version": "4.0.2", 867 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 868 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 869 | "dev": true 870 | } 871 | } 872 | } 873 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "shadow-cljs": "2.16.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | net.clojars.cjsauer 5 | joinery 6 | 0.1.0-SNAPSHOT 7 | cjsauer/joinery 8 | FIXME: my new library. 9 | https://github.com/cjsauer/joinery 10 | 11 | 12 | Eclipse Public License 13 | http://www.eclipse.org/legal/epl-v10.html 14 | 15 | 16 | 17 | 18 | Calvin 19 | 20 | 21 | 22 | https://github.com/cjsauer/joinery 23 | scm:git:git://github.com/cjsauer/joinery.git 24 | scm:git:ssh://git@github.com/cjsauer/joinery.git 25 | v0.1.0-SNAPSHOT 26 | 27 | 28 | 29 | org.clojure 30 | clojure 31 | 1.10.3 32 | 33 | 34 | 35 | src 36 | 37 | 38 | 39 | clojars 40 | https://repo.clojars.org/ 41 | 42 | 43 | sonatype 44 | https://oss.sonatype.org/content/repositories/snapshots/ 45 | 46 | 47 | 48 | 49 | clojars 50 | Clojars repository 51 | https://clojars.org/repo 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /resources/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjsauer/joinery/80a8efe2f6fa6bd0bad4ac00076b406a1b580558/resources/.keep -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:deps {:aliases [:test :cljs]} 2 | :builds 3 | {:test {:target :node-test 4 | :output-to "target/node-tests.js" 5 | :ns-regexp "-test$" 6 | :autorun true}}} -------------------------------------------------------------------------------- /src/cjsauer/joinery.cljc: -------------------------------------------------------------------------------- 1 | (ns cjsauer.joinery) 2 | 3 | (defprotocol Joinery 4 | "Description of normalized data source traversal" 5 | (is-join? [this v] "Returns true if the given value v represents a logical join") 6 | (join [this db v] "Returns the result of following the link that is v within the normalized data source db")) 7 | 8 | (deftype TableIdentJoinery [] 9 | Joinery 10 | (is-join? [_ v] (and (vector? v) 11 | (= 2 (count v)) 12 | (keyword? (first v)))) 13 | (join [_ table v] (get-in table v))) 14 | 15 | (declare joined-map? table-join -jm-equiv unwrap) 16 | 17 | (defn- map-entry 18 | [k v] 19 | #?(:clj (clojure.lang.MapEntry. k v) 20 | :cljs (cljs.core/MapEntry. k v nil))) 21 | 22 | #?(:clj (defn throw-arity [] (throw (clojure.lang.ArityException 1 (str `JoinedMap))))) 23 | 24 | (deftype JoinedMap [joinery db m] 25 | #?@(:clj 26 | [clojure.lang.MapEquivalence 27 | 28 | clojure.lang.IPersistentMap 29 | (assoc [_ k v] 30 | (JoinedMap. joinery db (.assoc m k v))) 31 | (assocEx [_ k v] 32 | (JoinedMap. joinery db (.assocEx m k v))) 33 | (without [_ k] 34 | (JoinedMap. joinery db (.without m k))) 35 | 36 | clojure.lang.IMapIterable 37 | (keyIterator [_] 38 | (.iterator (keys m))) 39 | (valIterator [_] 40 | (.iterator (map #(table-join joinery db %) (vals m)))) 41 | 42 | java.lang.Iterable 43 | (iterator [this] 44 | (.iterator (seq this))) 45 | 46 | clojure.lang.Associative 47 | (containsKey [_ k] 48 | (.containsKey m k)) 49 | (entryAt [_ k] 50 | (when-let [[k v] (find m k)] 51 | (map-entry k (table-join joinery db v)))) 52 | 53 | clojure.lang.Counted 54 | (count [_] (.count m)) 55 | 56 | clojure.lang.IPersistentCollection 57 | (cons [_ o] 58 | (JoinedMap. joinery db (.cons m o))) 59 | (empty [_] 60 | (JoinedMap. joinery db (.empty m))) 61 | (equiv [this o] 62 | (or (identical? this o) 63 | (if (joined-map? o) 64 | (.equiv (.m o) m) 65 | (.equiv o this)))) 66 | 67 | clojure.lang.Seqable 68 | (seq [_] 69 | (map (fn [[k v]] 70 | (let [v' (table-join joinery db v)] 71 | (map-entry k v'))) 72 | (seq m))) 73 | 74 | clojure.lang.ILookup 75 | (valAt [this k] 76 | (.valAt this k nil)) 77 | (valAt [_ k not-found] 78 | (let [v (get m k not-found)] 79 | (table-join joinery db v))) 80 | 81 | clojure.lang.IReduce 82 | (reduce [this f] 83 | (reduce f (seq this))) 84 | 85 | clojure.lang.IReduceInit 86 | (reduce [this f init] 87 | (reduce-kv (fn [r k v] 88 | (f r (map-entry k v))) 89 | init this)) 90 | 91 | clojure.core.protocols.IKVReduce 92 | (kv-reduce [this f init] 93 | (reduce (fn [r [k v]] 94 | (f r k v)) 95 | init (seq this))) 96 | 97 | java.util.Map 98 | (get [this k] 99 | (.valAt this k)) 100 | (isEmpty [this] 101 | (empty? this)) 102 | (size [this] 103 | (count this)) 104 | (keySet [_] 105 | (set (keys m))) 106 | (put [_ _ _] 107 | (throw (UnsupportedOperationException.))) 108 | (putAll [_ _] 109 | (throw (UnsupportedOperationException.))) 110 | (clear [_] 111 | (throw (UnsupportedOperationException.))) 112 | (remove [_ _] 113 | (throw (UnsupportedOperationException.))) 114 | (values [this] 115 | (->> this seq (map second))) 116 | (entrySet [this] 117 | (->> this seq set)) 118 | 119 | clojure.lang.IObj 120 | (withMeta [_ mta] 121 | (JoinedMap. joinery db (.withMeta m mta))) 122 | (meta [_] 123 | (meta m)) 124 | 125 | clojure.lang.IHashEq 126 | (hasheq [_] 127 | (.hasheq m)) 128 | 129 | clojure.lang.IFn 130 | (invoke [_this] (throw-arity)) 131 | (invoke [this k] (get this k)) 132 | (invoke [_this _k _] (throw-arity)) 133 | (invoke [_this _k _ _] (throw-arity)) 134 | (invoke [_this _k _ _ _] (throw-arity)) 135 | (invoke [_this _k _ _ _ _] (throw-arity)) 136 | (invoke [_this _k _ _ _ _ _] (throw-arity)) 137 | (invoke [_this _k _ _ _ _ _ _] (throw-arity)) 138 | (invoke [_this _k _ _ _ _ _ _ _] (throw-arity)) 139 | (invoke [_this _k _ _ _ _ _ _ _ _] (throw-arity)) 140 | (invoke [_this _k _ _ _ _ _ _ _ _ _] (throw-arity)) 141 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _] (throw-arity)) 142 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _ _] (throw-arity)) 143 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _ _ _] (throw-arity)) 144 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _ _ _ _] (throw-arity)) 145 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _ _ _ _ _] (throw-arity)) 146 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _ _ _ _ _ _] (throw-arity)) 147 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _] (throw-arity)) 148 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _] (throw-arity)) 149 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _] (throw-arity)) 150 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _] (throw-arity)) 151 | (invoke [_this _k _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _] (throw-arity)) 152 | 153 | Object 154 | (hashCode [_] 155 | (hash m)) 156 | (equals [this o] 157 | (.equiv this o)) 158 | (toString [_] 159 | (str m))]) 160 | 161 | #?@(:cljs 162 | [Object 163 | (toString [this] 164 | (pr-str* this)) 165 | (equiv [this o] 166 | (-equiv this o)) 167 | (keys [this] 168 | (es6-iterator (keys this))) 169 | (entries [this] 170 | (es6-entries-iterator (seq this))) 171 | (values [this] 172 | (es6-iterator (vals this))) 173 | (has [this k] 174 | (contains? this k)) 175 | (get [this k not-found] 176 | (-lookup this k not-found)) 177 | (forEach [this f] 178 | (doseq [[k v] this] 179 | (f v k))) 180 | 181 | ICloneable 182 | (-clone [_] (JoinedMap. joinery db m)) 183 | 184 | IWithMeta 185 | (-with-meta 186 | [_ new-meta] 187 | (JoinedMap. joinery db (-with-meta m new-meta))) 188 | 189 | IMeta 190 | (-meta [_] (meta m)) 191 | 192 | ICollection 193 | (-conj 194 | [_ entry] 195 | (JoinedMap. joinery db (conj m entry))) 196 | 197 | IEmptyableCollection 198 | (-empty 199 | [_] 200 | (JoinedMap. joinery db (empty m))) 201 | 202 | IEquiv 203 | (-equiv 204 | [this ^js o] 205 | (or (identical? this o) 206 | (if (joined-map? o) 207 | (-equiv (.-m o) m) 208 | (-equiv o this)))) 209 | 210 | IHash 211 | (-hash 212 | [_] 213 | (hash m)) 214 | 215 | IIterable 216 | (-iterator 217 | [this] 218 | (-iterator (into {} this))) 219 | 220 | ISeqable 221 | (-seq 222 | [_] 223 | (map (fn [[k v]] 224 | (let [v' (table-join joinery db v)] 225 | (map-entry k v'))) 226 | (seq m))) 227 | 228 | IAssociative 229 | (-assoc 230 | [_ k v] 231 | (JoinedMap. joinery db (-assoc m k v))) 232 | (-contains-key? 233 | [_ k] 234 | (contains? m k)) 235 | 236 | IFind 237 | (-find 238 | [_ k] 239 | (when-let [[k v] (find m k)] 240 | (map-entry k (table-join joinery db v)))) 241 | 242 | IMap 243 | (-dissoc 244 | [_ k] 245 | (JoinedMap. joinery db (dissoc m k))) 246 | 247 | ICounted 248 | (-count [_] (count m)) 249 | 250 | ILookup 251 | (-lookup 252 | [this k] 253 | (-lookup this k nil)) 254 | (-lookup 255 | [_ k not-found] 256 | (let [v (get m k not-found)] 257 | (table-join joinery db v))) 258 | 259 | IReduce 260 | (-reduce 261 | [this f] 262 | (reduce f (seq this))) 263 | (-reduce 264 | [this f init] 265 | (reduce-kv (fn [r k v] 266 | (f r (map-entry k v))) 267 | init this)) 268 | 269 | IKVReduce 270 | (-kv-reduce 271 | [this f init] 272 | (reduce (fn [r [k v]] 273 | (f r k v)) 274 | init (seq this))) 275 | 276 | IFn 277 | (-invoke 278 | [this k] 279 | (get this k)) 280 | (-invoke 281 | [this k not-found] 282 | (get this k not-found)) 283 | 284 | IPrintWithWriter 285 | (-pr-writer 286 | [_ writer opts] 287 | (print-map m pr-writer writer opts))]) 288 | 289 | ;; 290 | ) 291 | 292 | (defn- table-join 293 | [joinery db v] 294 | (cond 295 | (joined-map? v) 296 | v 297 | ;; 298 | (is-join? joinery v) 299 | (JoinedMap. joinery db (join joinery db v)) 300 | ;; 301 | (map? v) 302 | (JoinedMap. joinery db v) 303 | ;; 304 | (coll? v) 305 | (into (empty v) 306 | (map (fn [x] 307 | (if (is-join? joinery x) 308 | (JoinedMap. joinery db (join joinery db x)) 309 | x))) 310 | v) 311 | ;; 312 | :else v)) 313 | 314 | (defn joined-map? 315 | "Returns true if the given object is an instance of JoinedMap" 316 | [o] 317 | (instance? JoinedMap o)) 318 | 319 | (defn joined-map 320 | "Create a new joined-map using the given `db` as the normalize data source. 321 | Optionally takes a starting entity as the second arg that will become the 322 | current position of the joined-map. 323 | Optionally takes a third argument that is an implementation of the 324 | cjsauer.joinery/Joinery protocol to customize the behavior of joins." 325 | ([db] 326 | (->JoinedMap (TableIdentJoinery.) db db)) 327 | ([db e] 328 | (->JoinedMap (TableIdentJoinery.) db e)) 329 | ([db e joinery] 330 | (->JoinedMap joinery db e))) 331 | 332 | (defn unwrap 333 | "Given a JoinedMap instance, returns the plain underlying hash-map that this 334 | joined map wraps." 335 | [^JoinedMap jm] 336 | #?(:clj (.m jm) 337 | :cljs (.-m jm))) 338 | 339 | #?(:clj 340 | (defmethod print-method JoinedMap [jm writer] 341 | (.write writer (str jm)))) -------------------------------------------------------------------------------- /test/cjsauer/joinery_test.cljc: -------------------------------------------------------------------------------- 1 | (ns cjsauer.joinery-test 2 | (:require [clojure.test :refer [deftest testing is]] 3 | [cjsauer.joinery :refer [joined-map joined-map? unwrap]])) 4 | 5 | (def db 6 | {:person/id {1 {:person/name "Calvin" 7 | :person/friends [[:person/id 2]] 8 | :person/pet [:pet/id 1]} 9 | 2 {:person/name "Derek" 10 | :person/friends [[:person/id 1]]}} 11 | :pet/id {1 {:pet/name "Malcolm" 12 | :pet/species :dog 13 | :pet/owner [:person/id 1]}}}) 14 | 15 | (def jm (joined-map db)) 16 | 17 | (deftest joined-map-test 18 | (testing "containment" 19 | (is (true? (contains? jm :person/id))) 20 | (is (false? (contains? jm :foobar)))) 21 | 22 | (testing "count" 23 | (is (= 2 (count jm)))) 24 | 25 | (testing "equality" 26 | (is (= jm jm)) 27 | (is (= (empty jm) {})) 28 | (is (= {} (empty jm))) 29 | (is (= {:a 1} (joined-map {:a 1}))) 30 | (is (= (joined-map {:a 1}) {:a 1})) 31 | (is (not= db jm)) 32 | (is (not= jm db))) 33 | 34 | (testing "lookup" 35 | (let [c (get-in jm [:person/id 1]) 36 | d (get-in jm [:person/id 2]) 37 | m (get-in jm [:person/id 1 :person/pet]) 38 | fs (get-in jm [:person/id 1 :person/friends])] 39 | (is (true? (joined-map? c))) 40 | (is (true? (joined-map? m))) 41 | (is (true? (every? joined-map? fs))) 42 | (is (= c (get-in c [:person/pet :pet/owner]))) 43 | (is (= "Calvin" (:person/name c))) 44 | (is (= "Malcolm" (:pet/name m))) 45 | (is (= d (first fs))) 46 | ;; testing a "loop" 47 | (is (= c (get-in jm [:person/id 1 :person/pet :pet/owner]))))) 48 | 49 | (testing "find" 50 | (let [c (get-in jm [:person/id 1])] 51 | (is (= [:person/name "Calvin"] 52 | (find c :person/name))) 53 | (is (nil? (find c :foobar))))) 54 | 55 | (testing "assoc" 56 | (let [c (get-in jm [:person/id 1]) 57 | c' (assoc c :person/occupation "hacker")] 58 | (is (joined-map? c')) 59 | (is (= "hacker" (:person/occupation c'))) 60 | (is (= c (get-in c' [:person/pet :pet/owner]))))) 61 | 62 | (testing "meta" 63 | (let [jm' (with-meta jm {:foo "bar"})] 64 | (is (= {:foo "bar"} (meta jm'))))) 65 | 66 | (testing "keys" 67 | (let [c (get-in jm [:person/id 1])] 68 | (is (= #{:person/id :pet/id} 69 | (set (keys jm)))) 70 | (is (= #{1 2} 71 | (set (keys (:person/id jm))))) 72 | (is (= #{:person/name :person/friends :person/pet} 73 | (set (keys c)))))) 74 | 75 | (testing "vals" 76 | (let [c (get-in jm [:person/id 1])] 77 | (is (every? joined-map? (vals jm))) 78 | (is (= #{"Calvin" 79 | [(joined-map #:person{:friends [[:person/id 1]], :name "Derek"})] 80 | (joined-map #:pet{:name "Malcolm", :owner [:person/id 1], :species :dog})} 81 | (set (vals c)))) 82 | (is (every? joined-map? (reduce conj [] (vals jm)))))) 83 | 84 | (testing "unwrap" 85 | (is (= db (unwrap jm)) 86 | (= (get-in db [:person/id 1]) 87 | (unwrap (get-in jm [:person/id 1]))))) 88 | 89 | (testing "customize starting entity" 90 | (let [c (get-in jm [:person/id 1]) 91 | e {:current/person [:person/id 1]} 92 | jm' (joined-map db e)] 93 | (is (= c (:current/person jm'))))) 94 | 95 | (testing "to string" 96 | (is (= (str db) (str jm))) 97 | (is (= (str (get-in db [:person/id 1])) 98 | (str (get-in jm [:person/id 1])))) 99 | (is (= (str [(get-in db [:person/id 2])]) 100 | (str (get-in jm [:person/id 1 :person/friends]))))) 101 | 102 | (testing "hashing" 103 | (is (= (hash db) (hash jm))) 104 | (is (= (hash (get-in db [:person/id 1])) 105 | (hash (get-in jm [:person/id 1])))) 106 | (is (= (hash [(get-in db [:person/id 2])]) 107 | (hash (get-in jm [:person/id 1 :person/friends]))))) 108 | 109 | (testing "invoke" 110 | (is (= (joined-map (db :person/id)) 111 | (jm :person/id)))) 112 | 113 | (testing "reduce" 114 | (is (= (seq jm) 115 | (reduce conj [] jm))) 116 | (is (= (seq jm) 117 | (reduce-kv (fn [r k v] (conj r [k v])) [] jm)))) 118 | 119 | #?(:clj (testing "Java iterator" 120 | (let [i (.iterator (get-in jm [:person/id 1])) 121 | _ (.next i) 122 | friends (val (.next i))] 123 | (is (= (get-in jm [:person/id 2]) 124 | (first friends))))))) --------------------------------------------------------------------------------