├── .circleci └── config.yml ├── .clj-kondo └── config.edn ├── .gitignore ├── LICENSE ├── README.md ├── RELEASE.md ├── deps.edn ├── package.json ├── release.edn ├── shadow-cljs.edn ├── src ├── main │ └── applied_science │ │ ├── js_interop.clj │ │ ├── js_interop.cljs │ │ └── js_interop │ │ ├── alpha.cljc │ │ ├── destructure.cljc │ │ ├── impl.cljs │ │ └── inference.cljc └── test │ ├── applied_science │ ├── js_interop_test.cljs │ └── js_interop_usage.cljc │ ├── test1.edn │ └── test2.edn └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2.1 6 | 7 | commands: 8 | prepare: 9 | steps: 10 | - checkout 11 | - run: git fetch --tags 12 | - restore_cache: 13 | key: appliedscience.js_interop.{{ .Environment.CIRCLE_PROJECT_REPONAME }}-{{ checksum "deps.edn" }} 14 | 15 | executors: 16 | clojure: 17 | docker: 18 | - image: circleci/clojure:tools-deps-1.10.0.411-node-browsers-legacy 19 | environment: 20 | JAVA_OPTS: "-Xms512m -Xmx3200m" 21 | 22 | jobs: 23 | deps-and-test: 24 | executor: clojure 25 | steps: 26 | - prepare 27 | 28 | - run: clojure -A:release -Spath 29 | - run: clojure -A:test -Spath 30 | 31 | - run: 32 | name: Test (:optimizations :none) 33 | command: yarn test1; 34 | - run: 35 | name: Test (:optimizations :advanced) 36 | command: yarn test2; 37 | 38 | - save_cache: 39 | key: appliedscience.js_interop.{{ .Environment.CIRCLE_PROJECT_REPONAME }}-{{ checksum "deps.edn" }} 40 | paths: 41 | - ~/.m2 42 | - ~/.gitlibs 43 | - .cpcache 44 | 45 | build-and-deploy: 46 | executor: clojure 47 | steps: 48 | - prepare 49 | - run: clojure -A:release 50 | 51 | workflows: 52 | version: 2 53 | test-and-deploy: 54 | jobs: 55 | - deps-and-test: 56 | filters: 57 | tags: 58 | only: /.*/ 59 | - build-and-deploy: 60 | requires: 61 | - deps-and-test 62 | filters: 63 | tags: 64 | only: /^v.*/ 65 | branches: 66 | ignore: /.*/ 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {applied-science.js-interop/fn clojure.core/fn 2 | applied-science.js-interop/defn clojure.core/defn 3 | applied-science.js-interop/let clojure.core/let 4 | applied-science.js-interop/if-let clojure.core/if-let 5 | applied-science.js-interop/when-let clojure.core/when-let}} 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | .nrepl-port 5 | .cpcache 6 | .shadow-cljs 7 | out 8 | target 9 | pom.xml 10 | *.iml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-interop ![latest release](https://img.shields.io/clojars/v/applied-science/js-interop.svg?color=%23309631&label=release) [![tests passing?](https://circleci.com/gh/applied-science/js-interop.svg?style=svg)](https://circleci.com/gh/applied-science/js-interop) 2 | 3 | A JavaScript-interop library for ClojureScript. 4 | 5 | ## Features 6 | 7 | 1. Operations that mirror behaviour of core Clojure functions like `get`, `get-in`, `assoc!`, etc. 8 | 2. Keys are parsed at compile-time, and support both static keys (via keywords) and compiler-renamable forms (via dot-properties, eg `.-someAttribute`) 9 | 10 | ## Quick Example 11 | 12 | ```clj 13 | (ns my.app 14 | (:require [applied-science.js-interop :as j])) 15 | 16 | (def o #js{ …some javascript object… }) 17 | 18 | ;; Read 19 | (j/get o :x) 20 | (j/get o .-x "fallback-value") 21 | (j/get-in o [:x :y]) 22 | (j/select-keys o [:a :b :c]) 23 | 24 | (let [{:keys [x]} (j/lookup o)] ;; lookup wrapper 25 | ...) 26 | 27 | ;; Destructure 28 | (j/let [^:js {:keys [a b c]} o] ...) 29 | (j/fn [^:js [n1 n2]] ...) 30 | (j/defn my-fn [^:js {:syms [a b c]}]) 31 | 32 | ;; Write 33 | (j/assoc! o :a 1) 34 | (j/assoc-in! o [:x :y] 100) 35 | (j/assoc-in! o [.-x .-y] 100) 36 | 37 | (j/update! o :a inc) 38 | (j/update-in! o [:x :y] + 10) 39 | 40 | ;; Call functions 41 | (j/call o :someFn 42) 42 | (j/apply o :someFn #js[42]) 43 | 44 | (j/call-in o [:x :someFn] 42) 45 | (j/apply-in o [:x :someFn] #js[42]) 46 | 47 | ;; Create 48 | (j/obj :a 1 .-b 2) 49 | (j/lit {:a 1 .-b [2 3 4]}) 50 | ``` 51 | 52 | ## Installation 53 | 54 | ```clj 55 | ;; lein or boot 56 | [applied-science/js-interop "..."] 57 | ``` 58 | ```clj 59 | ;; deps.edn 60 | applied-science/js-interop {:mvn/version "..."} 61 | ``` 62 | 63 | 64 | ## Motivation 65 | 66 | ClojureScript does not have built-in functions for cleanly working with JavaScript 67 | objects without running into complexities related to the Closure Compiler. 68 | The built-in host interop syntax (eg. `(.-theField obj)`) leaves keys subject to renaming, 69 | which is a common case of breakage when working with external libraries. The [externs](https://clojurescript.org/guides/externs) 70 | handling of ClojureScript itself is constantly improving, as is externs handling of the 71 | build tool [shadow-cljs](https://shadow-cljs.github.io/docs/UsersGuide.html#infer-externs), but this is still 72 | a source of bugs and does not cover all cases. 73 | 74 | The recommended approach for JS interop when static keys are desired is to use functions in the `goog.object` namespace such 75 | as `goog.object/get`, `goog.object/getValueByKeys`, and `goog.object/set`. These functions are 76 | performant and useful but they do not offer a Clojure-centric api. Keys need to be passed in as strings, 77 | and return values from mutations are not amenable to threading. The `goog.object` namespace has published breaking changes as recently as [2017](https://github.com/google/closure-library/releases/tag/v20170910). 78 | 79 | One third-party library commonly recommended for JavaScript interop is [cljs-oops](https://github.com/binaryage/cljs-oops). This solves the renaming problem and is highly performant, but the string-oriented api diverges from Clojure norms. 80 | 81 | Neither library lets you choose to allow a given key to be renamed. For that, you must fall back to host-interop (dot) syntax, which has a different API, so the structure of your code may need to change based on unrelated compiler issues. 82 | 83 | The functions in this library work just like their Clojure equivalents, but adapted to a JavaScript context. Static keys are expressed as keywords, renamable keys are expressed via host-interop syntax (eg. `.-someKey`), nested paths are expressed as vectors of keys. Mutation functions are nil-friendly and return the original object, suitable for threading. Usage should be familiar to anyone with Clojure experience. 84 | 85 | ### Reading 86 | 87 | Reading functions include `get`, `get-in`, `select-keys` and follow Clojure lookup syntax (fallback to default values only when keys are not present) 88 | 89 | ```clj 90 | (j/get obj :x) 91 | 92 | (j/get obj :x default-value) ;; `default-value` is returned if key `:x` is not present 93 | 94 | (j/get-in obj [:x :y]) 95 | 96 | (j/get-in obj [:x :y] default-value) 97 | 98 | (j/select-keys obj [:x :z]) 99 | ``` 100 | 101 | `get` and `get-in` return "getter" functions when called with one argument: 102 | 103 | ```clj 104 | (j/get :x) ;; returns a function that will read key `x` 105 | ``` 106 | 107 | This can be useful for various kinds of functional composition (eg. `juxt`): 108 | 109 | ```clj 110 | 111 | (map (j/get :x) some-seq) ;; returns item.x for each item 112 | 113 | (map (juxt (j/get :x) (j/get :y)) some-seq) ;; returns [item.x, item.y] for each item 114 | 115 | ``` 116 | 117 | To cohere with Clojure semantics, `j/get` and `j/get-in` return `nil` if reading from a `nil` object instead of throwing an error. Unchecked variants (slightly faster) are provided as `j/!get` and `j/!get-in`. These will throw when attempting to read a key from an undefined/null object. 118 | 119 | The `lookup` function wraps an object with an `ILookup` implementation, suitable for destructuring: 120 | 121 | ```clj 122 | (let [{:keys [x]} (j/lookup obj)] ;; `x` will be looked up as (j/get obj :x) 123 | ...) 124 | ``` 125 | 126 | ### Destructuring 127 | 128 | With `j/let`, `j/defn` and `j/fn`, opt-in to js-interop lookups by adding `^js` in front of a 129 | binding form: 130 | 131 | ```clj 132 | (j/let [^js {:keys [x y z]} obj ;; static keys using keywords 133 | ^js {:syms [x y z]} obj] ;; renamable keys using symbols 134 | ...) 135 | 136 | (j/fn [^js [n1 n2 n3 & nrest]] ;; array access using aget, and .slice for &rest parameters 137 | ...) 138 | 139 | (j/defn my-fn [^js {:keys [a b c]}] 140 | ...) 141 | 142 | ``` 143 | 144 | The opt-in `^js` syntax was selected so that bindings behave like regular Clojure 145 | wherever `^js` is not explicitly invoked, and js-lookups are immediately recognizable 146 | even in a long `let` binding. (Note: the keyword metadata `^:js` is also accepted.) 147 | 148 | `^js` is recursive. At any depth, you may use `^clj` to opt-out. 149 | 150 | 151 | ### Mutation 152 | 153 | Mutation functions include `assoc!`, `assoc-in!`, `update!`, and `update-in!`. These functions 154 | **mutate the provided object** at the given key/path, and then return it. 155 | 156 | ```clj 157 | (j/assoc! obj :x 10) ;; mutates obj["x"], returns obj 158 | 159 | (j/assoc-in! obj [:x :y] 10) ;; intermediate objects are created when not present 160 | 161 | (j/update! obj :x inc) 162 | 163 | (j/update-in! obj [:x :y] + 10) 164 | ``` 165 | 166 | ### Host-interop (renamable) keys 167 | 168 | Keys of the form `.-someName` may be renamed by the Closure compiler just like other dot-based host interop forms. 169 | 170 | ```clj 171 | (j/get obj .-x) ;; like (.-x obj) 172 | 173 | (j/get obj .-x default) ;; like (.-x obj), but `default` is returned when `x` is not present 174 | 175 | (j/get-in obj [.-x .-y]) 176 | 177 | (j/assoc! obj .-a 1) ;; like (set! (.-a obj) 1), but returns `obj` 178 | 179 | (j/assoc-in! obj [.-x .-y] 10) 180 | 181 | (j/update! obj .-a inc) 182 | ``` 183 | 184 | ### Wrappers 185 | 186 | These utilities provide more convenient access to built-in JavaScript operations. 187 | 188 | #### Array operations 189 | 190 | Wrapped versions of `push!` and `unshift!` operate on arrays, and return the mutated array. 191 | 192 | ```clj 193 | (j/push! a 10) 194 | 195 | (j/unshift! a 10) 196 | ``` 197 | 198 | #### Function operations 199 | 200 | `j/call` and `j/apply` look up a function on an object, and invoke it with `this` bound to the object. These types of calls are particularly hard to get right when externs aren't available because there are no `goog.object/*` utils for this. 201 | 202 | 203 | ```clj 204 | ;; before 205 | (.someFunction o 10) 206 | 207 | ;; after 208 | (j/call o :someFunction 10) 209 | (j/call o .-someFunction 10) 210 | 211 | ;; before 212 | (let [f (.-someFunction o)] 213 | (.apply f o #js[1 2 3])) 214 | 215 | ;; after 216 | (j/apply o :someFunction #js[1 2 3]) 217 | (j/apply o .-someFunction #js[1 2 3]) 218 | ``` 219 | 220 | `j/call-in` and `j/apply-in` evaluate nested functions, with `this` bound to the function's parent object. 221 | 222 | ```clj 223 | (j/call-in o [:x :someFunction] 42) 224 | (j/call-in o [.-x .-someFunction] 1 2 3) 225 | 226 | (j/apply-in o [:x :someFunction] #js[42]) 227 | (j/apply-in o [.-x .-someFunction] #js[1 2 3]) 228 | ``` 229 | 230 | ### Object/array creation 231 | 232 | `j/obj` returns a literal js object for provided keys/values: 233 | 234 | ```clj 235 | (j/obj :a 1 .-b 2) ;; can use renamable keys 236 | ``` 237 | 238 | `j/lit` returns literal js objects/arrays for an arbitrarily nested structure of maps/vectors: 239 | 240 | ```clj 241 | (j/lit {:a 1 .-b [2 3]}) 242 | ``` 243 | 244 | `j/lit` supports unquote-splicing (similar to es6 spread): 245 | 246 | ```clj 247 | (j/lit [1 2 ~@some-sequential-value]) 248 | ``` 249 | 250 | `~@` is compiled to a loop of `.push` invocations, using `.forEach` when we infer the value to be an array, otherwise `doseq`. 251 | 252 | ### Threading 253 | 254 | Because all of these functions return their primary argument (unlike the functions in `goog.object`), 255 | they are suitable for threading. 256 | 257 | ```clj 258 | (-> #js {} 259 | (j/assoc-in! [:x :y] 9) 260 | (j/update-in! [:x :y] inc) 261 | (j/get-in [:x :y])) 262 | 263 | #=> 10 264 | ``` 265 | 266 | ## Core operations 267 | 268 | | | _arguments_| _examples_ | 269 | |-------------------|-------------------------------------------|----------------------------------------------------------------| 270 | | **j/get** | [key]
[obj key]
[obj key not-found] | `(j/get :x)` ;; returns a getter function
`(j/get o :x)`
`(j/get o :x :default-value)`
`(j/get o .-x)`| 271 | | **j/get-in** | [path]
[obj path]
[obj path not-found] | `(j/get-in [:x :y])` ;; returns a getter function
`(j/get-in o [:x :y])`
`(j/get-in o [:x :y] :default-value)` | 272 | | **j/select-keys** | [obj keys] | `(j/select-keys o [:a :b :c])` | 273 | | **j/assoc!** | [obj key value]
[obj key value & kvs] | `(j/assoc! o :a 1)`
`(j/assoc! o :a 1 :b 2)` | 274 | | **j/assoc-in!** | [obj path value] | `(j/assoc-in! o [:x :y] 100)` | 275 | | **j/update!** | [obj key f & args] | `(j/update! o :a inc)`
`(j/update! o :a + 10)` | 276 | | **j/update-in!** | [obj path f & args] | `(j/update-in! o [:x :y] inc)`
`(j/update-in! o [:x :y] + 10)` | 277 | 278 | ## Destructuring forms 279 | 280 | | | _example_ | 281 | |-------------------|-------------------------------------------| 282 | | **j/let** | `(j/let [^:js {:keys [a]} obj] ...)` | 283 | | **j/fn** | `(j/fn [^:js [a b c]] ...)` | 284 | | **j/defn** | `(j/defn [^:js {:syms [a]}] ...)` | 285 | 286 | ## Tests 287 | 288 | To run the tests: 289 | 290 | ```clj 291 | yarn test; 292 | ``` 293 | 294 | ## Patronage 295 | 296 | Special thanks to [NextJournal](https://nextjournal.com) for supporting the maintenance of this library. -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Releases 2 | 3 | To tag a version for release: 4 | 5 | ``` 6 | $ clj -A:release tag --patch (or --minor, --major) 7 | ``` 8 | 9 | After pushing a tagged commit, build+deploy will be triggered automatically on CircleCI. 10 | 11 | ``` 12 | git push --follow-tags 13 | ``` -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths 2 | ["src/main"] 3 | 4 | :deps 5 | {appliedscience/js-interop {:mvn/version "0.2.6-MOVED"}} 6 | 7 | :aliases 8 | {:usage 9 | {:extra-paths ["src/test"]} 10 | :test 11 | {:extra-paths ["src/test" 12 | "out/test-2/gen"] 13 | :extra-deps {olical/cljs-test-runner {:mvn/version "3.5.0"}} 14 | :main-opts ["-m" "cljs-test-runner.main"]} 15 | :release 16 | {:extra-deps {applied-science/deps-library {:mvn/version "0.4.0"}} 17 | :main-opts ["-m" "applied-science.deps-library"]} 18 | :dev 19 | {:extra-deps {org.clojure/clojure {:mvn/version "1.10.0"} 20 | org.clojure/clojurescript {:mvn/version "1.10.753"}}}}} 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test1": "clojure -A:dev:test -d src/test -o out/test-1 -c src/test/test1.edn", 4 | "test2": "mkdir -p out/test-2/gen && clojure -A:dev:test -d src/test -o out/test-2 -c src/test/test2.edn", 5 | "test": "yarn test1 && yarn test2", 6 | "usage": "shadow-cljs release usage" 7 | }, 8 | "devDependencies": { 9 | "source-map-support": "^0.5.10", 10 | "shadow-cljs": "^2.22.8" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /release.edn: -------------------------------------------------------------------------------- 1 | {:group-id "applied-science" 2 | :artifact-id "js-interop" 3 | :scm-url "https://github.com/applied-science/js-interop"} -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | ;; shadow-cljs configuration, for development purposes only 2 | {:source-paths 3 | ["src/main" 4 | "src/test"] 5 | 6 | :dependencies 7 | [] 8 | 9 | :builds 10 | {:test1 11 | {:target :node-test 12 | :output-to "out/shadow-1.js" 13 | :ns-regexp "-test$" 14 | :autorun true} 15 | :test2 16 | {:target :node-test 17 | :output-to "out/shadow-2.js" 18 | :ns-regexp "-test$" 19 | :autorun true 20 | :closure-defines {applied-science.js-interop-test/advanced? true} 21 | :compiler-options {:optimizations :advanced}} 22 | :usage 23 | {:target :browser 24 | :output-dir "out" 25 | :modules {:shadow-usage {:entries [applied-science.js-interop-usage]}} 26 | :compiler-options {:pseudo-names true}}}} 27 | -------------------------------------------------------------------------------- /src/main/applied_science/js_interop.clj: -------------------------------------------------------------------------------- 1 | (ns applied-science.js-interop 2 | (:refer-clojure :exclude [get get-in contains? select-keys assoc! 3 | unchecked-get unchecked-set apply extend 4 | let fn defn spread if-let when-let]) 5 | (:require [clojure.core :as c] 6 | [cljs.compiler :as comp] 7 | [cljs.analyzer :as ana] 8 | [clojure.string :as str] 9 | [applied-science.js-interop.destructure :as d] 10 | [applied-science.js-interop.inference :as inf])) 11 | 12 | (def ^:private reflect-property 'js/goog.reflect.objectProperty) 13 | (def ^:private wrap-key* 'applied-science.js-interop.impl/wrap-key) 14 | (def ^:private empty-obj '(cljs.core/js-obj)) 15 | (def ^:private *let 'clojure.core/let) 16 | 17 | ;;;;;;;;;;;;;;;;;;;;;;;; 18 | ;; 19 | ;; Host-key utils 20 | 21 | (defn- dot-sym? [k] 22 | (and (symbol? k) 23 | (str/starts-with? (name k) "."))) 24 | 25 | (defn- dot-name [sym] 26 | (str/replace (name sym) #"^\.\-?" "")) 27 | 28 | (defn- dot-get [sym] 29 | (symbol (str ".-" (dot-name sym)))) 30 | 31 | (defn- dot-call [sym] 32 | (symbol (str "." (dot-name sym)))) 33 | 34 | ;;;;;;;;;;;;;;;;;;;;;;;; 35 | ;; 36 | ;; Key conversion 37 | ;; 38 | ;; Throughout this namespace, k* and ks* refer to keys that have already been wrapped. 39 | 40 | (defn- as-string [x] (with-meta x {:tag 'string})) 41 | 42 | (defn- wrap-key 43 | "Convert key to string at compile time when possible." 44 | [env obj k] 45 | (cond 46 | (or (string? k) 47 | (number? k)) k 48 | (keyword? k) (name k) 49 | (or (symbol? k) 50 | (seq? k)) (if (dot-sym? k) 51 | (as-string `(~reflect-property ~(comp/munge (dot-name k)) ~obj)) 52 | (c/let [tags (inf/infer-tags env k)] 53 | (cond 54 | (inf/within? '#{string number} tags) k 55 | (inf/within? '#{keyword} tags) `(name ~k) 56 | :else (as-string `(~wrap-key* ~k))))) 57 | :else (as-string `(~wrap-key* ~k)))) 58 | 59 | (defn- wrap-keys 60 | "Fallback to wrapping keys at runtime" 61 | [ks] 62 | `(mapv ~wrap-key* ~ks)) 63 | 64 | (defn- literal-ks [ks] 65 | (cond (vector? ks) ks 66 | (and (list? ks) 67 | (= 'cljs.core/array (first ks))) (vec (rest ks)))) 68 | 69 | ;;;;;;;;;;;;;;;;;;;;;;;; 70 | ;; 71 | ;; Unchecked operations 72 | 73 | (defmacro in? [k obj] 74 | `(~'applied-science.js-interop.impl/in?* ~k ~obj)) 75 | 76 | (defmacro unchecked-get 77 | ([obj k] 78 | (if (dot-sym? k) 79 | `(~(dot-get k) ~obj) 80 | `(~'cljs.core/unchecked-get ~obj ~(wrap-key &env nil k)))) 81 | ([obj k not-found] 82 | (c/let [o (gensym "obj") 83 | k-sym (gensym "k")] 84 | `(~*let [~o ~obj 85 | ~k-sym ~(wrap-key &env o k)] 86 | (if (in? ~k-sym ~o) 87 | (unchecked-get ~o ~k-sym) 88 | ~not-found))))) 89 | 90 | (defmacro !get [& args] 91 | `(unchecked-get ~@args)) 92 | 93 | (defmacro unchecked-set [obj & keyvals] 94 | (c/let [o (gensym "obj")] 95 | `(~*let [~o ~obj] 96 | ~@(for [[k v] (partition 2 keyvals)] 97 | (if (dot-sym? k) 98 | `(set! (~(dot-get k) ~o) ~v) 99 | `(~'cljs.core/unchecked-set ~o ~(wrap-key &env nil k) ~v))) 100 | ~o))) 101 | 102 | (defmacro !set [obj & keyvals] 103 | `(applied-science.js-interop/unchecked-set ~obj ~@keyvals)) 104 | 105 | ;;;;;;;;;;;;;;;;;;;;;;;; 106 | ;; 107 | ;; Lookups 108 | 109 | (defmacro contains? 110 | [obj k] 111 | (if (:ns &env) 112 | (c/let [o (gensym "obj")] 113 | `(~*let [~o ~obj] 114 | (and (some? ~o) 115 | (in? ~(wrap-key &env o k) ~o)))) 116 | `(~'clojure.core/contains? ~obj ~k))) 117 | 118 | (defn- get* 119 | ([env obj k] 120 | (if (:ns env) 121 | (c/let [o (gensym "obj")] 122 | `(~*let [~o ~obj] 123 | (if (some? ~o) 124 | (cljs.core/unchecked-get ~o ~(wrap-key env o k)) 125 | ~'js/undefined))) 126 | `(~'clojure.core/get ~obj ~k))) 127 | ([env obj k not-found] 128 | (if (:ns env) 129 | `(~*let [val# ~(get* env obj k)] 130 | (if (cljs.core/undefined? val#) 131 | ~not-found 132 | val#)) 133 | `(~'clojure.core/get ~obj ~k ~not-found)))) 134 | 135 | (defmacro get 136 | ([k] 137 | `(c/fn [obj#] (get obj# ~k))) 138 | ([obj k] 139 | (get* &env obj k)) 140 | ([obj k not-found] 141 | (get* &env obj k not-found))) 142 | 143 | (defmacro get-in 144 | ([ks] 145 | (if (:ns &env) 146 | `(c/let [ks# ~(c/if-let [ks (literal-ks ks)] 147 | (mapv #(wrap-key &env nil %) ks) 148 | (wrap-keys ks))] 149 | (c/fn [obj#] 150 | (~'applied-science.js-interop.impl/get-in* obj# ks#))) 151 | `(c/let [ks# ~ks] 152 | (c/fn 153 | ([m#] (c/get-in m# ks#)) 154 | ([m# not-found#] (c/get-in m# not-found#)))))) 155 | ([obj ks] 156 | (if (:ns &env) 157 | (c/if-let [ks (literal-ks ks)] 158 | (reduce (partial get* &env) obj ks) 159 | `(~'applied-science.js-interop.impl/get-in* ~obj ~(wrap-keys ks))) 160 | `(c/get-in ~obj ~ks))) 161 | ([obj ks not-found] 162 | (if (:ns &env) 163 | (c/if-let [ks (literal-ks ks)] 164 | `(~*let [out# ~(reduce 165 | (c/fn [out k] 166 | `(~*let [out# ~out] 167 | (if (cljs.core/undefined? out#) 168 | ~'js/undefined 169 | (get out# ~k)))) obj ks)] 170 | (if (cljs.core/undefined? out#) 171 | ~not-found 172 | out#)) 173 | `(~'applied-science.js-interop.impl/get-in* ~obj ~(wrap-keys ks) ~not-found)) 174 | `(c/get-in ~obj ~ks ~not-found)))) 175 | 176 | (defmacro !get-in 177 | [obj ks] 178 | (if (:ns &env) 179 | (reduce (c/fn [out k] `(!get ~out ~k)) obj (literal-ks ks)) 180 | `(c/get-in ~obj ~ks))) 181 | 182 | (defmacro select-keys [obj ks] 183 | (if (:ns &env) 184 | (c/if-let [ks (literal-ks ks)] 185 | (c/let [o (gensym "obj") 186 | out (gensym "out")] 187 | `(~*let [~o ~obj] 188 | (if (some? ~o) 189 | (~*let [~out ~empty-obj] 190 | ~@(for [k ks] 191 | `(~*let [k# ~(wrap-key &env o k)] 192 | (when (in? k# ~o) 193 | (!set ~out k# (!get ~o k#))))) 194 | ~out) 195 | ~empty-obj))) 196 | `(~'applied-science.js-interop.impl/select-keys* ~obj ~(wrap-keys ks))) 197 | `(c/select-keys ~obj ~ks))) 198 | 199 | ;;;;;;;;;;;;;;;;;;;;;;;; 200 | ;; 201 | ;; Mutations 202 | 203 | ;; helpers 204 | 205 | (defmacro some-or 206 | "Like `or` but switches on `some?` instead of truthiness." 207 | [x y] 208 | `(if (some? ~x) ~x ~y)) 209 | 210 | (defn- get+! 211 | ;; internal 212 | ;; Returns `k` of `o`. If nil, sets and returns a new empty child object. 213 | [o k] 214 | (c/let [child (gensym "child")] 215 | `(~*let [~child (!get ~o ~k)] 216 | (some-or ~child 217 | (~*let [new-child# ~empty-obj] 218 | (!set ~o ~k new-child#) 219 | new-child#))))) 220 | 221 | (defn- get-in+! 222 | ;; internal 223 | [o ks] 224 | (reduce get+! o ks)) 225 | 226 | ;; core operations 227 | 228 | (defmacro assoc! [obj & keyvals] 229 | (if (:ns &env) 230 | (c/let [o (gensym "obj")] 231 | `(~*let [~o ~obj] 232 | (-> (some-or ~o ~empty-obj) 233 | ~@(for [[k v] (partition 2 keyvals)] 234 | `(!set ~k ~v))))) 235 | `(c/assoc ~obj ~@keyvals))) 236 | 237 | (defmacro assoc-in! [obj ks v] 238 | (if (:ns &env) 239 | (c/if-let [ks (literal-ks ks)] 240 | (c/let [o (gensym "obj")] 241 | `(~*let [~o ~obj 242 | ~o (some-or ~o ~empty-obj)] 243 | (!set ~(get-in+! o (drop-last ks)) ~(last ks) ~v) 244 | ~o)) 245 | `(~'applied-science.js-interop.impl/assoc-in* ~obj ~(wrap-keys ks) ~v)) 246 | `(c/assoc-in ~obj ~ks ~v))) 247 | 248 | (defmacro !assoc-in! [obj ks v] 249 | (if (:ns &env) 250 | (c/let [ks (literal-ks ks)] 251 | `(~*let [obj# ~obj] 252 | (-> (!get-in obj# ~(drop-last ks)) 253 | (!set ~(last ks) ~v)) 254 | obj#)) 255 | `(c/assoc-in ~obj ~ks ~v))) 256 | 257 | (defmacro !update [obj k f & args] 258 | (if (:ns &env) 259 | `(~*let [o# ~obj] 260 | (!set o# ~k (~f (!get o# ~k) ~@args))) 261 | `(c/update ~obj ~k ~f ~@args))) 262 | 263 | (defmacro update! [obj k f & args] 264 | (if (:ns &env) 265 | `(~*let [o# ~obj] 266 | (!update (some-or o# ~empty-obj) ~k ~f ~@args)) 267 | `(c/update ~obj ~k ~f ~@args))) 268 | 269 | (defmacro update-in! [obj ks f & args] 270 | (if (:ns &env) 271 | (c/if-let [ks (literal-ks ks)] 272 | (c/let [o (gensym "obj")] 273 | `(~*let [~o ~obj 274 | ~o (some-or ~o ~empty-obj) 275 | inner-obj# ~(get-in+! o (drop-last ks))] 276 | (update! inner-obj# ~(last ks) ~f ~@args) 277 | ~o)) 278 | `(~'applied-science.js-interop.impl/update-in* ~obj ~(wrap-keys ks) ~f ~(vec args))) 279 | `(c/update-in ~obj ~ks ~f ~@args))) 280 | 281 | (defmacro delete! [obj k] 282 | `(doto ~obj (~'js-delete ~(wrap-key &env obj k)))) 283 | 284 | ;;;;;;;;;;;;;;;;;;;;;;;; 285 | ;; 286 | ;; Array operations 287 | 288 | (defn- tagged-sym [tag] (with-meta (gensym (name tag)) {:tag tag})) 289 | 290 | (defmacro push! 291 | ([] (if (:ns &env) 292 | `(cljs.core/array) 293 | [])) 294 | ([array] array) 295 | ([array v] 296 | (if (:ns &env) 297 | (c/let [sym (tagged-sym 'js/Array)] 298 | `(~*let [~sym ~array] 299 | (~'.push ~sym ~v) 300 | ~sym)) 301 | `(c/conj ~array ~v)))) 302 | 303 | (defmacro unshift! [array v] 304 | `(doto ~array 305 | (~'.unshift ~v))) 306 | 307 | ;;;;;;;;;;;;;;;;;;;;;;;; 308 | ;; 309 | ;; Function operations 310 | 311 | (defmacro call 312 | ([k] 313 | `(c/fn [o#] (~'applied-science.js-interop/call o# ~k))) 314 | ([obj k & args] 315 | (if (dot-sym? k) 316 | `(~(dot-call k) ~obj ~@args) 317 | `(~*let [obj# ~obj 318 | ^function f# (!get obj# ~k)] 319 | (.call f# obj# ~@args))))) 320 | 321 | (defmacro call-in [obj ks & args] 322 | (c/if-let [ks (literal-ks ks)] 323 | `(~*let [parent# (!get-in ~obj ~(pop ks)) 324 | ^function f# (!get parent# ~(peek ks))] 325 | (.call f# parent# ~@args)) 326 | `(~'applied-science.js-interop.impl/apply-in* ~obj ~(wrap-keys ks) (cljs.core/array ~@args)))) 327 | 328 | (defmacro apply [obj k arg-array] 329 | `(~*let [obj# ~obj 330 | ^function f# (!get obj# ~k)] 331 | (.apply f# obj# ~arg-array))) 332 | 333 | (defmacro apply-in [obj ks arg-array] 334 | (c/if-let [ks (literal-ks ks)] 335 | `(~*let [parent# (!get-in ~obj ~(pop ks)) 336 | ^function f# (!get parent# ~(peek ks))] 337 | (.apply f# parent# ~arg-array)) 338 | `(~'applied-science.js-interop.impl/apply-in* ~obj ~(wrap-keys ks) ~arg-array))) 339 | 340 | ;;;;;;;;;;;;;;;;;;;;;;;; 341 | ;; 342 | ;; Object creation 343 | 344 | (defn- literal-obj 345 | [keyvals] 346 | (c/let [;; support for multiple keys binding to the same value, eg {[:a :b] :C} 347 | ;; (js objects would normally 'stringify' the vector) 348 | [lets keyvals] (reduce (c/fn [[lets keyvals] [k v]] 349 | (if (vector? k) 350 | (c/let [sym (gensym)] 351 | [(conj lets sym v) 352 | (reduce (c/fn [keyvals k] (assoc keyvals k sym)) keyvals k)]) 353 | [lets (assoc keyvals k v)])) 354 | [[] {}] 355 | keyvals) 356 | keyvals-str (str "({" (->> (map (c/fn [[k _]] 357 | (str (if (dot-sym? k) 358 | ;; renamable key 359 | (comp/munge (dot-name k)) 360 | ;; static key 361 | (str \" (name k) \")) 362 | ":~{}")) keyvals) 363 | (str/join ",")) "})") 364 | obj (vary-meta (list* 'js* keyvals-str (map second keyvals)) 365 | assoc :tag 'object)] 366 | (if (seq lets) 367 | `(c/let ~lets ~obj) 368 | obj))) 369 | 370 | (defmacro obj 371 | [& keyvals] 372 | (c/let [kvs (partition 2 keyvals)] 373 | (if (every? #(or (keyword? %) 374 | (char? %) 375 | (string? %) 376 | (dot-sym? %) 377 | (vector? %)) (map first kvs)) 378 | (literal-obj kvs) 379 | `(-> ~empty-obj 380 | ~@(for [[k v] kvs] 381 | `(!set ~k ~v)))))) 382 | 383 | ;; Nested literals (maps/vectors become objects/arrays) 384 | 385 | (c/defn litval* [v] 386 | (if (keyword? v) 387 | (cond->> (name v) 388 | (namespace v) 389 | (str (namespace v) "/")) 390 | v)) 391 | 392 | (declare lit*) 393 | 394 | (defn- spread 395 | "For ~@spread values, returns the unwrapped value, 396 | otherwise returns nil." 397 | [x] 398 | (when (and (seq? x) 399 | (= 'clojure.core/unquote-splicing (first x))) 400 | (second x))) 401 | 402 | (c/defn apply-to-bodies 403 | "For recursive lit*, ignores known clj syntax" 404 | [f expr] 405 | (c/let [op (first expr) 406 | ;; default to js let, fn, defn 407 | qop (cond-> op (symbol? op) ana/resolve-symbol) 408 | op ('{clojure.core/if-let applied-science.js-interop/js-if-let 409 | clojure.core/if-some applied-science.js-interop/js-if-some 410 | clojure.core/when-let applied-science.js-interop/js-when-let 411 | clojure.core/when-some applied-science.js-interop/js-when-some 412 | clojure.core/let applied-science.js-interop/js-let 413 | cljs.core/let applied-science.js-interop/js-let 414 | clojure.core/fn applied-science.js-interop/js-fn 415 | cljs.core/fn applied-science.js-interop/js-fn 416 | clojure.core/defn applied-science.js-interop/js-defn 417 | clojure.core/defn- applied-science.js-interop/js-defn 418 | cljs.core/defn applied-science.js-interop/js-defn 419 | cljs.core/defn- applied-science.js-interop/js-defn} qop op) 420 | op-name (if (symbol? op) 421 | (name op) 422 | "")] 423 | (cond (and (re-find #"\b(let|loop|some)$" op-name) 424 | (vector? (second expr))) 425 | (c/let [bindings (->> (second expr) 426 | (partition 2) 427 | (mapcat 428 | (c/fn [[k v]] [k (f v)])) 429 | vec)] 430 | `(~op ~bindings ~@(map f (drop 2 expr)))) 431 | 432 | (re-find #"\b(defn\-?|fn\*?)$" op-name) 433 | (c/let [expr (cons op (rest expr)) 434 | [pre post] (split-with #(or (symbol? %) 435 | (string? %) 436 | (map? %)) expr) 437 | handle-fn-body #(cons (d/js-tag-all (first %)) (map f (rest %))) 438 | post (if (vector? (first post)) 439 | (handle-fn-body post) 440 | (map handle-fn-body post))] 441 | (concat pre post)) 442 | 443 | (#{'applied-science.js-interop/log 444 | 'applied-science.js-interop/dolog} qop) 445 | `(~op ~@(map (c/fn [x] (cond-> x (not (keyword? x)) f)) (rest expr))) 446 | 447 | :else (map f expr)))) 448 | 449 | (c/defn lit* 450 | "Recursively converts literal Clojure maps/vectors into JavaScript object/array expressions 451 | 452 | Options map accepts a :keyfn for custom key conversions." 453 | ([x] 454 | (lit* nil x)) 455 | ([{:as opts 456 | :keys [keyfn valfn env deep?] 457 | :or {keyfn identity 458 | valfn litval*}} x] 459 | (if (and (coll? x) (d/clj-tag? (meta x))) 460 | x 461 | (cond (and deep? (list? x)) (apply-to-bodies (partial lit* opts) x) 462 | (map? x) 463 | (list* 'applied-science.js-interop/obj 464 | (reduce-kv #(conj %1 (keyfn %2) (lit* opts %3)) [] x)) 465 | (vector? x) 466 | (if (some spread x) 467 | (c/let [sym (tagged-sym 'js/Array)] 468 | `(c/let [~sym (~'cljs.core/array)] 469 | ;; handling the spread operator 470 | ~@(for [x' 471 | ;; chunk array members into spreads & non-spreads, 472 | ;; so that sequential non-spreads can be lumped into 473 | ;; a single .push 474 | (->> (partition-by spread x) 475 | (mapcat (clojure.core/fn [x] 476 | (if (spread (first x)) 477 | x 478 | (list x)))))] 479 | (c/if-let [x' (spread x')] 480 | (if (and env (inf/tag-in? env '#{array} x')) 481 | `(.forEach ~x' (c/fn [x#] (.push ~sym x#))) 482 | `(doseq [x# ~(lit* x')] (.push ~sym x#))) 483 | `(.push ~sym ~@(map (partial lit* opts) x')))) 484 | ~sym)) 485 | (list* 'cljs.core/array (mapv (partial lit* opts) x))) 486 | (keyword? x) x 487 | :else (valfn x))))) 488 | 489 | (c/defn clj-lit 490 | "lit for Clojure target, only handles ~@unquote-splicing" 491 | [x] 492 | (cond (map? x) 493 | (reduce-kv (c/fn [m k v] (c/assoc m k (clj-lit v))) {} x) 494 | (vector? x) 495 | (if (some spread x) 496 | (c/let [sym (gensym)] 497 | `(c/let [~sym (volatile! [])] 498 | ;; handling the spread operator 499 | ~@(for [x' x 500 | :let [many (spread x')]] 501 | (if many 502 | (if (vector? many) 503 | `(vswap! ~sym conj ~@(map clj-lit many)) 504 | `(vswap! ~sym into ~many)) 505 | `(vswap! ~sym conj ~x'))) 506 | @~sym)) 507 | (mapv clj-lit x)) 508 | :else x)) 509 | 510 | (defmacro lit 511 | "Recursively converts literal Clojure maps/vectors into JavaScript object/array expressions 512 | (using j/obj and cljs.core/array)" 513 | [form] 514 | (if (:ns &env) 515 | (lit* {:env &env} form) 516 | (clj-lit form))) 517 | 518 | ;;;;;;;;;;;;;;;;;;;;;;;; 519 | ;; 520 | ;; Destructured forms 521 | 522 | (defmacro let 523 | "`let` with destructuring that supports js property and array access. 524 | Use ^:js metadata on the binding form to invoke. Eg/ 525 | 526 | (let [^:js {:keys [a]} obj] …)" 527 | [bindings & body] 528 | `(c/let ~(cond-> bindings (:ns &env) d/destructure) ~@body)) 529 | 530 | (defmacro fn 531 | "`fn` with argument destructuring that supports js property and array access. 532 | Use ^:js metadata on binding forms to invoke. Eg/ 533 | 534 | (fn [^:js {:keys [a]}] …)" 535 | [& args] 536 | (if (:ns &env) 537 | (cons 'clojure.core/fn (d/destructure-fn-args args)) 538 | `(c/fn ~@args))) 539 | 540 | (defmacro defn 541 | "`defn` with argument destructuring that supports js property and array access. 542 | Use ^:js metadata on binding forms to invoke." 543 | [& args] 544 | (if (:ns &env) 545 | (cons 'clojure.core/defn (d/destructure-fn-args args)) 546 | `(c/defn ~@args))) 547 | 548 | (defmacro if-let 549 | ([bindings then else] 550 | `(c/if-let [result# ~(second bindings)] 551 | (~'applied-science.js-interop/let [~(first bindings) result#] 552 | ~then) 553 | ~else)) 554 | ([bindings then] `(if-let ~bindings ~then ~nil))) 555 | 556 | (defmacro when-let [bindings & body] 557 | `(if-let ~bindings (do ~@body))) 558 | 559 | (defn tag-js [x] (vary-meta x assoc :tag 'js)) 560 | ;; variants of let, fn, defn which destructure as js by default 561 | (defmacro js-let [bindings & body] 562 | `(~'applied-science.js-interop/let ~(tag-js bindings) ~@body)) 563 | (defmacro js-if-let [bindings & args] `(if-let ~(tag-js bindings) ~@args)) 564 | (defmacro js-if-some [bindings & args] `(if-some ~(tag-js bindings) ~@args)) 565 | (defmacro js-when-let [bindings & body] `(if-let ~(tag-js bindings) (do ~@body))) 566 | (defmacro js-when-some [bindings & body] `(when-some ~(tag-js bindings) (do ~@body))) 567 | (defmacro js-fn [& args] 568 | (binding [d/*js?* true] 569 | `(~'clojure.core/fn ~@(d/destructure-fn-args args)))) 570 | (defmacro js-defn [& args] 571 | (binding [d/*js?* true] 572 | `(~'clojure.core/defn ~@(d/destructure-fn-args args)))) 573 | 574 | 575 | (defn- str-kw [k] (cond-> k (keyword? k) str)) 576 | 577 | (defmacro log 578 | "js/console.log" 579 | [& args] 580 | `(~'js/console.log ~@args)) 581 | 582 | (defmacro expand [expr] 583 | `'~(macroexpand expr)) 584 | 585 | (comment 586 | ;; clj examples - default clojure behaviour 587 | (let [x [6 7]] 588 | (lit {:a [1 2 3 ~@[4 5] ~@x]})) 589 | (get {:x 1} :x) 590 | (get '{x 1} 'x nil) 591 | (get-in {:x {:y 1}} [:x :y]) 592 | (let [^js {:keys [x]} {:x 1}] x) 593 | (contains? {:x 1} :x)) -------------------------------------------------------------------------------- /src/main/applied_science/js_interop.cljs: -------------------------------------------------------------------------------- 1 | ;; Some docstrings copied and/or adapted from ClojureScript, which is copyright (c) Rich Hickey. 2 | ;; See https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs 3 | 4 | (ns applied-science.js-interop 5 | "A JavaScript-interop library for ClojureScript." 6 | (:refer-clojure :exclude [get get-in assoc! assoc-in! update! update-in! select-keys contains? unchecked-get unchecked-set apply]) 7 | (:require [goog.reflect :as reflect] 8 | [cljs.core :as core] 9 | [applied-science.js-interop.impl :as impl]) 10 | (:require-macros [applied-science.js-interop :as j])) 11 | 12 | ;;;;;;;;;;;;;;;;;;;;;;;; 13 | ;; 14 | ;; Unchecked operations 15 | 16 | (defn unchecked-set [obj & keyvals] 17 | (loop [[k v & keyvals] keyvals] 18 | (core/unchecked-set obj (impl/wrap-key k) v) 19 | (when keyvals 20 | (recur keyvals))) 21 | obj) 22 | 23 | (defn unchecked-get [obj k] 24 | (core/unchecked-get obj (impl/wrap-key k))) 25 | 26 | ;;;;;;;;;;;;;;;;;;;;;;;; 27 | ;; 28 | ;; Lookups 29 | 30 | (defn get 31 | "Returns the value mapped to key, not-found or nil if key not present. 32 | 33 | ``` 34 | (j/get o :k) 35 | (j/get o .-k) 36 | ```" 37 | ([k] (fn [obj] (j/get obj k))) 38 | ([obj k] 39 | (j/get obj k)) 40 | ([obj k not-found] 41 | (j/get obj k not-found))) 42 | 43 | (defn get-in 44 | "Returns the value in a nested object structure, where ks is 45 | a sequence of keys. Returns nil if the key is not present, 46 | or the not-found value if supplied. 47 | 48 | ``` 49 | (j/get-in o [:x :y] :fallback-value) 50 | (j/get-in o [.-x .-y] :fallback-value) 51 | ```" 52 | ([ks] 53 | (let [ks (mapv impl/wrap-key ks)] 54 | (fn [obj] (impl/get-in* obj ks)))) 55 | ([obj ks] 56 | (impl/get-in* obj (mapv impl/wrap-key ks))) 57 | ([obj ks not-found] 58 | (impl/get-in* obj (mapv impl/wrap-key ks) not-found))) 59 | 60 | (defn ^boolean contains? 61 | "Returns true if `obj` contains `k`. 62 | 63 | ``` 64 | (j/contains? o :k) 65 | (j/contains? o .-k) 66 | ```" 67 | [obj k] 68 | (impl/contains?* obj (impl/wrap-key k))) 69 | 70 | (defn select-keys 71 | "Returns an object containing only those entries in `o` whose key is in `ks`. 72 | 73 | ``` 74 | (j/select-keys o [:a :b :c]) 75 | (j/select-keys o [.-a .-b .-c]) 76 | ```" 77 | [obj ks] 78 | (impl/select-keys* obj (mapv impl/wrap-key ks))) 79 | 80 | (deftype ^:no-doc JSLookup [obj] 81 | ILookup 82 | (-lookup [_ k] 83 | (j/get obj k)) 84 | (-lookup [_ k not-found] 85 | (j/get obj k not-found)) 86 | IDeref 87 | (-deref [o] obj)) 88 | 89 | (defn lookup 90 | "Wraps `obj` with an ILookup implementation, to support reading/destructuring. Does not support renamable keys. 91 | 92 | ``` 93 | (let [{:keys [a b c]} (j/lookup o)] 94 | ...) 95 | ```" 96 | [obj] 97 | (when obj 98 | (JSLookup. obj))) 99 | 100 | ;;;;;;;;;;;;;;;;;;;;;;;; 101 | ;; 102 | ;; Mutations 103 | 104 | (defn assoc! 105 | "Sets key-value pairs on `obj`, returns `obj`. 106 | 107 | ``` 108 | (j/assoc! o :x 10) 109 | (j/assoc! o .-x 10) 110 | ```" 111 | [obj & keyvals] 112 | (let [obj (if (some? obj) obj #js{})] 113 | (loop [[k v & kvs] keyvals] 114 | (unchecked-set obj k v) 115 | (if kvs 116 | (recur kvs) 117 | obj)))) 118 | 119 | (defn assoc-in! 120 | "Mutates the value in a nested object structure, where ks is a 121 | sequence of keys and v is the new value. If any levels do not 122 | exist, objects will be created. 123 | 124 | ``` 125 | (j/assoc-in! o [:x :y] 10) 126 | (j/assoc-in! o [.-x .-y] 10) 127 | ```" 128 | [obj ks v] 129 | (impl/assoc-in* obj (mapv impl/wrap-key ks) v)) 130 | 131 | (defn update! 132 | "'Updates' a value in a JavaScript object, where k is a key and 133 | f is a function that will take the old value and any supplied 134 | args and return the new value, which replaces the old value. 135 | If the key does not exist, nil is passed as the old value. 136 | 137 | ``` 138 | (j/update! o :a + 10) 139 | (j/update! o .-a + 10) 140 | ```" 141 | [obj k f & args] 142 | (let [obj (if (some? obj) obj #js{}) 143 | k* (impl/wrap-key k) 144 | v (core/apply f (core/unchecked-get obj k*) args)] 145 | (core/unchecked-set obj k* v) 146 | obj)) 147 | 148 | (defn update-in! 149 | "'Updates' a value in a nested object structure, where ks is a 150 | sequence of keys and f is a function that will take the old value 151 | and any supplied args and return the new value, mutating the 152 | nested structure. If any levels do not exist, objects will be 153 | created. 154 | 155 | ``` 156 | (j/update-in! o [:x :y] + 10) 157 | (j/update-in! o [.-x .-y] + 10) 158 | ```" 159 | [obj ks f & args] 160 | (impl/update-in* obj (mapv impl/wrap-key ks) f args)) 161 | 162 | (defn merge! 163 | "Extends `obj` with the properties of one or more objects, overwriting 164 | existing properties, moving left to right. Returns `obj`. 165 | An empty starting object is provided if `obj` is nil. 166 | ``` 167 | (j/extend! o other) 168 | (j/extend! o other #js{:x 1}) 169 | ``` 170 | Not IE6-friendly" 171 | ([obj] obj) 172 | ([obj x] 173 | (let [obj (j/some-or obj #js{})] 174 | (when (some? x) 175 | (doseq [k (js-keys x)] 176 | (unchecked-set obj k (unchecked-get x k)))) 177 | obj)) 178 | ([obj x & more] 179 | (reduce merge! (merge! obj x) more))) 180 | 181 | (defn extend! 182 | "alias for merge!" 183 | ([obj] obj) 184 | ([obj x] (merge! obj x)) 185 | ([obj x & more] 186 | (reduce merge! (merge! obj x) more))) 187 | 188 | (defn update-keys! [obj f] 189 | "Updates the keys of `obj` by applying `f` to each key. Returns `obj`. 190 | ``` 191 | (j/update-keys! o (partial str \"prefix-\")) 192 | ```" 193 | (when obj 194 | (doseq [k (js/Object.keys obj) 195 | :let [v (core/unchecked-get obj k)]] 196 | (js-delete obj k) 197 | (core/unchecked-set obj (f k) v)) 198 | obj)) 199 | 200 | (defn update-vals! [obj f] 201 | "Updates the values of `obj` by applying `f` to each value, iterating using js/Object.entries. Returns `obj`." 202 | (when obj 203 | (doseq [entry (js/Object.entries obj)] 204 | (j/let [^js [k v] entry] 205 | (core/unchecked-set obj k (f v)))) 206 | obj)) 207 | 208 | ;;;;;;;;;;;;;;;;;;;;;;;; 209 | ;; 210 | ;; Array operations 211 | 212 | (defn push! 213 | "Appends `v` to `array` and returns the mutated array. 214 | 215 | ``` 216 | (j/push! arr 10) 217 | ```" 218 | ([] #js[]) 219 | ([array] array) 220 | ([^js array x] 221 | (doto array 222 | (.push x)))) 223 | 224 | (defn unshift! 225 | "Prepends `v` to `a` and returns the mutated array. 226 | 227 | ``` 228 | (j/unshift! arr 10) 229 | ```" 230 | [^js array x] 231 | (doto array 232 | (.unshift x))) 233 | 234 | ;;;;;;;;;;;;;;;;;;;;;;;; 235 | ;; 236 | ;; Function operations 237 | 238 | (defn call 239 | "Call function `k` of `obj`, binding `this` to `obj`. 240 | 241 | ``` 242 | (j/call o :someFunction arg1 arg2) 243 | (j/call o .-someFunction arg1 arg2) 244 | ```" 245 | ([k] (fn 246 | ([obj] (.call (j/get obj k) obj)) 247 | ([obj & args] 248 | (.apply (j/get obj k) obj (to-array args))))) 249 | ([obj k & args] 250 | (.apply (j/get obj k) obj (to-array args)))) 251 | 252 | (defn apply 253 | "Apply function `k` of `obj`, binding `this` to `obj`. 254 | 255 | ``` 256 | (j/apply o :someFunction #js [arg1 arg2]) 257 | (j/apply o .-someFunction #js [arg1 arg2]) 258 | ```" 259 | [obj k arg-array] 260 | (.apply (j/get obj k) obj arg-array)) 261 | 262 | (defn call-in 263 | "Call function nested at `path` with `args`, binding `this` to its parent object. 264 | 265 | ``` 266 | (j/call-in o [:x :someFunction] arg1 arg2) 267 | ```" 268 | [obj ks & args] 269 | (impl/apply-in* obj (mapv impl/wrap-key ks) (to-array args))) 270 | 271 | (defn apply-in 272 | "Apply function nested at `path` with `arg-array`, binding `this` to its parent object. 273 | 274 | ``` 275 | (j/apply-in o [:x :someFunction] arg1 arg2) 276 | ```" 277 | [obj ks arg-array] 278 | (impl/apply-in* obj (mapv impl/wrap-key ks) arg-array)) 279 | 280 | ;;;;;;;;;;;;;;;;;;;;;;;; 281 | ;; 282 | ;; Object creation 283 | 284 | (defn obj 285 | "Create JavaScript object from an even number arguments representing 286 | interleaved keys and values. 287 | 288 | ``` 289 | (obj :a 1 :b 2 .-c 3 .-d 4) 290 | ```" 291 | [& keyvals] 292 | (let [obj (js-obj)] 293 | (doseq [[k v] (partition 2 keyvals)] 294 | (j/assoc! obj k v)) 295 | obj)) 296 | -------------------------------------------------------------------------------- /src/main/applied_science/js_interop/alpha.cljc: -------------------------------------------------------------------------------- 1 | (ns applied-science.js-interop.alpha 2 | (:require [applied-science.js-interop :as j] 3 | [applied-science.js-interop.destructure :as d]) 4 | #?(:cljs (:require-macros applied-science.js-interop.alpha))) 5 | 6 | ;; anything in this namespace is subject to change 7 | 8 | (defmacro js [& forms] 9 | (binding [d/*js?* true] 10 | `(do ~@(map (partial j/lit* {:env &env :deep? true}) forms)))) -------------------------------------------------------------------------------- /src/main/applied_science/js_interop/destructure.cljc: -------------------------------------------------------------------------------- 1 | (ns applied-science.js-interop.destructure 2 | (:refer-clojure :exclude [destructure]) 3 | (:require [clojure.string :as str] 4 | [clojure.core :as c] 5 | [clojure.spec.alpha :as s] 6 | [clojure.walk :as walk])) 7 | 8 | (defn- dequote [x] 9 | (if (and (list? x) (= 'quote (first x))) 10 | (second x) 11 | x)) 12 | 13 | (defn- dot-access? [x] 14 | (and (symbol? x) (str/starts-with? (name x) ".-"))) 15 | 16 | (defn- dot-access [s] 17 | (symbol (str/replace-first (name s) #"^(?:\.\-)?" ".-"))) 18 | 19 | (def ^:dynamic *js?* false) 20 | 21 | (defn tag-js [sym] 22 | (c/let [m (meta sym)] 23 | (cond-> sym 24 | (and (not (:clj m)) 25 | (not (:tag m))) 26 | (vary-meta assoc :tag 'js)))) 27 | 28 | (defn maybe-tag-js [x] 29 | (cond-> x *js?* tag-js)) 30 | 31 | (defn js-tag-all [expr] 32 | (walk/postwalk (c/fn [param] 33 | (cond-> param (symbol? param) tag-js)) 34 | expr)) 35 | 36 | (defn js-tag? [m] (or (:js m) (= 'js (:tag m)))) 37 | (defn clj-tag? [m] (or (:clj m) (= 'clj (:tag m)))) 38 | 39 | (c/defn destructure 40 | "Destructure with direct array and object access. 41 | 42 | Invoked via ^:js metadata on binding form: 43 | 44 | (let [^:js {:keys [a]} obj] ...) 45 | 46 | Keywords compile to static keys, symbols to renamable keys, 47 | and array access to `aget`." 48 | [bindings] 49 | ;; modified from cljs.core/destructure 50 | (binding [*js?* (or *js?* (js-tag? (meta bindings)))] 51 | (c/let [bents (partition 2 bindings) 52 | pb (c/fn pb [bvec b v] 53 | (let [b-meta (meta b) 54 | _ (assert (not (:js/shallow b-meta)) "Deprecated :js/shallow meta, use ^clj instead") 55 | js? (boolean (cond (clj-tag? b-meta) false 56 | (js-tag? b-meta) true 57 | *js?* true 58 | :else false))] 59 | (binding [*js?* js?] 60 | (c/let [pvec 61 | (c/fn [bvec b v] 62 | (c/let [gvec (gensym "vec__") 63 | gvec? (gensym "some_vec__") 64 | gseq (gensym "seq__") 65 | gfirst (gensym "first__") 66 | has-rest (some #{'&} b) 67 | clj-rest? (and has-rest (not js?)) 68 | get-nth (fn [n] 69 | (if js? 70 | `(when ~gvec? (aget ~gvec ~n)) 71 | `(nth ~gvec ~n nil))) 72 | get-rest (fn [n] 73 | (if js? 74 | `(some-> 75 | ~(with-meta gvec {:tag 'array}) 76 | (.slice ~n)) 77 | gseq))] 78 | (c/loop [ret (c/let [ret (cond-> (conj bvec gvec v) 79 | js? (conj gvec? `(some? ~gvec)))] 80 | (if clj-rest? 81 | (conj ret gseq (c/list `seq gvec)) 82 | ret)) 83 | n 0 84 | bs b 85 | seen-rest? false] 86 | (if (seq bs) 87 | (c/let [firstb (first bs)] 88 | (c/cond 89 | (= firstb '&) (recur (pb ret (second bs) (get-rest n)) 90 | n 91 | (nnext bs) 92 | true) 93 | (= firstb :as) (pb ret (maybe-tag-js (second bs)) gvec) 94 | :else (if seen-rest? 95 | (throw #?(:clj (new Exception "Unsupported binding form, only :as can follow & parameter") 96 | :cljs (new js/Error "Unsupported binding form, only :as can follow & parameter"))) 97 | (recur (pb (if clj-rest? 98 | (conj ret 99 | gfirst `(first ~gseq) 100 | gseq `(next ~gseq)) 101 | ret) 102 | (maybe-tag-js firstb) 103 | (if clj-rest? 104 | gfirst 105 | (get-nth n))) 106 | (c/inc n) 107 | (next bs) 108 | seen-rest?)))) 109 | ret)))) 110 | pmap 111 | (c/fn [bvec b v] 112 | (c/let [gmap (gensym "map__") 113 | defaults (:or b)] 114 | (c/loop [ret (c/-> bvec (conj gmap) (conj v) 115 | (conj gmap) (conj `(if (seq? ~gmap) (apply cljs.core/hash-map ~gmap) ~gmap)) 116 | ((c/fn [ret] 117 | (if (:as b) 118 | (conj ret (maybe-tag-js (:as b)) gmap) 119 | ret)))) 120 | bes (c/let [transforms 121 | (reduce 122 | (c/fn [transforms mk] 123 | (if (c/keyword? mk) 124 | (c/let [mkns (namespace mk) 125 | mkn (name mk)] 126 | (c/cond (= mkn "keys") (assoc transforms mk #(keyword (c/or mkns (namespace %)) (name %))) 127 | (= mkn "syms") (assoc transforms mk #(c/list `quote (symbol (c/or mkns (namespace %)) (name %)))) 128 | (= mkn "strs") (assoc transforms mk c/str) 129 | :else transforms)) 130 | transforms)) 131 | {} 132 | (keys b))] 133 | (reduce 134 | (c/fn [bes entry] 135 | (reduce #(assoc %1 %2 ((val entry) %2)) 136 | (dissoc bes (key entry)) 137 | ((key entry) bes))) 138 | (dissoc b :as :or) 139 | transforms))] 140 | (if (seq bes) 141 | (c/let [bb (key (first bes)) 142 | bk (val (first bes)) 143 | 144 | ;; convert renamable keys to .-dotFormat 145 | bk (let [k (dequote bk)] 146 | (if (and js? (symbol? k)) 147 | (dot-access k) 148 | bk)) 149 | ;; use js-interop for ^js-tagged bindings & other renamable keys 150 | getf (if js? 151 | 'applied-science.js-interop/get 152 | 'cljs.core/get) 153 | 154 | local (maybe-tag-js 155 | (if #?(:clj (c/instance? clojure.lang.Named bb) 156 | :cljs (cljs.core/implements? INamed bb)) 157 | (with-meta (symbol nil (name bb)) (meta bb)) 158 | bb)) 159 | bv (if (contains? defaults local) 160 | (c/list getf gmap bk (defaults local)) 161 | (c/list getf gmap bk))] 162 | (recur 163 | (if (c/or (c/keyword? bb) (c/symbol? bb)) ;(ident? bb) 164 | (c/-> ret (conj local bv)) 165 | (pb ret bb bv)) 166 | (next bes))) 167 | ret))))] 168 | (c/cond 169 | (c/symbol? b) (c/-> bvec (conj (if (namespace b) (symbol (name b)) b)) (conj v)) 170 | (c/keyword? b) (c/-> bvec (conj (symbol (name b))) (conj v)) 171 | (vector? b) (pvec bvec b v) 172 | (map? b) (pmap bvec b v) 173 | :else (throw 174 | #?(:clj (new Exception (c/str "Unsupported binding form: " b)) 175 | :cljs (new js/Error (c/str "Unsupported binding form: " b))))))))) 176 | process-entry (c/fn [bvec b] (pb bvec (first b) (second b)))] 177 | (->> (if (every? c/symbol? (map first bents)) 178 | bindings 179 | (c/if-let [kwbs (seq (filter #(c/keyword? (first %)) bents))] 180 | (throw 181 | #?(:clj (new Exception (c/str "Unsupported binding key: " (ffirst kwbs))) 182 | :cljs (new js/Error (c/str "Unsupported binding key: " (ffirst kwbs))))) 183 | (reduce process-entry [] bents))) 184 | (partition 2) 185 | (mapcat (if *js?* #_true ;; always tag these syms? 186 | (fn [[k v]] 187 | [(tag-js k) v]) 188 | identity)) 189 | vec)))) 190 | 191 | ;;;;;;;;;;;;;;;;;;;;;;;; 192 | ;; 193 | ;; Function argument parsing 194 | 195 | (s/def ::argv+body 196 | (s/cat :params (s/and 197 | vector? 198 | (s/conformer identity vec) 199 | (s/cat :params (s/* any?))) 200 | :body (s/alt :prepost+body (s/cat :prepost map? 201 | :body (s/+ any?)) 202 | :body (s/* any?)))) 203 | 204 | (s/def ::function-args 205 | (s/cat :fn-prelude (s/* #(and (not (vector? %)) (not (list? %)))) 206 | :fn-tail (s/alt :arity-1 ::argv+body 207 | :arity-n (s/cat :bodies (s/+ (s/spec ::argv+body)) 208 | :attr-map (s/? map?))))) 209 | 210 | (c/defn- spec-reform [spec args update-conf] 211 | (->> (s/conform spec args) 212 | (update-conf) 213 | (s/unform spec))) 214 | 215 | (c/defn- update-argv+body [update-fn {[arity] :fn-tail :as conf}] 216 | (let [update-pair 217 | (fn [conf] 218 | (let [body-path (cond-> [:body 1] 219 | (= :prepost+body (first (:body conf))) (conj :body)) 220 | [params body] (update-fn [(get-in conf [:params :params]) 221 | (get-in conf body-path)])] 222 | (-> conf 223 | (assoc-in [:params :params] params) 224 | (assoc-in body-path body))))] 225 | (case arity 226 | :arity-1 (update-in conf [:fn-tail 1] update-pair) 227 | :arity-n (update-in conf [:fn-tail 1 :bodies] #(mapv update-pair %))))) 228 | 229 | (c/defn- maybe-destructured 230 | [[params body]] 231 | (let [syms (into [] 232 | (take (count params)) 233 | (repeatedly gensym)) 234 | bindings (-> (interleave params syms) 235 | vec 236 | (with-meta (meta params)) 237 | destructure)] 238 | [syms 239 | `[(~'applied-science.js-interop/let ~bindings ~@body)]])) 240 | 241 | (c/defn destructure-fn-args [args] 242 | (spec-reform ::function-args args #(update-argv+body maybe-destructured %))) 243 | -------------------------------------------------------------------------------- /src/main/applied_science/js_interop/impl.cljs: -------------------------------------------------------------------------------- 1 | (ns applied-science.js-interop.impl 2 | (:require-macros [applied-science.js-interop :as j])) 3 | 4 | (defn wrap-key 5 | "Returns `k` or, if it is a keyword, its name." 6 | [k] 7 | (cond-> k 8 | (keyword? k) (name))) 9 | 10 | (defn ^boolean in?* [k* obj] 11 | (js-in k* obj)) 12 | 13 | (defn ^boolean contains?* [obj k*] 14 | (and (some? obj) 15 | (in?* k* obj))) 16 | 17 | (defn- get+! [o k*] 18 | (if-some [child-obj (unchecked-get o k*)] 19 | child-obj 20 | (unchecked-set o k* #js{}))) 21 | 22 | (defn- get-value-by-keys 23 | "Look up `ks` in `obj`, stopping at any nil" 24 | [obj ks*] 25 | (when obj 26 | (let [end (count ks*)] 27 | (loop [i 0 28 | obj obj] 29 | (if (or (= i end) 30 | (nil? obj)) 31 | obj 32 | (recur (inc i) 33 | (unchecked-get obj (nth ks* i)))))))) 34 | 35 | (defn get-in* 36 | ([obj ks*] 37 | (get-value-by-keys obj ks*)) 38 | ([obj ks* not-found] 39 | (if-some [last-obj (get-value-by-keys obj (butlast ks*))] 40 | (let [k (peek ks*)] 41 | (if (js-in k last-obj) 42 | (j/unchecked-get last-obj k) 43 | not-found)) 44 | not-found))) 45 | 46 | (defn select-keys* 47 | "Returns an object containing only those entries in `o` whose key is in `ks`" 48 | [obj ks*] 49 | (->> ks* 50 | (reduce (fn [m k] 51 | (cond-> m 52 | ^boolean (contains?* obj k) 53 | (doto 54 | (unchecked-set k 55 | (unchecked-get obj k))))) #js {}))) 56 | (defn assoc-in* 57 | [obj ks* v] 58 | (let [obj (j/some-or obj #js{}) 59 | inner-obj (reduce get+! obj (butlast ks*))] 60 | (unchecked-set inner-obj (peek ks*) v) 61 | obj)) 62 | 63 | (defn update-in* 64 | [obj ks* f args] 65 | (let [obj (j/some-or obj #js{}) 66 | last-k* (peek ks*) 67 | inner-obj (reduce get+! obj (butlast ks*)) 68 | old-val (unchecked-get inner-obj last-k*)] 69 | (unchecked-set inner-obj 70 | last-k* 71 | (apply f old-val args)) 72 | obj)) 73 | 74 | (defn apply-in* 75 | [obj ks* arg-array] 76 | (let [parent (get-in* obj (pop ks*)) 77 | f (unchecked-get parent (peek ks*))] 78 | (.apply f parent arg-array))) 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/applied_science/js_interop/inference.cljc: -------------------------------------------------------------------------------- 1 | (ns applied-science.js-interop.inference 2 | (:require [cljs.analyzer :as ana] 3 | [clojure.set :as set])) 4 | 5 | (defn normalize-tag [tag] 6 | (if (set? tag) 7 | (let [tags (into #{} (keep normalize-tag) tag)] 8 | (if (<= 1 (count tags)) (first tags) tags)) 9 | (when tag 10 | ('{"Array" array 11 | "String" string 12 | "Keyword" keyword} (name tag) tag)))) 13 | 14 | (defn as-set [x] (if (set? x) x #{x})) 15 | 16 | (defn within? [pred-tags tags] 17 | (set/superset? pred-tags (as-set tags))) 18 | 19 | (defn infer-tags 20 | "Infers type of expr" 21 | [env expr] 22 | (->> (ana/analyze env expr) 23 | ana/no-warn 24 | (ana/infer-tag env) 25 | (normalize-tag))) 26 | 27 | (defn tag-in? [env tags form] 28 | (when env 29 | (some->> (infer-tags env form) 30 | (within? tags)))) -------------------------------------------------------------------------------- /src/test/applied_science/js_interop_test.cljs: -------------------------------------------------------------------------------- 1 | (ns applied-science.js-interop-test 2 | (:require [applied-science.js-interop :as j] 3 | [applied-science.js-interop.alpha :refer [js]] 4 | [cljs.test :as test :refer [is 5 | are 6 | testing 7 | deftest]] 8 | [applied-science.js-interop.inference :as inf] 9 | [clojure.pprint :refer [pprint]] 10 | [clojure.string :as str] 11 | [goog.object :as gobj] 12 | [goog.reflect :as reflect])) 13 | 14 | (set! *warn-on-infer* true) 15 | 16 | (goog-define advanced? false) 17 | 18 | (def advanced-= (if advanced? = not=)) 19 | (def advanced-not= (if advanced? not= =)) 20 | 21 | (defn clj= [& args] 22 | (->> args 23 | (mapv #(js->clj % :keywordize-keys true)) 24 | (apply =))) 25 | 26 | (deftest js-interop 27 | 28 | (are [macro-expr fn-expr val] 29 | (clj= macro-expr fn-expr val) 30 | 31 | 32 | ;; get with nil 33 | (j/get nil :x) 34 | (apply j/get [nil :x]) 35 | nil 36 | 37 | ;;^same, but undefined 38 | (j/get js/undefined :x) 39 | (apply j/get [js/undefined :x]) 40 | nil 41 | 42 | ;; get with default 43 | (j/get nil :x 10) 44 | (apply j/get [nil :x 10]) 45 | 10 46 | 47 | ;; ^same but undefined 48 | (j/get js/undefined :x 10) 49 | (apply j/get [js/undefined :x 10]) 50 | 10 51 | 52 | ;; lookup semantics for default with nil-present 53 | (j/get #js{:x nil} :x 10) 54 | (apply j/get [#js{:x nil} :x 10]) 55 | nil 56 | 57 | ;; string - missing property 58 | (j/get "a" :x) 59 | (apply j/get ["a" :x]) 60 | js/undefined 61 | 62 | ;; string - missing property 63 | (j/get "a" :x :y) 64 | (apply j/get ["a" :x :y]) 65 | :y 66 | 67 | ;; string - existing property 68 | (j/get "a" :charAt) 69 | (apply j/get ["a" :charAt]) 70 | js/String.prototype.charAt 71 | 72 | ;; get-in, nil root 73 | (j/get-in nil [:x]) 74 | (apply j/get-in [nil [:x]]) 75 | nil 76 | 77 | ;; ^same but undefined 78 | (j/get-in js/undefined [:x]) 79 | (apply j/get-in [js/undefined [:x]]) 80 | nil 81 | 82 | ;; get-in, nil nested 83 | (j/get-in #js {:x nil} [:x :y]) 84 | (apply j/get-in [#js {:x nil} [:x :y]]) 85 | nil 86 | 87 | ;; ^same but undefined 88 | (j/get-in #js {:x js/undefined} [:x :y]) 89 | (apply j/get-in [#js {:x js/undefined} [:x :y]]) 90 | nil 91 | 92 | ;; get-in with default 93 | (j/get-in nil [:x] 10) 94 | (apply j/get-in [nil [:x] 10]) 95 | 10 96 | 97 | ;; get-in lookup semantics with nil-present 98 | (j/get-in #js{:x nil} [:x] 10) 99 | (apply j/get-in [#js{:x nil} [:x] 10]) 100 | nil 101 | 102 | (j/get-in #js {:x 10} [:x] 20) 103 | (apply j/get-in [#js {:x 10} [:x] 20]) 104 | 10 105 | 106 | ;; get-in multi-level 107 | (j/get-in #js {:x #js {:y 10}} [:x :y]) 108 | (apply j/get-in [#js {:x #js {:y 10}} [:x :y]]) 109 | 10 110 | 111 | ;; get-in multi-level 112 | (j/get-in #js {} [:x :y]) 113 | (apply j/get-in [#js {} [:x :y]]) 114 | nil 115 | 116 | ;; get-in with nested not-present 117 | (j/get-in #js {:x #js {}} [:x :y]) 118 | (apply j/get-in [#js {:x #js {}} [:x :y]]) 119 | nil 120 | 121 | ;; get-in with nested nil-present 122 | (j/get-in #js {:x #js {:y nil}} [:x :y] 10) 123 | (apply j/get-in [#js {:x #js {:y nil}} [:x :y] 10]) 124 | nil 125 | 126 | ;; get-in with array 127 | (j/get-in #js [#js {:x 10}] [0 :x]) 128 | (apply j/get-in [#js [#js {:x 10}] [0 :x]]) 129 | 10 130 | 131 | (j/contains? (j/obj :aaaaa 10) :aaaaa) 132 | (apply j/contains? (j/obj :aaaaa 10) [:aaaaa]) 133 | true 134 | 135 | (j/contains? (j/obj .-bbbbb 20) .-bbbbb) 136 | (j/contains? (j/obj .-bbbbb 20) .-bbbbb) 137 | true 138 | 139 | (j/assoc! #js{} :x 10) 140 | (apply j/assoc! #js{} [:x 10]) 141 | {:x 10} 142 | 143 | (j/assoc! nil :x 10 :y 20) 144 | (apply j/assoc! nil [:x 10 :y 20]) 145 | {:x 10 146 | :y 20} 147 | 148 | (j/unchecked-set #js{} :x 10 :y 20) 149 | (apply j/unchecked-set #js{} [:x 10 :y 20]) 150 | {:x 10 151 | :y 20} 152 | 153 | ;; assoc-in 154 | (j/assoc-in! #js {} [:x :y] 10) 155 | (apply j/assoc-in! [#js {} [:x :y] 10]) 156 | {:x {:y 10}} 157 | 158 | ;; assoc-in with nil 159 | (j/assoc-in! nil [:x :y] 10) 160 | (apply j/assoc-in! [nil [:x :y] 10]) 161 | {:x {:y 10}} 162 | 163 | ;; assoc-in with nested not-present 164 | (j/assoc-in! #js {:x #js {}} [:x :y] 10) 165 | (apply j/assoc-in! [#js {:x #js {}} [:x :y] 10]) 166 | {:x {:y 10}} 167 | 168 | ;; assoc-in with nested nil 169 | (j/assoc-in! #js {:x nil} [:x :y] 10) 170 | (apply j/assoc-in! [#js {:x nil} [:x :y] 10]) 171 | {:x {:y 10}} 172 | 173 | ;; assoc-in with nested nil 174 | (j/assoc-in! #js {:x #js {:y nil}} [:x :y] 10) 175 | (apply j/assoc-in! [#js {:x #js {:y nil}} [:x :y] 10]) 176 | {:x {:y 10}} 177 | 178 | ;; update with f 179 | (j/update! #js {:x 9} :x inc) 180 | (apply j/update! [#js {:x 9} :x inc]) 181 | {:x 10} 182 | 183 | ;; update with f and args 184 | (j/update! #js {:x 0} :x + 1 9) 185 | (apply j/update! [#js {:x 0} :x + 1 9]) 186 | {:x 10} 187 | 188 | ;; update an array 189 | (j/update! #js [10] 0 inc) 190 | (apply j/update! [#js [10] 0 inc]) 191 | [11] 192 | 193 | ;; update nil 194 | (j/update! nil :x (fnil inc 9)) 195 | (apply j/update! [nil :x (fnil inc 9)]) 196 | {:x 10} 197 | 198 | ;; update-in nil 199 | (j/update-in! nil [:x :y] (fnil inc 0)) 200 | (apply j/update-in! [nil [:x :y] (fnil inc 0)]) 201 | {:x {:y 1}} 202 | 203 | ;; update-in with args 204 | (j/update-in! nil [:x :y] (fnil + 0) 10) 205 | (apply j/update-in! [nil [:x :y] (fnil + 0) 10]) 206 | {:x {:y 10}} 207 | 208 | ;; update-in with args 209 | (j/update-in! #js {:x nil} [:x :y] (fnil + 0) 10) 210 | (apply j/update-in! [#js {:x nil} [:x :y] (fnil + 0) 10]) 211 | {:x {:y 10}} 212 | 213 | ;; update-in mutates provided object 214 | (j/update-in! #js {:x 0 215 | :y 9} [:y] inc) 216 | (apply j/update-in! [#js {:x 0 217 | :y 9} [:y] inc]) 218 | {:x 0 219 | :y 10} 220 | 221 | 222 | ;; lookup 223 | (let [{:keys [a b c]} (j/lookup #js {:a 1 224 | :b 2 225 | :c 3})] 226 | [a b c]) 227 | ((juxt :a :b :c) (apply j/lookup [#js {:a 1 228 | :b 2 229 | :c 3}])) 230 | [1 2 3] 231 | 232 | ;; lookup with nil 233 | (j/lookup nil) 234 | (apply j/lookup [nil]) 235 | nil 236 | 237 | ;; select-keys 238 | (j/select-keys #js {:x 10} [:x :y]) 239 | (apply j/select-keys [#js {:x 10} [:x :y]]) 240 | {:x 10} 241 | 242 | ;; select-keys with nil 243 | (j/select-keys nil [:x]) 244 | (apply j/select-keys [nil []]) 245 | {} 246 | 247 | 248 | ;; array ops 249 | 250 | (j/push! #js [0] 10) 251 | (apply j/push! [#js [0] 10]) 252 | [0 10] 253 | 254 | (j/unshift! #js [0] 10) 255 | (apply j/unshift! [#js [0] 10]) 256 | [10 0] 257 | 258 | (j/call #js [10] :indexOf 10) 259 | (apply j/call [#js [10] :indexOf 10]) 260 | 0 261 | 262 | (j/call #js [10] .-indexOf 10) 263 | (j/call #js [10] .-indexOf 10) 264 | 0 265 | 266 | (j/apply #js[10] :indexOf #js[10]) 267 | (apply j/apply [#js [10] :indexOf #js[10]]) 268 | 0 269 | 270 | (j/apply #js[10] .-indexOf #js[10]) 271 | (j/apply #js[10] .-indexOf #js[10]) 272 | 0) 273 | 274 | ;; added to check for warning 275 | (is (clj= ["x"] (j/push! @(atom #js[]) "x"))) 276 | 277 | (is (-> (j/assoc-in! #js {} [] 10) 278 | (j/get :null) 279 | (= 10)) 280 | "Same behaviour as Clojure for assoc-in with empty path. 281 | JavaScript coerces `nil` to the string 'null'.") 282 | 283 | (let [obj (j/assoc-in! nil [:x :y] 284 | (fn [x] 285 | (this-as this 286 | [x ;; variables are passed in 287 | (fn? (j/get this :y))])))] ;; `this` is set to parent 288 | 289 | 290 | (is (= (j/call-in obj [:x :y] 10) 291 | (apply j/call-in [obj [:x :y] 10]) 292 | [10 true]) 293 | "call-in") 294 | 295 | (is (= (j/apply-in obj [:x :y] #js[10]) 296 | (apply j/apply-in [obj [:x :y] #js[10]]) 297 | [10 true]) 298 | "apply-in")) 299 | 300 | (testing "Host interop keys" 301 | 302 | (testing "get" 303 | (let [obj #js{}] 304 | (set! (.-hostProperty obj) "x") 305 | 306 | (is (= (.-hostProperty obj) "x")) 307 | (is ((if advanced? not= =) 308 | (j/get obj :hostProperty) "x") 309 | "Unhinted object property is renamed under :advanced optimizations") 310 | (is (= (j/get obj .-hostProperty) 311 | "x")))) 312 | 313 | (testing "select-keys" 314 | (let [obj (-> #js{} 315 | (j/assoc! .-aaaaa 1 .-bbbbb 2 .-ccccc 3 :ddddd 4) 316 | (j/select-keys [.-aaaaa .-bbbbb :ddddd]))] 317 | 318 | (is (= 1 (j/get obj .-aaaaa))) 319 | (is (= 2 (j/get obj .-bbbbb))) 320 | (is (nil? (j/get obj .-ccccc))) 321 | (is (= 4 (j/get obj :ddddd))) 322 | 323 | (is (advanced-not= 1 (j/get obj :aaaaa))) 324 | (is (advanced-not= 2 (j/get obj :bbbbb))))) 325 | 326 | (testing "^js type hinting" 327 | (when advanced? 328 | (let [^js obj #js{}] 329 | (set! (.-hostProperty2 obj) "x") 330 | (is (= (j/get obj :hostProperty2) "x") 331 | "^js hint prevents renaming")))) 332 | 333 | (testing "paths" 334 | (let [obj #js{:x #js {:y "z"}}] 335 | 336 | (is (= (j/get-in obj [:x :y] "z"))) 337 | (j/assoc-in! obj [:x :y] "zz") 338 | (is (= (j/get-in obj [:x :y] "zz"))) 339 | 340 | (set! (.-aaaaa obj) 341 | (doto #js {} 342 | (-> .-bbbbb (set! "c")))) 343 | 344 | (is (= (j/get-in obj [.-aaaaa .-bbbbb] "c"))) 345 | 346 | (j/assoc-in! obj [.-aaaaa .-bbbbb] "cc") 347 | (is (= (j/get-in obj [.-aaaaa .-bbbbb] "cc"))) 348 | 349 | (j/assoc-in! obj [.-ddddd .-eeeee] "f") 350 | (is (= (j/get-in obj [.-ddddd .-eeeee]) "f")) 351 | 352 | (j/update-in! obj [.-ddddd .-eeeee] str "f") 353 | (is (= (j/get-in obj [.-ddddd .-eeeee]) "ff")) 354 | (is (advanced-not= (j/get-in obj [:ddddd :eeeee]) "ff")))) 355 | 356 | (testing "multiple types with same property name" 357 | (deftype A [someProperty]) 358 | (deftype B [someProperty]) 359 | (deftype C [someProperty]) 360 | 361 | (let [a (new A "x") 362 | b (new B "x") 363 | c (new C "x") 364 | d (doto #js{} 365 | (-> .-someProperty (set! "x")))] 366 | 367 | (is (= (reflect/objectProperty "someProperty" a) 368 | (reflect/objectProperty "someProperty" b) 369 | (reflect/objectProperty "someProperty" c) 370 | (reflect/objectProperty "someProperty" d)) 371 | "goog.reflect returns the same property key for different types") 372 | 373 | (is (= (j/get a .-someProperty) 374 | (j/get b .-someProperty) 375 | (j/get c .-someProperty) 376 | (j/get d .-someProperty) 377 | "x") 378 | "host-interop keys work across different types using the same keys"))) 379 | 380 | (testing "function operations with deftype" 381 | 382 | (deftype F [someArg] 383 | Object 384 | (someFunction [this s] [someArg s])) 385 | 386 | (let [F-instance (-> (new F "x") 387 | (j/assoc! :staticFunction identity))] 388 | 389 | (is (= (.someFunction F-instance "y") 390 | (j/call F-instance .-someFunction "y") 391 | (j/apply F-instance .-someFunction #js["y"]) 392 | ["x" "y"]) 393 | "host interop, j/call, and j/apply equivalence") 394 | 395 | (is (= (j/call F-instance :staticFunction "y") 396 | (apply j/call F-instance [:staticFunction "y"]) 397 | (j/apply F-instance :staticFunction #js["y"]) 398 | (apply j/apply F-instance [:staticFunction #js["y"]]) 399 | "y")) 400 | 401 | (when advanced? 402 | 403 | (is (nil? (j/get F-instance :someFunction)) 404 | "Property is renamed on `deftype`") 405 | 406 | (is (thrown? js/Error 407 | (= ["x" "y"] 408 | (j/call F-instance :someFunction "y"))) 409 | "advanced: j/call with keyword throws on renamable key") 410 | (is (thrown? js/Error 411 | (= ["x" "y"] 412 | (j/apply F-instance :someFunction #js["y"]))) 413 | "advanced: j/apply with keyword throws on renamable key")) 414 | 415 | (testing "nested function operations" 416 | 417 | (deftype G [someArg] 418 | Object 419 | (someFunction [this s] 420 | [someArg s])) 421 | 422 | (let [obj #js{:xxxxx #js{:yyyyy (-> (new G "x") 423 | (j/assoc! :staticFunction identity 424 | .-dynamicKey 999))}}] 425 | (testing "static function" 426 | (is (-> obj 427 | (j/get-in [:xxxxx :yyyyy]) 428 | (j/call :staticFunction 10) 429 | (= 10))) 430 | (is (-> obj 431 | (j/get-in [:xxxxx :yyyyy]) 432 | (j/apply :staticFunction #js[10]) 433 | (= 10)))) 434 | 435 | (testing "renamable method" 436 | (is (-> obj 437 | (j/get-in [:xxxxx :yyyyy]) 438 | (j/call .someFunction 10) 439 | (= ["x" 10])) 440 | "j/call, nested application") 441 | (is (-> obj 442 | (j/get-in [:xxxxx :yyyyy]) 443 | (j/apply .someFunction #js[10]) 444 | (= ["x" 10])) 445 | "j/apply, nested application")) 446 | 447 | (testing "renamable property" 448 | (is (-> obj 449 | (j/get-in [:xxxxx :yyyyy .-dynamicKey]) 450 | (= 999))) 451 | 452 | (is (-> obj 453 | (j/get-in [:xxxxx :yyyyy :dynamicKey]) 454 | (advanced-not= 999))))))))) 455 | 456 | (testing "function operations with deftype" 457 | 458 | (deftype H [someArg] 459 | Object 460 | (some_fn_H [this s] [someArg s]) 461 | (some_fn_HH [this s] [someArg s])) 462 | 463 | (let [h-inst (new H "x")] 464 | 465 | (is (= (.some_fn_H h-inst "y") 466 | ["x" "y"])) 467 | 468 | (is (= (j/call h-inst .-some_fn_H 10) 469 | ["x" 10]) 470 | "some_fn_H is not inlined by GCC") 471 | 472 | ;; this test represents _weird behaviour_, 473 | ;; GCC has inlined `some_fn_H` 474 | (let [property-name (reflect/objectProperty "some_fn_HH" h-inst) 475 | ^js/Function some_fn2 (gobj/get h-inst property-name)] 476 | 477 | (is (= (.some_fn_HH h-inst "y") 478 | ["x" "y"])) 479 | 480 | 481 | (is (= (.call some_fn2 h-inst 10) 482 | (if advanced? 483 | ["x" "y"] 484 | ["x" 10])) 485 | "some_fn_H is inlined by GCC") 486 | 487 | ;; either of the following two expressions can prevent this inlining. 488 | ;; sinkValue has the further effect of preventing DCE. 489 | #_(.-some_fn_HH h-inst) 490 | #_(reflect/sinkValue 491 | (.-some_fn_HH h-inst)) 492 | ))) 493 | 494 | (is (clj= (j/select-keys #js{:x 1 :y 2} (do [:x])) 495 | {:x 1}) 496 | "fallback to runtime parsing") 497 | 498 | ;; the following test fails before the code can even load 499 | #_(is (thrown? js/Error 500 | (j/select-keys nil (do [.-x])))) 501 | 502 | (testing "unchecked operations" 503 | 504 | (is (thrown? js/Error 505 | (j/unchecked-get nil :k))) 506 | 507 | (let [o #js{}] 508 | (j/unchecked-set o :x 1 .-yyyyy 2 .-zzzzz 3) 509 | 510 | (is (= (j/unchecked-get o :x) 511 | (j/get o :x) 512 | 1)) 513 | (is (= (j/unchecked-get o .-yyyyy) 2)) 514 | (is (= (j/get o .-zzzzz) 3))) 515 | 516 | (is (clj= (j/unchecked-set #js{} :x 10 :y 20) 517 | {:x 10 518 | :y 20})) 519 | 520 | (is (clj= (j/unchecked-set #js{} .-aaaaaa 10 .-bbbbbb 20) 521 | (j/obj .-aaaaaa 10 .-bbbbbb 20))) 522 | 523 | (testing "unchecked-get compiles directly to expected syntax" 524 | (is (= (macroexpand-1 '(applied-science.js-interop/unchecked-get o .-y)) 525 | '(.-y o))) 526 | 527 | (is (-> '(applied-science.js-interop/unchecked-set o .-y :value) 528 | (macroexpand-1) 529 | (flatten) 530 | (set) 531 | (contains? '.-y)) 532 | "unchecked-set uses host-interop syntax directly (GCC friendly)")) 533 | 534 | (is (= (j/!get-in #js{:a #js{:b 1}} [:a :b]) 535 | 1)) 536 | 537 | 538 | (is (thrown? js/Error 539 | (= (j/!get-in #js{} [:a :b]) 540 | 1))) 541 | 542 | ) 543 | 544 | (testing "object creation" 545 | 546 | (let [o (j/obj :aaaaa 1 547 | :bbbbb 2 548 | .-ccccc 3 549 | .-ddddd 4 550 | .-hello-there? 5)] 551 | 552 | (is (= [(j/get o :aaaaa) 553 | (j/get o :bbbbb) 554 | (j/get o .-ccccc) 555 | (j/get o .-ddddd) 556 | (j/get o .-hello-there?)] 557 | [1 2 3 4 5]))) 558 | 559 | 560 | 561 | (is (clj= (j/assoc! nil .-aaa 1 .-bbb? 2) 562 | (j/obj .-aaa 1 .-bbb? 2) 563 | (let [obj #js{}] 564 | (set! (.-aaa obj) 1) 565 | (set! (.-bbb? obj) 2) 566 | obj)) 567 | "dot keys") 568 | 569 | (is (clj= (j/assoc! nil .-aaa 1 :bbb 2) 570 | (j/obj .-aaa 1 :bbb 2)) 571 | "mixed keys") 572 | 573 | (is (clj= (j/assoc! nil :aaa 1 "bbb" 2) 574 | (j/obj :aaa 1 "bbb" 2)) 575 | "named keys") 576 | 577 | (is (clj= (j/assoc! nil :aaa (* 10 10)) 578 | (j/obj :aaa (* 10 10)) 579 | #js{:aaa 100})) 580 | 581 | (let [o2 (apply j/obj [:aaaaa 1 582 | :bbbbb 2])] 583 | 584 | (is (= [(j/get o2 :aaaaa) 585 | (j/get o2 :bbbbb)] 586 | [1 2]))) 587 | 588 | (testing "js-literal behaviour" 589 | (let [o #js {:yyyyyy 10 590 | "zzzzzz" 20}] 591 | (is (= (j/get o .-yyyyyy) (if advanced? nil 10))) 592 | (is (= (j/get o :yyyyyy) 10)) 593 | (is (= (j/get o .-zzzzzz) (if advanced? nil 20))) 594 | (is (= (j/get o :zzzzzz) 20))))) 595 | 596 | (testing "argument evaluation" 597 | (let [counter (atom 0)] 598 | (j/get (do (swap! counter inc) 599 | #js{:x 10}) 600 | :x) 601 | (j/get (do (swap! counter inc) 602 | #js{:x 10}) 603 | :x :not-found) 604 | (j/get-in (do (swap! counter inc) 605 | #js{:x 10}) 606 | [:x]) 607 | (j/assoc! (do (swap! counter inc) 608 | #js{:x 10}) :x 20) 609 | (j/assoc-in! (do (swap! counter inc) 610 | #js{:a #js{:b 0}}) [:a :b] 20) 611 | (j/update! (do (swap! counter inc) 612 | #js{:x 10}) :x inc) 613 | (j/update-in! (do (swap! counter inc) 614 | #js{:a #js{:b 0}}) [:a :b] inc) 615 | (j/select-keys (do (swap! counter inc) 616 | #js{:x 10}) 617 | [:x]) 618 | (j/call (do (swap! counter inc) 619 | #js{:x 10}) :hasOwnProperty "x") 620 | (j/apply (do (swap! counter inc) 621 | #js{:x 10}) :hasOwnProperty #js["x"]) 622 | 623 | (is (= @counter 10) 624 | "macros do not evaluate their obj argument more than once"))) 625 | 626 | (testing "extend!" 627 | 628 | ;; extend 629 | 630 | (is (clj= (j/extend! nil #js{:x 2}) 631 | {:x 2}) 632 | "extend `nil`") 633 | 634 | (is (clj= (j/extend! #js{:x 1} #js{:x 2}) 635 | {:x 2}) 636 | "extend two objects") 637 | 638 | (is (clj= (j/extend! #js{:x 1} #js{:y 2 :z 3} #js{:w 0}) 639 | {:w 0 :x 1 :y 2 :z 3}) 640 | "extend three objects") 641 | 642 | (is (clj= (j/extend! #js{:w 0} nil) 643 | {:w 0}) 644 | "extend with nil object")) 645 | 646 | (testing "lit" 647 | 648 | (is (object? (j/lit {}))) 649 | (is (object? (j/lit {:my-fn (fn [])}))) 650 | (is (fn? (j/get (j/lit {:my-fn (fn [])}) :my-fn))) 651 | (is (array? (j/lit []))) 652 | (is (object? (first (j/lit [{}])))) 653 | (is (array? (-> (j/lit [{:a [{:b []}]}]) 654 | (j/get-in [0 :a 0 :b])))) 655 | 656 | (is (clj= (j/lit [1 2 ~@(map inc [1 2 3 4])]) 657 | (j/lit [1 2 ~@(list 2 3 4 5)]) 658 | (j/lit [1 2 ~@[2 3 4 5]]) 659 | (j/lit [1 2 ~@[2 3 4 ~@[5]]]) 660 | [1 2 2 3 4 5]) 661 | "unquote-splice with lists and vectors") 662 | 663 | (let [more [3 4]] 664 | (is (clj= (j/lit {:x [1 2 ~@more]}) 665 | {:x [1 2 3 4]}) 666 | "unquote-splice nested in an object")) 667 | 668 | (is (clj= (j/lit [1 2 ~@#js[5 6]]) 669 | (j/lit [1 2 ~@(array 5 6)]) 670 | [1 2 5 6]) 671 | "unquote-splice with arrays") 672 | ) 673 | 674 | (testing "destructure" 675 | 676 | ;; records store their fields, so we can recognize them. 677 | ;; only defined fields use direct lookup. 678 | (defrecord Hello [record-field]) 679 | 680 | (is (= [:record-field nil] 681 | (j/let [^js {as-sym 'record-field 682 | as-key :record-field} (Hello. :record-field)] 683 | [as-sym as-key]))) 684 | 685 | (is (= [1 2] 686 | (j/let [{one :record-field} (Hello. 1) 687 | {two :record-field} (Hello. 2)] 688 | [one two]))) 689 | 690 | (is (= 10 691 | (j/let [^js {{{[_ _ n] :z} :y} :x} (j/lit {:x {:y {:z [0 5 10]}}})] 692 | n)) 693 | "Nested ^js") 694 | 695 | (is (= 10 696 | (j/let [^js {{^:clj {[_ _ n] :z} :y} :x} #js{:x #js {:y {:z [0 5 10]}}}] 697 | n)) 698 | "Nested ^js with internal ^:clj") 699 | 700 | (is (= 10 ((j/fn [^js {:keys [aaaaa]}] aaaaa) 701 | #js{:aaaaa 10})) 702 | "js-destructure with ^js") 703 | (is (= nil ((j/fn [{:keys [aaaaa]}] aaaaa) 704 | #js{:aaaaa 10})) 705 | "No js-destructure without ^js") 706 | (is (= nil ((j/fn [^js {:keys [aaaaa]}] aaaaa) 707 | {:aaaaa 10})) 708 | "js-destructure does not read from map") 709 | 710 | (j/defn multi-arity 711 | ([^js {:keys [aaaaa]}] 712 | aaaaa) 713 | ([{clj :aaaaa} ^js {js :aaaaa}] 714 | [clj js])) 715 | 716 | 717 | (let [obj #js{:aaaaa 10}] 718 | (and (is (= 10 (multi-arity obj)) 719 | "Multi-arity defn, js-destructure") 720 | (is (= [nil 10] (multi-arity obj obj)) 721 | "Multi-arity defn, dual-distructure"))) 722 | 723 | 724 | 725 | (is (= [10 nil] ((j/fn [^js [_ a b]] [a b]) 726 | #js[0 10])) 727 | "array js-destructure") 728 | (is (= 10 ((j/fn [[_ a]] a) 729 | #js[0 10])) 730 | "nth destructure") 731 | 732 | (is (clj= (j/let [[n1 n2 & more] #js[0 1 2 3 4]] 733 | [n1 n2 more]) 734 | [0 1 [2 3 4]]) 735 | "array destructure & rest") 736 | 737 | (is (= (j/let [^js [& more] nil] more) 738 | nil) 739 | "array destructure & rest") 740 | 741 | (j/let [^js {:keys [aaaaa]} #js{:aaaaa 10}] 742 | (is (= 10 aaaaa) 743 | "let js-destructure, static key")) 744 | 745 | (j/let [^js {:syms [aaaaa]} (j/obj .-aaaaa 10)] 746 | (is (= 10 aaaaa) 747 | "let js-destructure, renamable key")) 748 | 749 | (j/let [^js {:keys [aaaaa]} (j/obj .-aaaaa 10)] 750 | (is (advanced-not= 10 aaaaa) 751 | "let js-destructure, static key does not find renamed property")) 752 | 753 | (is (= [10 20 30 40] (j/let [a 10 b 20 ^js [c d] #js [30 40]] [a b c d])))) 754 | 755 | (testing "getter fns" 756 | (j/let [obj (j/lit {.-aaaaa 10 757 | :bbbbb 20 758 | "ccccc" 30 759 | :ddddd {.-eeeee 40}})] 760 | (is (= [10 20 30 40] 761 | ((juxt (j/get .-aaaaa) 762 | (j/get :bbbbb) 763 | (j/get "ccccc") 764 | (j/get-in [:ddddd .-eeeee])) obj)) 765 | "Getter functions")))) 766 | 767 | (deftest update-obj 768 | (testing "update-keys" 769 | (let [obj (j/obj :a 1 :b 2)] 770 | (is (clj= (j/update-keys! obj str/upper-case) 771 | {:A 1 :B 2})))) 772 | (testing "update-vals" 773 | (let [obj (j/obj :a 1 :b 2)] 774 | (is (clj= (j/update-vals! obj inc) 775 | {:a 2 :b 3}))))) 776 | 777 | 778 | (deftest conditionals 779 | (testing "if-let" 780 | (let [obj (j/obj :a 1)] 781 | (is (= 1 782 | (j/if-let [^js {:keys [a]} obj] 783 | a 784 | 2))) 785 | (is (= 1 786 | (j/when-let [^js {:keys [a]} obj] 787 | a))) 788 | (is (= 2 789 | (j/if-let [^js {:keys [a]} nil] 790 | a 791 | 2))) 792 | (is (nil? 793 | (j/when-let [^js {:keys [a]} nil] 794 | a)))))) 795 | 796 | (deftest js-mode 797 | (let [obj (j/lit {:a {:b {:c 10}}})] 798 | (js 799 | (is (= 10 800 | (j/get-in {:a {:b {:c 10}}} [:a :b :c]) 801 | (j/get-in obj [:a :b :c]))) 802 | (is (= 10 803 | ((fn [{{{c :c} :b} :a}] c) obj) 804 | (j/get-in obj [:a :b :c])))))) 805 | 806 | 807 | (comment 808 | (let [arr (rand-nth [#js[1 2 3 4]])] 809 | (simple-benchmark [] 810 | (j/let [^js [n1 n2 n3 n4] arr] (+ n1 n2 n3 n4)) 811 | 10000) 812 | (simple-benchmark [] 813 | (let [[n1 n2 n3 n4] arr] (+ n1 n2 n3 n4)) 814 | 10000) 815 | ;; [], (j/let [[n1 n2 n3 n4] arr] (+ n1 n2 n3 n4)), 10000 runs, 1 msecs 816 | ;; [], (let [[n1 n2 n3 n4] arr] (+ n1 n2 n3 n4)), 10000 runs, 6 msecs 817 | )) 818 | 819 | (comment 820 | ;; `case` is the ~same speed as looking up a key in an object 821 | (let [obj #js{:abc 10 :def 20 :ghi 30 :jkl 40 :mno 50}] 822 | (simple-benchmark [x (rand-nth [:abc :def :ghi :jkl :mno])] 823 | (j/!get obj x) 824 | 100000) 825 | (simple-benchmark [x (rand-nth [:abc :def :ghi :jkl :mno])] 826 | (case x 827 | :abc 10 828 | :def 20 829 | :ghi 30 830 | :jkl 40 831 | :mno 50) 832 | 100000) 833 | ;[x (rand-nth [:abc :def :ghi :jkl :mno])], (j/!get obj x), 100000 runs, 11 msecs 834 | ;[x (rand-nth [:abc :def :ghi :jkl :mno])], (case x :abc 10 :def 20 :ghi 30 :jkl 40 :mno 50), 100000 runs, 11 msecs 835 | 836 | )) 837 | 838 | (comment 839 | 840 | (def get-thing (fn [] (rand-nth [{} #js{}]))) 841 | ((fn [f] 842 | (simple-benchmark [thing (f)] 843 | (map? thing) 844 | 100000) 845 | (simple-benchmark [thing (f)] 846 | (object? thing) 847 | 100000)) get-thing) 848 | ;[x (rand-nth [:abc :def :ghi :jkl :mno])], (j/!get obj x), 100000 runs, 11 msecs 849 | ;[x (rand-nth [:abc :def :ghi :jkl :mno])], (case x :abc 10 :def 20 :ghi 30 :jkl 40 :mno 50), 100000 runs, 11 msecs 850 | 851 | ) 852 | 853 | (comment 854 | (defn ro [] (when ([true false] 1) #js{})) 855 | (j/infer-tags (ro)) 856 | (j/let [^js [n1 n2 & n3] nil] [n1 n2 n3]) 857 | (macroexpand '(j/let [^js [a] (take 1 (repeat "a"))]))) 858 | -------------------------------------------------------------------------------- /src/test/applied_science/js_interop_usage.cljc: -------------------------------------------------------------------------------- 1 | (ns applied-science.js-interop-usage 2 | (:require [applied-science.js-interop :as j]) 3 | #?(:cljs (:require-macros [applied-science.js-interop-usage :refer [wrap]]))) 4 | 5 | 6 | ;; sample operations, used to inspect generated code. 7 | ;; 8 | ;; to compile, run: 9 | ;; yarn usage 10 | ;; then inspect: 11 | ;; out/shadow-usage.js (:advanced compiled, with pseudo-names) 12 | 13 | (defn log [x] 14 | #?(:cljs 15 | (js/console.log x))) 16 | 17 | #?(:clj 18 | (defmacro wrap [label & body] 19 | `(do 20 | (log ~(str "------ " label " ------")) 21 | (log ~@body)))) 22 | 23 | #?(:cljs 24 | (do 25 | 26 | (set! *warn-on-infer* true) 27 | 28 | (def ^:export o #js{}) 29 | (def ^:export out #js{}) 30 | (def ^:export o2 (j/obj .-world 10)) 31 | 32 | (let [k (str "a" :b)] 33 | (wrap "!get without hint - check inference" 34 | (j/!get o k))) 35 | 36 | (wrap "!get without hint - inline expr" 37 | (j/!get o (str "a" :b))) 38 | 39 | (wrap "!get with kw" 40 | (let [k :some-key] 41 | (j/!get o k) 42 | )) 43 | 44 | (wrap "Array destructuring" 45 | (j/let [a #js[1 2 3 4 5] 46 | ^:js [a b c & xs] a] 47 | (js/console.log #js[b xs]))) 48 | 49 | (wrap "!get" 50 | ((fn [o2] 51 | #js [(j/!get o2 .-world) 52 | (.-world o2)]) 53 | o2)) 54 | 55 | (wrap "undefined" 56 | (let [o #js{}] 57 | (undefined? (j/!get o :whatever)))) 58 | 59 | (wrap "get-1" 60 | (j/get o :something)) 61 | 62 | (wrap "get-2" 63 | (j/get o .-something)) 64 | 65 | (wrap "get-3" 66 | (j/get o :something "default")) 67 | 68 | (wrap "get-4" 69 | (j/get o .-something "default")) 70 | 71 | (wrap "get-in-1" 72 | (j/get-in o [:something :more])) 73 | 74 | (wrap "get-in-2" 75 | (j/get-in o [.-something .-more])) 76 | 77 | (wrap "get-in-3" 78 | (j/get-in o [:something :more] "default")) 79 | 80 | (wrap "get-in-4 3" 81 | (j/get-in o [.-something .-more] "default")) 82 | 83 | (wrap "assoc-1" 84 | (j/assoc! o :something "value")) 85 | 86 | (wrap "assoc-2" 87 | (j/assoc! o .-something "value")) 88 | 89 | (wrap "assoc-in-1" 90 | (j/assoc-in! o [:something :more] "value")) 91 | 92 | (wrap "assoc-in-2" 93 | (j/assoc-in! o [.-something .-more] "value")) 94 | 95 | (wrap "update-1" 96 | (j/update! o :something str "--suffix")) 97 | 98 | (wrap "update-2" 99 | (j/update! o .-something str "--suffix")) 100 | 101 | (wrap "update-in-1" 102 | (j/update-in! o [:something :more] str "--suffix")) 103 | 104 | (wrap "update-in-2" 105 | (j/update-in! o [.-something .-more] str "value")) 106 | 107 | (wrap "select-keys-0 " 108 | (j/select-keys o [:not-existing])) 109 | 110 | (wrap "select-keys-1" 111 | (j/select-keys o [:something :more])) 112 | 113 | (wrap "select-keys-2" 114 | (j/select-keys o [.-something])) 115 | 116 | (wrap "getters-1" 117 | (j/get .-abracadabra)) 118 | 119 | (wrap "getters-2" 120 | (j/get :abracabra)) 121 | 122 | (wrap "deftype with fields" 123 | (do 124 | (deftype MyType [^:mutable currentThing] 125 | IDeref 126 | (-deref [o] currentThing) 127 | IReset 128 | (-reset! [o new-val] (set! (.-currentThing ^js o) new-val))) 129 | 130 | (let [my-instance (MyType. "the Thing")] 131 | @my-instance) 132 | ) 133 | 134 | ) 135 | 136 | 137 | (goog-define debug false) 138 | ;; sanity-check: both of the following are DCE'd, the ^boolean hint is unnecessary 139 | (when debug (js/console.log "CCC")) 140 | (when ^boolean debug (js/console.log "DDD")) 141 | 142 | )) 143 | -------------------------------------------------------------------------------- /src/test/test1.edn: -------------------------------------------------------------------------------- 1 | {:infer-externs true} -------------------------------------------------------------------------------- /src/test/test2.edn: -------------------------------------------------------------------------------- 1 | {:infer-externs true 2 | :optimizations :advanced 3 | :closure-defines {applied-science.js-interop-test/advanced? true}} -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asn1.js@^4.0.0: 6 | version "4.10.1" 7 | resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" 8 | integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== 9 | dependencies: 10 | bn.js "^4.0.0" 11 | inherits "^2.0.1" 12 | minimalistic-assert "^1.0.0" 13 | 14 | assert@^1.1.1: 15 | version "1.4.1" 16 | resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" 17 | integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= 18 | dependencies: 19 | util "0.10.3" 20 | 21 | base64-js@^1.0.2: 22 | version "1.3.0" 23 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" 24 | integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== 25 | 26 | bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: 27 | version "4.11.9" 28 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" 29 | integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== 30 | 31 | brorand@^1.0.1: 32 | version "1.1.0" 33 | resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" 34 | integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= 35 | 36 | browserify-aes@^1.0.0, browserify-aes@^1.0.4: 37 | version "1.2.0" 38 | resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" 39 | integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== 40 | dependencies: 41 | buffer-xor "^1.0.3" 42 | cipher-base "^1.0.0" 43 | create-hash "^1.1.0" 44 | evp_bytestokey "^1.0.3" 45 | inherits "^2.0.1" 46 | safe-buffer "^5.0.1" 47 | 48 | browserify-cipher@^1.0.0: 49 | version "1.0.1" 50 | resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" 51 | integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== 52 | dependencies: 53 | browserify-aes "^1.0.4" 54 | browserify-des "^1.0.0" 55 | evp_bytestokey "^1.0.0" 56 | 57 | browserify-des@^1.0.0: 58 | version "1.0.2" 59 | resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" 60 | integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== 61 | dependencies: 62 | cipher-base "^1.0.1" 63 | des.js "^1.0.0" 64 | inherits "^2.0.1" 65 | safe-buffer "^5.1.2" 66 | 67 | browserify-rsa@^4.0.0: 68 | version "4.0.1" 69 | resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" 70 | integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= 71 | dependencies: 72 | bn.js "^4.1.0" 73 | randombytes "^2.0.1" 74 | 75 | browserify-sign@^4.0.0: 76 | version "4.0.4" 77 | resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" 78 | integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= 79 | dependencies: 80 | bn.js "^4.1.1" 81 | browserify-rsa "^4.0.0" 82 | create-hash "^1.1.0" 83 | create-hmac "^1.1.2" 84 | elliptic "^6.0.0" 85 | inherits "^2.0.1" 86 | parse-asn1 "^5.0.0" 87 | 88 | browserify-zlib@^0.2.0: 89 | version "0.2.0" 90 | resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" 91 | integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== 92 | dependencies: 93 | pako "~1.0.5" 94 | 95 | buffer-from@^1.0.0: 96 | version "1.1.1" 97 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 98 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 99 | 100 | buffer-xor@^1.0.3: 101 | version "1.0.3" 102 | resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" 103 | integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= 104 | 105 | buffer@^4.3.0: 106 | version "4.9.1" 107 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" 108 | integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= 109 | dependencies: 110 | base64-js "^1.0.2" 111 | ieee754 "^1.1.4" 112 | isarray "^1.0.0" 113 | 114 | builtin-status-codes@^3.0.0: 115 | version "3.0.0" 116 | resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" 117 | integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= 118 | 119 | cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: 120 | version "1.0.4" 121 | resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" 122 | integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== 123 | dependencies: 124 | inherits "^2.0.1" 125 | safe-buffer "^5.0.1" 126 | 127 | console-browserify@^1.1.0: 128 | version "1.1.0" 129 | resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" 130 | integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= 131 | dependencies: 132 | date-now "^0.1.4" 133 | 134 | constants-browserify@^1.0.0: 135 | version "1.0.0" 136 | resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" 137 | integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= 138 | 139 | core-util-is@~1.0.0: 140 | version "1.0.2" 141 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 142 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 143 | 144 | create-ecdh@^4.0.0: 145 | version "4.0.3" 146 | resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" 147 | integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== 148 | dependencies: 149 | bn.js "^4.1.0" 150 | elliptic "^6.0.0" 151 | 152 | create-hash@^1.1.0, create-hash@^1.1.2: 153 | version "1.2.0" 154 | resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" 155 | integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== 156 | dependencies: 157 | cipher-base "^1.0.1" 158 | inherits "^2.0.1" 159 | md5.js "^1.3.4" 160 | ripemd160 "^2.0.1" 161 | sha.js "^2.4.0" 162 | 163 | create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: 164 | version "1.1.7" 165 | resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" 166 | integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== 167 | dependencies: 168 | cipher-base "^1.0.3" 169 | create-hash "^1.1.0" 170 | inherits "^2.0.1" 171 | ripemd160 "^2.0.0" 172 | safe-buffer "^5.0.1" 173 | sha.js "^2.4.8" 174 | 175 | crypto-browserify@^3.11.0: 176 | version "3.12.0" 177 | resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" 178 | integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== 179 | dependencies: 180 | browserify-cipher "^1.0.0" 181 | browserify-sign "^4.0.0" 182 | create-ecdh "^4.0.0" 183 | create-hash "^1.1.0" 184 | create-hmac "^1.1.0" 185 | diffie-hellman "^5.0.0" 186 | inherits "^2.0.1" 187 | pbkdf2 "^3.0.3" 188 | public-encrypt "^4.0.0" 189 | randombytes "^2.0.0" 190 | randomfill "^1.0.3" 191 | 192 | date-now@^0.1.4: 193 | version "0.1.4" 194 | resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" 195 | integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= 196 | 197 | des.js@^1.0.0: 198 | version "1.0.0" 199 | resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" 200 | integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw= 201 | dependencies: 202 | inherits "^2.0.1" 203 | minimalistic-assert "^1.0.0" 204 | 205 | diffie-hellman@^5.0.0: 206 | version "5.0.3" 207 | resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" 208 | integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== 209 | dependencies: 210 | bn.js "^4.1.0" 211 | miller-rabin "^4.0.0" 212 | randombytes "^2.0.0" 213 | 214 | domain-browser@^1.1.1: 215 | version "1.2.0" 216 | resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" 217 | integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== 218 | 219 | elliptic@^6.0.0: 220 | version "6.5.3" 221 | resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" 222 | integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== 223 | dependencies: 224 | bn.js "^4.4.0" 225 | brorand "^1.0.1" 226 | hash.js "^1.0.0" 227 | hmac-drbg "^1.0.0" 228 | inherits "^2.0.1" 229 | minimalistic-assert "^1.0.0" 230 | minimalistic-crypto-utils "^1.0.0" 231 | 232 | events@^3.0.0: 233 | version "3.0.0" 234 | resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" 235 | integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== 236 | 237 | evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: 238 | version "1.0.3" 239 | resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" 240 | integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== 241 | dependencies: 242 | md5.js "^1.3.4" 243 | safe-buffer "^5.1.1" 244 | 245 | hash-base@^3.0.0: 246 | version "3.0.4" 247 | resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" 248 | integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= 249 | dependencies: 250 | inherits "^2.0.1" 251 | safe-buffer "^5.0.1" 252 | 253 | hash.js@^1.0.0, hash.js@^1.0.3: 254 | version "1.1.7" 255 | resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" 256 | integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== 257 | dependencies: 258 | inherits "^2.0.3" 259 | minimalistic-assert "^1.0.1" 260 | 261 | hmac-drbg@^1.0.0: 262 | version "1.0.1" 263 | resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" 264 | integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= 265 | dependencies: 266 | hash.js "^1.0.3" 267 | minimalistic-assert "^1.0.0" 268 | minimalistic-crypto-utils "^1.0.1" 269 | 270 | https-browserify@^1.0.0: 271 | version "1.0.0" 272 | resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" 273 | integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= 274 | 275 | ieee754@^1.1.4: 276 | version "1.1.12" 277 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" 278 | integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== 279 | 280 | inherits@2.0.1: 281 | version "2.0.1" 282 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" 283 | integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= 284 | 285 | inherits@2.0.3: 286 | version "2.0.3" 287 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 288 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 289 | 290 | inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: 291 | version "2.0.4" 292 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 293 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 294 | 295 | isarray@^1.0.0, isarray@~1.0.0: 296 | version "1.0.0" 297 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 298 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 299 | 300 | isexe@^2.0.0: 301 | version "2.0.0" 302 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 303 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 304 | 305 | md5.js@^1.3.4: 306 | version "1.3.5" 307 | resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" 308 | integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== 309 | dependencies: 310 | hash-base "^3.0.0" 311 | inherits "^2.0.1" 312 | safe-buffer "^5.1.2" 313 | 314 | miller-rabin@^4.0.0: 315 | version "4.0.1" 316 | resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" 317 | integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== 318 | dependencies: 319 | bn.js "^4.0.0" 320 | brorand "^1.0.1" 321 | 322 | minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: 323 | version "1.0.1" 324 | resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" 325 | integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== 326 | 327 | minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: 328 | version "1.0.1" 329 | resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" 330 | integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= 331 | 332 | node-libs-browser@^2.2.1: 333 | version "2.2.1" 334 | resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" 335 | integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== 336 | dependencies: 337 | assert "^1.1.1" 338 | browserify-zlib "^0.2.0" 339 | buffer "^4.3.0" 340 | console-browserify "^1.1.0" 341 | constants-browserify "^1.0.0" 342 | crypto-browserify "^3.11.0" 343 | domain-browser "^1.1.1" 344 | events "^3.0.0" 345 | https-browserify "^1.0.0" 346 | os-browserify "^0.3.0" 347 | path-browserify "0.0.1" 348 | process "^0.11.10" 349 | punycode "^1.2.4" 350 | querystring-es3 "^0.2.0" 351 | readable-stream "^2.3.3" 352 | stream-browserify "^2.0.1" 353 | stream-http "^2.7.2" 354 | string_decoder "^1.0.0" 355 | timers-browserify "^2.0.4" 356 | tty-browserify "0.0.0" 357 | url "^0.11.0" 358 | util "^0.11.0" 359 | vm-browserify "^1.0.1" 360 | 361 | os-browserify@^0.3.0: 362 | version "0.3.0" 363 | resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" 364 | integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= 365 | 366 | pako@~1.0.5: 367 | version "1.0.8" 368 | resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.8.tgz#6844890aab9c635af868ad5fecc62e8acbba3ea4" 369 | integrity sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA== 370 | 371 | parse-asn1@^5.0.0: 372 | version "5.1.4" 373 | resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc" 374 | integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw== 375 | dependencies: 376 | asn1.js "^4.0.0" 377 | browserify-aes "^1.0.0" 378 | create-hash "^1.1.0" 379 | evp_bytestokey "^1.0.0" 380 | pbkdf2 "^3.0.3" 381 | safe-buffer "^5.1.1" 382 | 383 | path-browserify@0.0.1: 384 | version "0.0.1" 385 | resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" 386 | integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== 387 | 388 | pbkdf2@^3.0.3: 389 | version "3.0.17" 390 | resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" 391 | integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== 392 | dependencies: 393 | create-hash "^1.1.2" 394 | create-hmac "^1.1.4" 395 | ripemd160 "^2.0.1" 396 | safe-buffer "^5.0.1" 397 | sha.js "^2.4.8" 398 | 399 | process-nextick-args@~2.0.0: 400 | version "2.0.0" 401 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" 402 | integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== 403 | 404 | process@^0.11.10: 405 | version "0.11.10" 406 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 407 | integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= 408 | 409 | public-encrypt@^4.0.0: 410 | version "4.0.3" 411 | resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" 412 | integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== 413 | dependencies: 414 | bn.js "^4.1.0" 415 | browserify-rsa "^4.0.0" 416 | create-hash "^1.1.0" 417 | parse-asn1 "^5.0.0" 418 | randombytes "^2.0.1" 419 | safe-buffer "^5.1.2" 420 | 421 | punycode@1.3.2: 422 | version "1.3.2" 423 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" 424 | integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= 425 | 426 | punycode@^1.2.4: 427 | version "1.4.1" 428 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 429 | integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= 430 | 431 | querystring-es3@^0.2.0: 432 | version "0.2.1" 433 | resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" 434 | integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= 435 | 436 | querystring@0.2.0: 437 | version "0.2.0" 438 | resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" 439 | integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= 440 | 441 | randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: 442 | version "2.1.0" 443 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 444 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 445 | dependencies: 446 | safe-buffer "^5.1.0" 447 | 448 | randomfill@^1.0.3: 449 | version "1.0.4" 450 | resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" 451 | integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== 452 | dependencies: 453 | randombytes "^2.0.5" 454 | safe-buffer "^5.1.0" 455 | 456 | readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6: 457 | version "2.3.6" 458 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" 459 | integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== 460 | dependencies: 461 | core-util-is "~1.0.0" 462 | inherits "~2.0.3" 463 | isarray "~1.0.0" 464 | process-nextick-args "~2.0.0" 465 | safe-buffer "~5.1.1" 466 | string_decoder "~1.1.1" 467 | util-deprecate "~1.0.1" 468 | 469 | readline-sync@^1.4.7: 470 | version "1.4.9" 471 | resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.9.tgz#3eda8e65f23cd2a17e61301b1f0003396af5ecda" 472 | integrity sha1-PtqOZfI80qF+YTAbHwADOWr17No= 473 | 474 | ripemd160@^2.0.0, ripemd160@^2.0.1: 475 | version "2.0.2" 476 | resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" 477 | integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== 478 | dependencies: 479 | hash-base "^3.0.0" 480 | inherits "^2.0.1" 481 | 482 | safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: 483 | version "5.1.2" 484 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 485 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 486 | 487 | setimmediate@^1.0.4: 488 | version "1.0.5" 489 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 490 | integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= 491 | 492 | sha.js@^2.4.0, sha.js@^2.4.8: 493 | version "2.4.11" 494 | resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" 495 | integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== 496 | dependencies: 497 | inherits "^2.0.1" 498 | safe-buffer "^5.0.1" 499 | 500 | shadow-cljs-jar@1.3.4: 501 | version "1.3.4" 502 | resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.4.tgz#0939d91c468b4bc5eab5a958f79e7ef5696fdf62" 503 | integrity sha512-cZB2pzVXBnhpJ6PQdsjO+j/MksR28mv4QD/hP/2y1fsIa9Z9RutYgh3N34FZ8Ktl4puAXaIGlct+gMCJ5BmwmA== 504 | 505 | shadow-cljs@^2.22.8: 506 | version "2.22.8" 507 | resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.22.8.tgz#b96d62efd79a207fd93ab634d8cf9274daebd2b9" 508 | integrity sha512-EqVbe1bareIw7cYsYeiAxQzGltasXYYsz/i4oy7G9EYLL5RwY1k3UNRw9evNnW3ZAYQxH7xCqizPMflHTpexAw== 509 | dependencies: 510 | node-libs-browser "^2.2.1" 511 | readline-sync "^1.4.7" 512 | shadow-cljs-jar "1.3.4" 513 | source-map-support "^0.4.15" 514 | which "^1.3.1" 515 | ws "^7.4.6" 516 | 517 | source-map-support@^0.4.15: 518 | version "0.4.18" 519 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" 520 | integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== 521 | dependencies: 522 | source-map "^0.5.6" 523 | 524 | source-map-support@^0.5.10: 525 | version "0.5.10" 526 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" 527 | integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== 528 | dependencies: 529 | buffer-from "^1.0.0" 530 | source-map "^0.6.0" 531 | 532 | source-map@^0.5.6: 533 | version "0.5.7" 534 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 535 | integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= 536 | 537 | source-map@^0.6.0: 538 | version "0.6.1" 539 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 540 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 541 | 542 | stream-browserify@^2.0.1: 543 | version "2.0.2" 544 | resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" 545 | integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== 546 | dependencies: 547 | inherits "~2.0.1" 548 | readable-stream "^2.0.2" 549 | 550 | stream-http@^2.7.2: 551 | version "2.8.3" 552 | resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" 553 | integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== 554 | dependencies: 555 | builtin-status-codes "^3.0.0" 556 | inherits "^2.0.1" 557 | readable-stream "^2.3.6" 558 | to-arraybuffer "^1.0.0" 559 | xtend "^4.0.0" 560 | 561 | string_decoder@^1.0.0: 562 | version "1.2.0" 563 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" 564 | integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== 565 | dependencies: 566 | safe-buffer "~5.1.0" 567 | 568 | string_decoder@~1.1.1: 569 | version "1.1.1" 570 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 571 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 572 | dependencies: 573 | safe-buffer "~5.1.0" 574 | 575 | timers-browserify@^2.0.4: 576 | version "2.0.10" 577 | resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" 578 | integrity sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg== 579 | dependencies: 580 | setimmediate "^1.0.4" 581 | 582 | to-arraybuffer@^1.0.0: 583 | version "1.0.1" 584 | resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" 585 | integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= 586 | 587 | tty-browserify@0.0.0: 588 | version "0.0.0" 589 | resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" 590 | integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= 591 | 592 | url@^0.11.0: 593 | version "0.11.0" 594 | resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" 595 | integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= 596 | dependencies: 597 | punycode "1.3.2" 598 | querystring "0.2.0" 599 | 600 | util-deprecate@~1.0.1: 601 | version "1.0.2" 602 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 603 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 604 | 605 | util@0.10.3: 606 | version "0.10.3" 607 | resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" 608 | integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= 609 | dependencies: 610 | inherits "2.0.1" 611 | 612 | util@^0.11.0: 613 | version "0.11.1" 614 | resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" 615 | integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== 616 | dependencies: 617 | inherits "2.0.3" 618 | 619 | vm-browserify@^1.0.1: 620 | version "1.1.2" 621 | resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" 622 | integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== 623 | 624 | which@^1.3.1: 625 | version "1.3.1" 626 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 627 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 628 | dependencies: 629 | isexe "^2.0.0" 630 | 631 | ws@^7.4.6: 632 | version "7.5.9" 633 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" 634 | integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== 635 | 636 | xtend@^4.0.0: 637 | version "4.0.1" 638 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 639 | integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= 640 | --------------------------------------------------------------------------------