├── .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  [](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 |
--------------------------------------------------------------------------------