├── examples ├── .gitignore ├── tsconfig.json ├── malli-ts.ts_gen-types.d.ts ├── malli-interop.ts └── malli-ts.ts_gen-types.js ├── test └── malli_ts │ ├── ast_test.cljc │ ├── core_test.cljc │ └── data_mapping_test.cljs ├── README.md ├── project.clj ├── deps.edn ├── shadow-cljs.edn ├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── src └── malli_ts │ ├── test.clj │ ├── core_schema.cljc │ ├── ts.cljs │ ├── data_mapping.cljs │ ├── data_mapping │ ├── to_clj.cljs │ └── to_js.cljs │ ├── ast.cljc │ └── core.cljc └── development └── malli_ts └── dev └── experiments ├── exp2_schema_options.cljc ├── exp1.cljs └── exp2_external_types.cljc /examples/.gitignore: -------------------------------------------------------------------------------- 1 | out/* -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | // tsconfig.json 2 | { 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "*": [ 7 | "../node_modules/*", 8 | ] 9 | }, 10 | "outDir": "out/" 11 | } 12 | } -------------------------------------------------------------------------------- /test/malli_ts/ast_test.cljc: -------------------------------------------------------------------------------- 1 | (ns malli-ts.ast-test 2 | (:require #?(:clj [clojure.test :refer [deftest is testing]] 3 | :cljs [cljs.test :refer-macros [deftest is testing]]) 4 | [malli-ts.ast :refer [->ast]])) 5 | 6 | (deftest number 7 | (is (= :number (get (->ast int?) :type))) 8 | (is (= :number (get (->ast int?) :type))) 9 | (is (= :number (get (->ast int?) :type)))) 10 | 11 | 12 | (comment 13 | (testing "primitives" 14 | (number))) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # malli-ts 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/org.clojars.flowyourmoney/malli-ts.svg)](https://clojars.org/org.clojars.flowyourmoney/malli-ts) 4 | 5 | A library for generating TS type definitions from malli schemas 6 | 7 | ## FAQ 8 | 9 | ### Can I get an example? 10 | 11 | We're working on providing a comprehensive guide + documentation, but as of today, your best chance is reading the [`malli-ts.core-test`](./test/malli_ts/core_test.cljc). 12 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject org.clojars.flowyourmoney/malli-ts "0.3.0-SNAPSHOT" 2 | :description "A library for generating TS type definitions from malli schemas" 3 | :url "https://flowyour.money/" 4 | 5 | :license {:name "MIT" 6 | :url "http://www.opensource.org/licenses/mit-license.php"} 7 | 8 | :dependencies 9 | [[org.clojure/clojure "1.11.1"] 10 | [metosin/malli "0.9.2"] 11 | [camel-snake-kebab "0.4.2"]] 12 | 13 | :source-paths 14 | ["src"] 15 | 16 | :repositories 17 | {"clojars" {:url "https://clojars.org/repo" 18 | :sign-releases false}}) 19 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps 2 | {metosin/malli {:mvn/version "0.9.2"} 3 | camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.2"} 4 | cljs-bean/cljs-bean {:git/url "https://github.com/flowyourmoney/cljs-bean.git" 5 | :git/sha "0c4a2ceb5dac86fac716558d804a0cd3bbea2e4c"}} 6 | 7 | :paths ["src" "test"] 8 | 9 | :aliases {:shadow-cljs {:extra-deps {thheller/shadow-cljs {:mvn/version "2.20.11"}} 10 | :main-opts ["-m" "shadow.cljs.devtools.cli"]} 11 | :dev {:extra-paths ["development"]}}} 12 | -------------------------------------------------------------------------------- /examples/malli-ts.ts_gen-types.d.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from 'crypto'; 2 | 3 | /** 4 | * @schema [:=> [:catn [:s string?]] any?] 5 | */ 6 | export var k: (s:string) => any; 7 | /** 8 | * @schema [:=> [:catn [:s string?]] any?] 9 | */ 10 | export var sym: (s:string) => any; 11 | /** 12 | * @schema [:=> [:catn [:o any?]] any?] 13 | */ 14 | export var toClj: (o:any) => any; 15 | /** 16 | * @schema [:=> [:catn [:schema any?] [:val any?]] any?] 17 | */ 18 | export var validate: (schema:any, val:any) => any; 19 | /** 20 | * @schema [:=> :cat [any? {:external-type {:t-name "Date", :t-path nil, :t-alias nil}}]] 21 | */ 22 | export var now: () => Date; 23 | /** 24 | * @schema [:=> [:catn [:s string?]] :crypto/hash] 25 | */ 26 | export var toSha256: (s:string) => crypto.Hash; -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:deps {:aliases [:shadow-cljs]} 2 | 3 | :builds 4 | {:dev 5 | {:target :node-library 6 | :output-to "out/dev.js" 7 | :exports {}} 8 | 9 | :test 10 | {:target :node-test 11 | :output-to "out/test.js" 12 | :ns-regexp "test$" 13 | :test-dir "test" 14 | :autorun true 15 | :compiler-options {:infer-externs false}} 16 | 17 | :malli-ts.ts 18 | {:target :node-library 19 | :output-to "node_modules/malli-ts.ts.js" 20 | :exports-fn malli-ts.ts/exports-fn} 21 | 22 | :malli-ts.ts_gen-types 23 | {:target :node-script 24 | :output-to "examples/malli-ts.ts_gen-types.js" 25 | :main malli-ts.ts/gen-types 26 | :autorun true}}} 27 | 28 | ;; (do (shadow/watch :dev) (shadow/repl :dev)) 29 | 30 | -------------------------------------------------------------------------------- /examples/malli-interop.ts: -------------------------------------------------------------------------------- 1 | import { k, toClj, sym, validate, toSha256 } from "./malli-ts.ts_gen-types"; 2 | 3 | console.log("-- toClj({a: 1, b: \"hello\"}) --") 4 | console.log(toClj({a: 1, b: "hello"})) 5 | 6 | console.log("-- VALIDATION TEST --") 7 | 8 | const schema = toClj([ 9 | k("map"), 10 | [k("name"), sym("string?")], 11 | [k("age"), sym("int?")], 12 | [k("passwordHash"), sym("string")] 13 | ]) 14 | 15 | type Person = { 16 | name: string; 17 | age: number; 18 | passwordHash: string 19 | } 20 | 21 | const tiago: Person = { 22 | name: "Tiago", 23 | age: 21, 24 | passwordHash: toSha256("verystrongpassword").digest('hex') } 25 | 26 | const val = toClj(tiago) 27 | console.log("schema: ", schema) 28 | console.log("val: ", val.obj) 29 | console.log(validate(schema, val)) // true -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push] 3 | jobs: 4 | clojure: 5 | runs-on: ubuntu-latest 6 | 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v3 10 | 11 | - name: Cache Clojure Dependencies 12 | uses: actions/cache@v3 13 | with: 14 | path: | 15 | ~/.m2 16 | ~/.gitlibs 17 | key: cache-${{ hashFiles('**/deps.edn') }} 18 | 19 | - name: Prepare java 20 | uses: actions/setup-java@v3.5.1 21 | with: 22 | distribution: 'temurin' 23 | java-version: '17' 24 | 25 | - name: Install clojure tools 26 | uses: DeLaGuardo/setup-clojure@9.5 27 | with: 28 | cli: latest 29 | lein: 2.9.8 30 | 31 | - name: Run Unit tests (Clojure) 32 | run: lein test 33 | 34 | - name: Run Unit tests (ClojureScript) 35 | run: clojure -M:shadow-cljs compile test 36 | 37 | - name: Run Unit tests (ClojureScript - Advanced compilation) 38 | run: clojure -M:shadow-cljs release test 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | 3 | out 4 | secrets 5 | .clj-kondo 6 | .calva 7 | .lsp 8 | 9 | # clojure 10 | 11 | pom.xml 12 | pom.xml.asc 13 | *.jar 14 | *.class 15 | /lib/ 16 | /classes/ 17 | /target/ 18 | /checkouts/ 19 | .lein-deps-sum 20 | .lein-repl-history 21 | .lein-plugins/ 22 | .lein-failures 23 | .nrepl-port 24 | .cpcache/ 25 | 26 | # clojurescript 27 | 28 | *~ 29 | .idea 30 | /.DS_Store 31 | .DS_Store 32 | /classes 33 | /lib 34 | /target 35 | closure 36 | /core.js 37 | /coreadvanced.js 38 | /coresimple.js 39 | /out 40 | *out 41 | .lein* 42 | /pom.xml 43 | *.iml 44 | .repl* 45 | *.swp 46 | *.zip 47 | clojurescript_release_* 48 | closure-release-* 49 | .lein-repl-history 50 | .nrepl-port 51 | .nrepl-repl-history 52 | builds 53 | .cljs* 54 | node_modules 55 | nashorn_code_cache 56 | src/main/cljs/cljs/core.aot.js 57 | src/main/cljs/cljs/core.aot.js.map 58 | src/main/cljs/cljs/core.cljs.cache.aot.edn 59 | src/main/cljs/cljs/core.cljs.cache.aot.json 60 | .node_repl 61 | package.json 62 | package-lock.json 63 | .cpcache 64 | resources/brepl_client.js 65 | .shadow-cljs 66 | *.clojars-token 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Flow Your Money 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/malli_ts/test.clj: -------------------------------------------------------------------------------- 1 | (ns malli-ts.test 2 | (:require [clojure.core :refer [spit]] 3 | [malli-ts.core :as mts])) 4 | 5 | (def schemas 6 | {:k [:=> [:catn [:s string?]] any?] 7 | :sym [:=> [:catn [:s string?]] any?] 8 | :toClj [:=> [:catn [:o any?]] any?] 9 | :validate [:=> [:catn [:schema any?] [:val any?]] any?] 10 | :my-identity [:=> [:catn [:x any?]] any?]}) 11 | 12 | (defn prep-lib 13 | [] 14 | (let [[_ content] 15 | (-> (mts/parse-files 16 | {"flow-domain.core.d.ts" [[:k {:t-name "k" :declare true}] 17 | [:sym {:t-name "sym" :declare true}] 18 | [:toClj {:t-name "toClj" :declare true}] 19 | [:validate {:t-name "validate" :declare true}] 20 | [:my-identity {:t-name "identity" :declare true}]]} 21 | {:export-default true 22 | :jsdoc-default [::mts/schema] 23 | :use-default-registry true 24 | :registry schemas}) 25 | first)] 26 | (spit "out/flow-domain.core.d.ts" content))) 27 | 28 | (comment (prep-lib)) -------------------------------------------------------------------------------- /development/malli_ts/dev/experiments/exp2_schema_options.cljc: -------------------------------------------------------------------------------- 1 | (ns malli-ts.dev.experiments.exp2-schema-options 2 | (:require [malli-ts.core :refer [parse-files] :as mt] 3 | [malli.core :as m] 4 | [malli.util :as mu])) 5 | 6 | (defn print-files 7 | [file->content] 8 | (doseq [[file content] file->content] 9 | (println 10 | 11 | (str "-- " file " --" \newline 12 | content \newline)))) 13 | 14 | (def flow-person-schema 15 | (m/schema [:map 16 | {::mt/t-name "Person" ::mt/export true ::mt/jsdoc [::mt/schema]} 17 | [:name string?] [:age pos-int?]])) 18 | 19 | (def flow-office-schema 20 | (m/schema 21 | [:set 22 | {::mt/t-name "Office" ::mt/jsdoc [::mt/schema]} 23 | [:schema {:registry {:flow/person flow-person-schema}} :flow/person]])) 24 | 25 | (def registry 26 | {:flow/person flow-person-schema 27 | :flow/office flow-office-schema 28 | :ibanxs/blah (m/schema [:enum {::mt/t-name "Blah"} "blah"])}) 29 | 30 | (comment 31 | (-> (parse-files 32 | {"flow/index.d.ts" [[:flow/person {}]]} 33 | {:export-default true 34 | :files-import-alias {"flow/index.d.ts" "flow"} 35 | :registry registry}) 36 | print-files) 37 | 38 | (-> (mt/parse-matching-schemas {:registry registry}) 39 | print-files) 40 | 41 | (-> (mt/parse-ns-schemas '(flow ibanxs) {:registry registry}) 42 | print-files) 43 | 44 | (mu/update-properties 45 | (m/deref :flow/person {:registry registry}) assoc ::mt/t-name "FlowPersonV2")) 46 | -------------------------------------------------------------------------------- /src/malli_ts/core_schema.cljc: -------------------------------------------------------------------------------- 1 | (ns malli-ts.core-schema) 2 | 3 | (def schema-type-options 4 | [:map 5 | [:declare {:optional true} boolean?] 6 | [:t-name {:optional true} string?] 7 | [:export {:optional true} boolean?]]) 8 | 9 | (def schema-type-options-with-literals 10 | (conj schema-type-options [:literal string?] [:jsdoc-literal string?])) 11 | 12 | (def file->schema-type-vectors 13 | [:map-of 14 | string? 15 | [:vector 16 | [:or 17 | [:catn 18 | [:schema-id keyword?] 19 | [:schema-type-options schema-type-options]] 20 | keyword?]]]) 21 | 22 | (def parse-files-options 23 | [:map 24 | [:export-default {:optional true, :default true} boolean?] 25 | [:jsdoc-default {:optional true} [:vector keyword?]] 26 | [:use-default-schemas {:optional true, :default true} boolean?] 27 | [:files-import-alias {:optional true} [:map-of string? string?]] 28 | [:registry {:optional true} [:map-of any? any?]]]) 29 | 30 | (def parse-files-return 31 | [:map-of string? string?]) 32 | 33 | (def schema-id->type-options 34 | [:map-of keyword? schema-type-options]) 35 | 36 | (def assoc-literals-options 37 | (conj parse-files-options 38 | [:schema-id->type-options schema-id->type-options] 39 | [:file-imports* some?] 40 | [:files-import-alias* some?])) 41 | 42 | (def registry 43 | {::schema-type-options schema-type-options 44 | ::schema-type-options-with-literals schema-type-options-with-literals 45 | ::file->schema-type-vectors file->schema-type-vectors 46 | ::parse-files-options parse-files-options 47 | ::parse-files-return parse-files-return 48 | ::schema-id->type-options schema-id->type-options 49 | ::assoc-literals-options assoc-literals-options}) 50 | -------------------------------------------------------------------------------- /development/malli_ts/dev/experiments/exp1.cljs: -------------------------------------------------------------------------------- 1 | (ns malli-ts.dev.experiments.exp1 2 | (:require [malli-ts.core :refer [parse-files] :as mt] 3 | [malli.core :as m])) 4 | 5 | (defn print-files 6 | [file->content] 7 | (doseq [[file content] file->content] 8 | (println 9 | 10 | (str "-- "file " --" \newline 11 | content \newline)))) 12 | 13 | (comment 14 | (-> (parse-files 15 | {"flow/index.d.ts" [[:flow/person {:t-name "FlowPerson" 16 | :export true 17 | :jsdoc [::mt/schema]}]]} 18 | 19 | {:export-default true 20 | :files-import-alias {"flow/index.d.ts" "flow"} 21 | :registry {:flow/person (m/schema [:map [:name string?] [:age pos-int?]])}}) 22 | print-files)) 23 | 24 | (comment 25 | (let [file->content 26 | (parse-files 27 | {"flow/index.d.ts" [[:flow/person {:t-name "FlowPerson"}] 28 | [:account {:t-name "Account"}]] 29 | "flow/company/index.d.ts" [[:flow/address {:t-name "FlowAddress"}] 30 | [:flow/company {:t-name "FlowCompany"}]]} 31 | {:export-default true 32 | :default-to-camel-case true 33 | :files-import-alias {"flow/index.d.ts" "flow" 34 | "flow/company/index.d.ts" "fCompany"} 35 | :jsdoc-default [::mt/schema] 36 | :registry 37 | (let [registry* (atom (m/default-schemas)) 38 | update-registry! 39 | (fn [schema-id schema] 40 | (swap! registry* assoc schema-id 41 | (m/schema schema {:registry @registry*})))] 42 | (update-registry! :flow/person [:map [:name :string] [:age :int]]) 43 | (update-registry! :flow/address string?) 44 | 45 | (update-registry! :flow/company [:map 46 | [:name :string] 47 | [:people [:set :flow/person]] 48 | [:stuff [:set any?]] 49 | [:address :flow/address]]) 50 | 51 | (update-registry! :account [:map [:balance float?] [:asdf-asdf any?]]) 52 | @registry*)})] 53 | (print-files file->content))) 54 | 55 | -------------------------------------------------------------------------------- /src/malli_ts/ts.cljs: -------------------------------------------------------------------------------- 1 | (ns malli-ts.ts 2 | (:require [malli-ts.core :as mt] 3 | [malli.core :as m] 4 | [cljs-bean.core :as b] 5 | ["fs" :as fs])) 6 | 7 | (defn k [s] (keyword s)) 8 | (defn sym [s] (symbol s)) 9 | 10 | (defn bean [o] (b/bean o)) 11 | 12 | (defn toClj [o] (b/->clj o)) 13 | 14 | (comment 15 | (m/validate 16 | [:map [:a int?]] 17 | (toClj #js {"a" 1}))) 18 | 19 | (defn validate 20 | [& args] 21 | (apply m/validate (map b/->clj args))) 22 | 23 | (defn now [] (js/Date.)) 24 | 25 | (defn toSha256 [s] 26 | (-> (js/require "crypto") 27 | (.createHash "sha256") 28 | (.update s))) 29 | 30 | (comment 31 | (-> "hello world" toSha256 (.digest "hex"))) 32 | 33 | (defn exports-fn 34 | [] 35 | (comment 36 | (-> (doto (ns-publics 'malli-ts.ts) prn) 37 | (dissoc 'exports-fn) 38 | (->> (map (fn [[sym var]] [(keyword sym) @var])) 39 | (into {})) 40 | clj->js)) 41 | #js {:k k 42 | :sym sym 43 | :bean bean 44 | :toClj toClj 45 | :validate validate}) 46 | 47 | (defn gen-types 48 | [& args] 49 | (let [[_ content] 50 | (-> (mt/parse-files 51 | {"malli-ts.ts.d.ts" [[:k {:t-name "k" :declare true}] 52 | [:sym {:t-name "sym" :declare true}] 53 | [:toClj {:t-name "toClj" :declare true}] 54 | [:validate {:t-name "validate" :declare true}] 55 | [:now {:t-name "now" :declare true}] 56 | [:toSha256 {:t-name "toSha256" :declare true}]]} 57 | {:export-default true 58 | :jsdoc-default [::mt/schema] 59 | :use-default-schemas true 60 | :registry 61 | {:k [:=> [:catn [:s string?]] any?] 62 | :sym [:=> [:catn [:s string?]] any?] 63 | :toClj [:=> [:catn [:o any?]] any?] 64 | :validate [:=> [:catn [:schema any?] [:val any?]] any?] 65 | 66 | :date (mt/external-type "Date") 67 | :now [:=> [:cat] :date] 68 | 69 | :crypto/hash (mt/external-type "Hash" "crypto" "crypto") 70 | :toSha256 [:=> [:catn [:s string?]] :crypto/hash]}}) 71 | first)] 72 | (.writeFileSync fs "examples/malli-ts.d.ts" content))) 73 | -------------------------------------------------------------------------------- /development/malli_ts/dev/experiments/exp2_external_types.cljc: -------------------------------------------------------------------------------- 1 | (ns malli-ts.dev.experiments.exp2-external-types 2 | (:require [malli-ts.core :refer [parse-files] :as mt] 3 | [malli.core :as m])) 4 | 5 | (defn external-type 6 | ([type-name type-path type-import-alias] 7 | [any? {:external-type {:t-name type-name 8 | :t-path type-path 9 | :t-alias type-import-alias}}]) 10 | ([type-name type-path] 11 | (external-type type-name type-path nil))) 12 | 13 | (defn print-files 14 | [file->content] 15 | (doseq [[file content] file->content] 16 | (println 17 | 18 | (str "-- "file " --" \newline 19 | content \newline)))) 20 | 21 | (comment 22 | (-> (parse-files 23 | {"flow/index.d.ts" [[:flow/person {:t-name "FlowPerson" 24 | :export true 25 | :jsdoc [::mt/schema]}]]} 26 | 27 | {:export-default true 28 | :files-import-alias {"flow/index.d.ts" "flow"} 29 | :registry {:flow/person (m/schema [:map [:name string?] [:age pos-int?]])}}) 30 | print-files)) 31 | 32 | (comment 33 | (let [file->content 34 | (parse-files 35 | {"flow/index.d.ts" [[:flow/person {:t-name "FlowPerson"}] 36 | [:account {:t-name "Account"}]] 37 | "flow/company/index.d.ts" [[:flow/address {:t-name "FlowAddress"}] 38 | [:flow/company {:t-name "FlowCompany"}]]} 39 | {:export-default true 40 | :default-to-camel-case true 41 | :files-import-alias {"flow/index.d.ts" "flow" 42 | "flow/company/index.d.ts" "fCompany"} 43 | :jsdoc-default [::mt/schema] 44 | :registry 45 | (let [registry* (atom (m/default-schemas)) 46 | update-registry! 47 | (fn [schema-id schema] 48 | (swap! registry* assoc schema-id 49 | (m/schema schema {:registry @registry*})))] 50 | (update-registry! :lib/ding-dong 51 | (external-type "DingDong" "lib-asdf")) 52 | (update-registry! :flow/person [:map 53 | [:name :string] 54 | [:age :int] 55 | [:ding-dong :lib/ding-dong]]) 56 | (update-registry! :flow/address string?) 57 | 58 | (update-registry! :flow/company [:map 59 | [:name :string] 60 | [:people [:set :flow/person]] 61 | [:stuff [:set any?]] 62 | [:address :lib/ding-dong]]) 63 | 64 | (update-registry! :account [:map [:balance float?] [:asdf-asdf any?]]) 65 | @registry*)})] 66 | (print-files file->content))) 67 | 68 | -------------------------------------------------------------------------------- /src/malli_ts/data_mapping.cljs: -------------------------------------------------------------------------------- 1 | (ns malli-ts.data-mapping 2 | (:require 3 | [camel-snake-kebab.core :as csk] 4 | [malli-ts.core :as-alias mts] 5 | [malli.core :as m])) 6 | 7 | (def complex-types #{'seqable? 8 | 'indexed? 9 | 'map? 10 | 'vector? 11 | 'list? 12 | 'seq? 13 | 'set? 14 | 'empty? 15 | 'sequential? 16 | 'coll? 17 | 'associative? 18 | ::m/val}) 19 | 20 | (defn primitive? [x] 21 | (nil? (complex-types x))) 22 | 23 | (defrecord Mapping [key prop schema]) 24 | 25 | (defn- deref-schema [schema defs] 26 | (if-let [ref (::ref schema)] 27 | (-> defs ref (deref-schema defs)) 28 | schema)) 29 | 30 | (defn- walk-schema->clj<>js-mapping 31 | ([schema {::keys [prop-name-fn] :as options}] 32 | (m/walk 33 | schema 34 | (fn [schema' path children {::keys [*definitions] :as opts}] 35 | (let [s-type (m/type schema')] 36 | (case s-type 37 | :ref 38 | , {::ref (m/-ref schema')} 39 | 40 | (::m/schema :union :merge :select-keys) 41 | , (let [result (walk-schema->clj<>js-mapping (m/deref schema') opts)] 42 | (if-let [ref (m/-ref schema')] 43 | (do 44 | (swap! *definitions assoc! ref result) 45 | {::ref ref}) 46 | result)) 47 | 48 | (:schema :set :sequential :vector) 49 | , (first children) 50 | 51 | :enum 52 | , (m/form schema') 53 | 54 | :map-of 55 | , (Mapping. nil nil (second children)) 56 | 57 | :or 58 | , (let [merged (->> children 59 | (reduce 60 | (fn [a b] 61 | (let [a (deref-schema a @*definitions) 62 | b (deref-schema b @*definitions)] 63 | (if (map? a) (if (map? b) (merge a b) #_else a) 64 | #_else b)))))] 65 | ;; Either the children-mappings merged into a single map, or the first child 66 | merged) 67 | 68 | :map 69 | , (->> children 70 | (reduce 71 | (fn [x [k opts s]] 72 | (let [p (-> opts ::mts/clj<->js :prop (or (prop-name-fn k))) 73 | m (Mapping. k p (first s))] 74 | (assoc! x, k m, p m))) 75 | (transient {})) 76 | (persistent!)) 77 | 78 | ; else 79 | (cond 80 | (empty? path) 81 | , (first children) 82 | 83 | (primitive? s-type) 84 | , s-type 85 | 86 | :else 87 | , children)))) 88 | options))) 89 | 90 | (defn- -clj<>js-mapping 91 | ([schema] 92 | (-clj<>js-mapping schema {})) 93 | ([schema {:keys [default-to-camel-case] :as options}] 94 | (let [*defs (or (::*definitions options) 95 | (atom (transient {}))) 96 | options 97 | (merge options 98 | {::*definitions *defs 99 | ::prop-name-fn (if default-to-camel-case csk/->camelCaseString #_else name) 100 | ::m/walk-schema-refs true 101 | ::m/walk-refs true 102 | ::m/walk-entry-vals true}) 103 | root (walk-schema->clj<>js-mapping schema options)] 104 | (as-> @*defs mapping 105 | (assoc! mapping ::root root) 106 | (persistent! mapping) 107 | ;; Follow mapping ::refs to their final value: 108 | (update-vals mapping 109 | #(loop [v %] (if-let [v' (::ref v)] (recur (mapping v')) #_else v))))))) 110 | 111 | (def clj<->js-mapping (memoize -clj<>js-mapping)) 112 | -------------------------------------------------------------------------------- /src/malli_ts/data_mapping/to_clj.cljs: -------------------------------------------------------------------------------- 1 | (ns malli-ts.data-mapping.to-clj 2 | (:require 3 | [malli-ts.core :as-alias mts] 4 | [malli-ts.data-mapping :as mts-dm] 5 | [malli.core :as m] 6 | [cljs-bean.core :as b :refer [bean?]])) 7 | 8 | (defn unwrap [v] 9 | (when v 10 | (or (unchecked-get v "unwrap/clj") 11 | (when (bean? v) v)))) 12 | 13 | (deftype BeanContext [js<->clj-mapping mapping ^:mutable sub-cache] 14 | b/BeanContext 15 | (keywords? [_] true) 16 | (key->prop [_ key'] 17 | ; When prop is nil, the schema is for a :map-of and we return the given key as is. 18 | ; If there is a schema, we return the prop from the schema 19 | ; or if there is no schema, we return the name of the given key' 20 | (let [s (get mapping key')] (set! sub-cache s) (if-let [p (some-> s .-prop)] p (name key')))) 21 | (prop->key [_ prop] 22 | ; When key is nil, the schema is for a :map-of and we return the given prop as is. 23 | ; If there is a schema, we return the key from the schema 24 | ; or if there is no schema, we keywordize the given prop 25 | (let [s (get mapping prop)] (set! sub-cache s) (if s (or (.-key s) prop) (keyword prop)))) 26 | (transform [_ v prop key' nth'] 27 | (if-some [v (unwrap v)] v 28 | ;else 29 | (if-some [bean' (cond (object? v) true (array? v) false)] 30 | (let [sub-mapping 31 | (if (or nth' (not sub-cache)) 32 | mapping 33 | ;else 34 | (let [s (.-schema sub-cache)] 35 | (if-let [ref (::mts-dm/ref s)] (js<->clj-mapping ref) s))) 36 | bean-context 37 | (BeanContext. js<->clj-mapping sub-mapping nil)] 38 | (if bean' 39 | (b/Bean. nil v bean-context true nil nil nil) 40 | (b/ArrayVector. nil bean-context v nil))) 41 | ;else 42 | v)))) 43 | 44 | (defn ^:export to-clj 45 | ([v js<->clj-mapping] 46 | (if-some [v (unwrap v)] v 47 | ;else 48 | (if-some [bean' (cond (object? v) true (array? v) false)] 49 | (let [root 50 | (::mts-dm/root js<->clj-mapping) 51 | root 52 | (if-let [ref (::mts-dm/ref root)] (js<->clj-mapping ref) #_else root) 53 | bean-context 54 | (BeanContext. js<->clj-mapping root nil)] 55 | (if bean' 56 | (b/Bean. nil v bean-context true nil nil nil) 57 | (b/ArrayVector. nil bean-context v nil))) 58 | ;else 59 | v))) 60 | 61 | ([x registry schema & [mapping-options]] 62 | (let [s (m/schema [:schema {:registry registry} 63 | schema])] 64 | (to-clj x (mts-dm/clj<->js-mapping s mapping-options))))) 65 | 66 | (comment 67 | 68 | (let [order-items-schema [:vector [:map 69 | [:order/item {:optional true 70 | ::mts/clj<->js {:prop "orderItem" 71 | :fn-to nil 72 | :fn-from nil}} 73 | [:map 74 | [:order-item/id uuid?] 75 | [:order-item/type {::mts/clj<->js {:prop "type"}} 76 | string?] 77 | [:order-item/price 78 | [:map 79 | [:order-item/currency [:enum :EUR :USD :ZAR]] 80 | [:order-item/amount number?]]] 81 | [:order-item/test-dummy {::mts/clj<->js {:prop "TESTDummyXYZ"}} 82 | string?] 83 | [:order-item/related-items 84 | [:ref ::order-items] 85 | #_[:vector [:map 86 | [:related-item/how-is-related string? 87 | :related-item/order-item-id uuid?]]]]]] 88 | [:order/credit {:optional true} 89 | [:map 90 | [:order.credit/valid-for-timespan [:enum :milliseconds :seconds :minutes :hours :days]] 91 | [:order-credit/amount number?]]]]] 92 | order-schema [:map 93 | [:model-type [:= ::order]] 94 | [:order/id {::mts/clj<->js {:prop "orderId"}} 95 | string?] 96 | [:order/type {::mts/clj<->js {:prop "orderType"}} 97 | [:or keyword? string?]] 98 | #_[:order/items {:optional true 99 | ::mts/clj<->js {:prop "orderItems"}} 100 | [:ref ::order-items]] 101 | [:order/items {:optional true 102 | ::mts/clj<->js {:prop "orderItems"}} ::order-items] 103 | [:order/total-amount {:optional true 104 | ::mts/clj<->js {:prop "totalAmount"}} 105 | number?] 106 | [:order/user {::mts/clj<->js {:prop "user"} 107 | :optional true} 108 | [:map 109 | [:user/id {::mts/clj<->js {:prop "userId"}} string?] 110 | [:user/name {:optional true} string?]]]] 111 | r {::order-items order-items-schema} 112 | s (m/schema [:schema {:registry {::order-items order-items-schema}} 113 | order-schema]) 114 | clj-map (to-clj #js {"modelType" ::order 115 | "orderId" "2763yughjbh333" 116 | "orderType" "Sport Gear" 117 | "user" #js {"userId" "u678672" 118 | "name" "Kosie"} 119 | "orderItems" #js [#js {:orderItem 120 | #js {:type "some-test-order-item-type-1" 121 | :price #js {:currency :EUR 122 | :amount 22.3} 123 | :TESTDummyXYZ "TD-A1" 124 | :relatedItems #js [#js {:credit 125 | #js {:amount 676.30}}]}} 126 | #js {:orderItem 127 | #js {:type "some-test-order-item-type-2" 128 | :price #js {:currency :ZAR 129 | :amount 898} 130 | :TESTDummyXYZ "TD-B2"}}]} s)] 131 | #_{:js->clj-mapping (mts-dm/clj<->js-mapping s :prop) 132 | :clj->js-mapping (mts-dm/clj<->js-mapping s :key)} 133 | #_(-> clj-map :order/user :user/id) 134 | (get-in clj-map [:order/items 0 :order/item :order-item/related-items 0 135 | :order/credit :order-credit/amount]) 136 | #_(get-in clj-map [:order/items 1 :order/item :order-item/price :order-item/currency]))) 137 | -------------------------------------------------------------------------------- /test/malli_ts/core_test.cljc: -------------------------------------------------------------------------------- 1 | (ns malli-ts.core-test 2 | (:require 3 | #?(:clj [clojure.test :refer [deftest is testing]] 4 | :cljs [cljs.test :refer-macros [deftest is testing]]) 5 | [malli-ts.ast :refer [->ast]] 6 | [malli-ts.core :refer [->type-declaration-str -jsdoc-literal parse-ast-node 7 | parse-files parse-ns-schemas] :as mts] 8 | [malli.instrument :as mi] 9 | [malli.dev.pretty :as malli-dev-pretty])) 10 | 11 | (comment 12 | (mi/instrument! 13 | {:filters [(mi/-filter-ns 'malli-ts.core)] 14 | :report (malli-dev-pretty/thrower)}) 15 | 16 | (mi/unstrument!)) 17 | 18 | 19 | (deftest function-types 20 | (is (= "(a:string, b:number) => {\"a\":number,\"b\":string}" 21 | (parse-ast-node 22 | (->ast [:=> [:cat :string :int] [:map [:a :int] [:b :string]]])))) 23 | (is (= "(name:string, age:number) => {\"a\":number,\"b\":string}" 24 | (parse-ast-node 25 | (->ast [:=> [:cat :string :int] [:map [:a :int] [:b :string]]]) 26 | {:args-names ["name" "age"]}))) 27 | (is (= "(name:string, age:number, accountNumber:string) => {\"a\":number,\"b\":string}" 28 | (parse-ast-node 29 | (->ast [:=> [:catn [:name :string] [:age :int] [:account-number string?]] 30 | [:map [:a :int] [:b :string]]])))) 31 | (is (= "() => any" 32 | (parse-ast-node 33 | (->ast [:=> [:cat] any?]))))) 34 | 35 | (deftest declaration 36 | (let [schema [:map-of :string :any] 37 | literal (parse-ast-node (->ast schema)) 38 | jsdoc-literal (-jsdoc-literal [["schema" (str schema)] 39 | ["author" "Mr. Poopybutthole"]]) 40 | declaration-str (->type-declaration-str "config" literal jsdoc-literal 41 | {:export true :declare true})] 42 | (is (= "/**\n * @schema [:map-of :string :any]\n * @author Mr. Poopybutthole\n */\nexport var config: {[k:string]:any};" 43 | declaration-str)))) 44 | 45 | (def parse-files-data 46 | {"malli_ts.ts.d.ts" [[::k {:declare true}] 47 | [::sym {:declare true}] 48 | [::toClj {:declare true}] 49 | [::validate {:declare true}] 50 | [::answerUltimateQuestion {:declare true}] 51 | [::now {:t-name "now" :declare true}] 52 | [::toSha256 {:t-name "toSha256" :declare true}]] 53 | "random/dir/universe.d.ts" [[:random.dir.universe/answer-to-everything {:t-name "answerToEverything"}] 54 | :random.dir.universe/import-test-fn] 55 | "random/util/string" [:random.util.string/get-default-string-decoder]}) 56 | 57 | (def parse-ns-schemas-data 58 | '#{malli-ts.core-test random.dir.universe random.util.string}) 59 | 60 | (def parse-files-options 61 | {:export-default true 62 | :jsdoc-default [::mts/schema] 63 | :use-default-schemas true 64 | :files-import-alias {'random.util.string "utilString" 65 | "random/util/string" "utilString"} 66 | :registry 67 | {::k [:=> [:catn [:s string?]] any?] 68 | ::sym [:=> [:catn [:s string?]] any?] 69 | ::toClj [:=> [:catn [:o any?]] any?] 70 | ::validate [:=> [:catn [:schema any?] [:val any?]] any?] 71 | 72 | :random.dir.universe/answer-to-everything [:enum 42] 73 | :random.dir.universe/import-test-fn :random.util.string/get-default-string-decoder 74 | 75 | :random.util.string/get-default-string-decoder 76 | [:=> :catn (mts/external-type "StringDecoder" :import-path "string_decoder" :schema some?)] 77 | ::answerUltimateQuestion [:=> :cat :random.dir.universe/answer-to-everything] 78 | 79 | :global/date (mts/external-type "Date" :schema inst?) 80 | ::now [:=> [:cat] :global/date] 81 | 82 | ;; explicit "absolute" path (already default ) 83 | :crypto/hash (mts/external-type "Hash" :import-path {:absolute "crypto"} :import-alias "crypto") 84 | ::toSha256 [:=> [:catn [:s string?]] :crypto/hash]}}) 85 | 86 | (comment 87 | (parse-files parse-files-data parse-files-options) 88 | (parse-ns-schemas parse-ns-schemas-data parse-files-options)) 89 | 90 | (deftest parsing 91 | (is (= {"user.d.ts" "type event = {\"value\":any,\"timestamp\":Date};"} 92 | (parse-ns-schemas 93 | #{'user} 94 | {:registry {:user/event [:map 95 | [:value any?] 96 | [:timestamp inst?]]}}) 97 | (parse-ns-schemas 98 | #{'user} 99 | {:registry {:date [inst? {::mts/external-type "Date"}] 100 | :user/event [:map 101 | [:value any?] 102 | [:timestamp :date]]}}))) 103 | 104 | (is (= {"malli_ts.ts.d.ts" 105 | "import * as crypto from 'crypto';\nimport * as random_dir_universe from './random/dir/universe.d.ts';\n\n/**\n * @schema [:=> [:catn [:s string?]] any?]\n */\nexport var k: (s:string) => any;\n/**\n * @schema [:=> [:catn [:s string?]] any?]\n */\nexport var sym: (s:string) => any;\n/**\n * @schema [:=> [:catn [:o any?]] any?]\n */\nexport var to_clj: (o:any) => any;\n/**\n * @schema [:=> [:catn [:schema any?] [:val any?]] any?]\n */\nexport var validate: (schema:any, val:any) => any;\n/**\n * @schema [:=> :cat :random.dir.universe/answer-to-everything]\n */\nexport var answer_ultimate_question: () => random_dir_universe.answerToEverything;\n/**\n * @schema [:=> :cat :global/date]\n */\nexport var now: () => Date;\n/**\n * @schema [:=> [:catn [:s string?]] :crypto/hash]\n */\nexport var toSha256: (s:string) => crypto.Hash;", 106 | "random/dir/universe.d.ts" 107 | "import * as utilString from './../util/string';\n\n/**\n * @schema [:enum 42]\n */\nexport type answerToEverything = (42);\n/**\n * @schema :random.util.string/get-default-string-decoder\n */\nexport type import_test_fn = utilString.get_default_string_decoder;", 108 | "random/util/string.d.ts" 109 | "import * as string_decoder from 'string_decoder';\n\n/**\n * @schema [:=> :catn [some? #:malli-ts.core{:external-type \"StringDecoder\", :t-path {:absolute \"string_decoder\"}}]]\n */\nexport type get_default_string_decoder = () => string_decoder.StringDecoder;"} 110 | (parse-files parse-files-data parse-files-options))) 111 | 112 | (is (= {"random.dir.universe.d.ts" 113 | "import * as utilString from './random.util.string';\n\n/**\n * @schema [:enum 42]\n */\nexport type answer_to_everything = (42);\n/**\n * @schema :random.util.string/get-default-string-decoder\n */\nexport type import_test_fn = utilString.get_default_string_decoder;", 114 | "random.util.string.d.ts" 115 | "import * as string_decoder from 'string_decoder';\n\n/**\n * @schema [:=> :catn [some? #:malli-ts.core{:external-type \"StringDecoder\", :t-path {:absolute \"string_decoder\"}}]]\n */\nexport type get_default_string_decoder = () => string_decoder.StringDecoder;", 116 | "malli_ts.core_test.d.ts" 117 | "import * as crypto from 'crypto';\nimport * as random_dir_universe from './random.dir.universe';\n\n/**\n * @schema [:=> :cat :random.dir.universe/answer-to-everything]\n */\nexport type answer_ultimate_question = () => random_dir_universe.answer_to_everything;\n/**\n * @schema [:=> [:catn [:schema any?] [:val any?]] any?]\n */\nexport type validate = (schema:any, val:any) => any;\n/**\n * @schema [:=> :cat :global/date]\n */\nexport type now = () => Date;\n/**\n * @schema [:=> [:catn [:o any?]] any?]\n */\nexport type to_clj = (o:any) => any;\n/**\n * @schema [:=> [:catn [:s string?]] :crypto/hash]\n */\nexport type to_sha_256 = (s:string) => crypto.Hash;\n/**\n * @schema [:=> [:catn [:s string?]] any?]\n */\nexport type sym = (s:string) => any;\n/**\n * @schema [:=> [:catn [:s string?]] any?]\n */\nexport type k = (s:string) => any;"} 118 | (parse-ns-schemas parse-ns-schemas-data parse-files-options)))) 119 | 120 | (comment 121 | (testing "ast parsing" 122 | (function-types)) 123 | (testing "declaration" 124 | (declaration)) 125 | (testing "parsing" 126 | (parsing))) 127 | 128 | -------------------------------------------------------------------------------- /src/malli_ts/data_mapping/to_js.cljs: -------------------------------------------------------------------------------- 1 | (ns malli-ts.data-mapping.to-js 2 | (:require 3 | [malli-ts.core :as-alias mts] 4 | [malli-ts.data-mapping :as mts-dm] 5 | [malli.core :as m] 6 | [cljs-bean.core :as b])) 7 | 8 | (declare to-js') 9 | 10 | (defn- get-prop [js->clj-mapping cur-js->clj-mapping this target prop] 11 | (let [mapping (when cur-js->clj-mapping (cur-js->clj-mapping prop)) 12 | ; When mapping :key is nil, the schema is for a :map-of and we lookup the given prop 13 | m-prop (if mapping (or (.-key mapping) prop) #_else (keyword prop)) 14 | value (get target m-prop ::not-found)] 15 | (if (= value ::not-found) 16 | value 17 | ;else 18 | (or (unchecked-get this prop) 19 | (let [proxy (to-js' value js->clj-mapping (:schema mapping))] 20 | ;; cache proxy as `this[prop]` 21 | (unchecked-set this prop proxy) 22 | proxy))))) 23 | 24 | (deftype JsProxy [js->clj-mapping cur-js->clj-mapping] 25 | Object 26 | (ownKeys [this target] 27 | (if cur-js->clj-mapping 28 | (->> (keys target) 29 | (map (fn [k] (-> (cur-js->clj-mapping k) 30 | :prop 31 | (or (if (keyword? k) (name k) #_else (str k)))))) 32 | (apply array)) 33 | ;else 34 | (apply array (map #(if (keyword? %) (name %) #_else (str %)) (keys target))))) 35 | (getOwnPropertyDescriptor [this target prop] 36 | (let [v (get-prop js->clj-mapping cur-js->clj-mapping this target prop)] 37 | (if (= v ::not-found) 38 | #js {:value nil, :writable false, :enumerable false, :configurable true} 39 | ;else 40 | #js {:value v, :writable false, :enumerable true, :configurable true}))) 41 | (get [this target prop] 42 | (case prop 43 | "unwrap/clj" target 44 | (or (unchecked-get this prop) 45 | (let [v (get-prop js->clj-mapping cur-js->clj-mapping this target prop)] 46 | (if (= v ::not-found) nil #_else v))))) 47 | (getPrototypeOf [this] 48 | ; Javascript requires object or explicit null value and will crash on undefined: 49 | (or (.-prototype this) (.-prototype js/Object)))) 50 | 51 | (defn- to-js' 52 | ([x js->clj-mapping cur-js->clj-mapping] 53 | (cond 54 | (or (nil? x) 55 | ;; If the "unwrap/clj" property exists, x is already wrapped 56 | ;; Cast to boolean for unwrapped JS truthyness check 57 | ^boolean (unchecked-get x "unwrap/clj")) 58 | , x 59 | 60 | (b/bean? x) 61 | , (b/->js x) 62 | 63 | (or (sequential? x) 64 | (set? x)) 65 | , (let [len (count x) 66 | arr (js/Array. len)] 67 | (loop [i 0 x (seq x)] 68 | (if x 69 | (let [[v & rest] x] 70 | (unchecked-set arr i (to-js' v js->clj-mapping cur-js->clj-mapping)) 71 | (recur (unchecked-inc i) rest)) 72 | arr))) 73 | 74 | (associative? x) 75 | , (let [nested-mapping 76 | (if-let [ref (::mts-dm/ref cur-js->clj-mapping)] (js->clj-mapping ref) 77 | #_else cur-js->clj-mapping)] 78 | (js/Proxy. x (JsProxy. js->clj-mapping (when (map? nested-mapping) nested-mapping)))) 79 | 80 | :else 81 | , x))) 82 | 83 | (defn ^:export to-js 84 | ([x mapping] 85 | (let [cur (::mts-dm/root mapping) 86 | cur (if-let [ref (::mts-dm/ref cur)] (mapping ref) #_else cur)] 87 | (to-js' x mapping cur))) 88 | ([x registry schema & [mapping-options]] 89 | (let [s (m/schema [:schema {:registry registry} 90 | schema]) 91 | m (mts-dm/clj<->js-mapping s mapping-options)] 92 | (to-js x m)))) 93 | 94 | (comment 95 | (let [order-items-schema [:vector [:map 96 | [:order/item {:optional true 97 | ::mts/clj<->js {:prop "orderItem" 98 | :fn-to nil 99 | :fn-from nil}} 100 | [:map 101 | [:order-item/id uuid?] 102 | [:order-item/type {::mts/clj<->js {:prop "type"}} 103 | string?] 104 | [:order-item/price 105 | [:map 106 | [:order-item/currency [:enum :EUR :USD :ZAR]] 107 | [:order-item/amount number?]]] 108 | [:order-item/test-dummy {::mts/clj<->js {:prop "TESTDummyXYZ"}} 109 | string?] 110 | [:order-item/related-items 111 | [:ref ::order-items] 112 | #_[:vector [:map 113 | [:related-item/how-is-related string? 114 | :related-item/order-item-id uuid?]]]]]] 115 | [:order/credit {:optional true} 116 | [:map 117 | [:order.credit/valid-for-timespan [:enum :milliseconds :seconds :minutes :hours :days]] 118 | [:order.credit/amount number?]]]]] 119 | order-schema [:map 120 | [:model-type [:= ::order]] 121 | [:order/id {::mts/clj<->js {:prop "orderId"}} 122 | string?] 123 | [:order/type {::mts/clj<->js {:prop "orderType"}} 124 | [:or keyword? string?]] 125 | #_[:order/items {:optional true 126 | ::mts/clj<->js {:prop "orderItems"}} 127 | [:ref ::order-items]] 128 | [:order/items {:optional true 129 | ::mts/clj<->js {:prop "orderItems"}} ::order-items] 130 | [:order/total-amount {:optional true 131 | ::mts/clj<->js {:prop "totalAmount"}} 132 | number?] 133 | [:order/user {::mts/clj<->js {:prop "user"} 134 | :optional true} 135 | [:map 136 | [:user/id {::mts/clj<->js {:prop "userId"}} string?] 137 | [:user/name {:optional true} string?]]]] 138 | r {::order-items order-items-schema} 139 | s (m/schema [:schema {:registry {::order-items order-items-schema}} 140 | order-schema]) 141 | js-obj (to-js {:model-type ::order 142 | :order/id "2763yughjbh333" 143 | :order/type "Sport Gear" 144 | :order/user {:user/id "u678672" 145 | :user/name "Kosie"} 146 | :order/items [{:order/item 147 | {:order-item/type "some-test-order-item-type-1" 148 | :order-item/price {:currency :EUR 149 | :amount 22.3} 150 | :order-item/test-dummy "TD-A1" 151 | :order-item/related-items [{:order/credit 152 | {:order.credit/amount 676.30}}]}} 153 | {:order/item 154 | {:order-item/type "some-test-order-item-type-2" 155 | :order-item/price {:order-item/currency :ZAR 156 | :order-item/amount 898} 157 | :order-item/test-dummy "TD-B2"}}]} s)] 158 | #_(cljs.pprint/pprint (clj<->js-mapping s :prop)) 159 | (-> js-obj .-user .-userId) 160 | #_(-> js-obj .-orderItems (aget 1) .-orderItem .-price .-currency))) 161 | -------------------------------------------------------------------------------- /src/malli_ts/ast.cljc: -------------------------------------------------------------------------------- 1 | (ns malli-ts.ast 2 | (:require [camel-snake-kebab.core :as csk] 3 | [malli.core :as m] 4 | [malli-ts.core :as-alias mts])) 5 | 6 | (defprotocol TsSchema 7 | (-parse-schema-node [this children options] 8 | "Parses a schema to TS type AST")) 9 | 10 | (defn -ref [x] {:$ref x}) 11 | 12 | (defn -schema 13 | [schema {::keys [transform definitions] :as options}] 14 | (let [derefed (m/deref schema) 15 | result (merge (transform derefed options) {:schema derefed}) 16 | ref (m/-ref schema)] 17 | (if ref 18 | (do (swap! definitions assoc ref result) 19 | (-ref ref)) 20 | result))) 21 | 22 | (defmulti parse-schema-node 23 | (fn [name _schema _children _options] name) 24 | :default ::default) 25 | 26 | (defmethod parse-schema-node ::default [_ _ _ _] {}) 27 | (defmethod parse-schema-node 'any? [_ _ _ _] {}) 28 | (defmethod parse-schema-node 'some? [_ _ _ _] {}) 29 | (defmethod parse-schema-node 'number? [_ _ _ _] {:type :number}) 30 | (defmethod parse-schema-node 'integer? [_ _ _ _] {:type :number}) 31 | (defmethod parse-schema-node 'int? [_ _ _ _] {:type :number}) 32 | (defmethod parse-schema-node 'pos-int? [_ _ _ _] {:type :number}) 33 | (defmethod parse-schema-node 'neg-int? [_ _ _ _] {:type :number}) 34 | (defmethod parse-schema-node 'nat-int? [_ _ _ _] {:type :number}) 35 | (defmethod parse-schema-node 'float? [_ _ _ _] {:type :number}) 36 | (defmethod parse-schema-node 'double? [_ _ _ _] {:type :number}) 37 | (defmethod parse-schema-node 'pos? [_ _ _ _] {:type :number}) 38 | (defmethod parse-schema-node 'neg? [_ _ _ _] {:type :number}) 39 | (defmethod parse-schema-node 'boolean? [_ _ _ _] {:type :boolean}) 40 | (defmethod parse-schema-node 'string? [_ _ _ _] {:type :string}) 41 | (defmethod parse-schema-node 'ident? [_ _ _ _] {:type :string}) 42 | (defmethod parse-schema-node 'simple-ident? [_ _ _ _] {:type :string}) 43 | (defmethod parse-schema-node 'qualified-ident? [_ _ _ _] {:type :string}) 44 | (defmethod parse-schema-node 'keyword? [_ _ _ _] {:type :string}) 45 | (defmethod parse-schema-node 'simple-keyword? [_ _ _ _] {:type :string}) 46 | (defmethod parse-schema-node 'qualified-keyword? [_ _ _ _] {:type :string}) 47 | (defmethod parse-schema-node 'symbol? [_ _ _ _] {:type :string}) 48 | (defmethod parse-schema-node 'simple-symbol? [_ _ _ _] {:type :string}) 49 | (defmethod parse-schema-node 'qualified-symbol? [_ _ _ _] {:type :string}) 50 | (defmethod parse-schema-node 'uuid? [_ _ _ _] {:type :string}) 51 | (defmethod parse-schema-node 'uri? [_ _ _ _] {:type :string}) 52 | (defmethod parse-schema-node 'decimal? [_ _ _ _] {:type :number}) 53 | (defmethod parse-schema-node 'inst? [_ _ _ _] {:type :date}) 54 | (defmethod parse-schema-node 'seqable? [_ _ _ _] {:type :array}) 55 | (defmethod parse-schema-node 'indexed? [_ _ _ _] {:type :array}) 56 | (defmethod parse-schema-node 'map? [_ _ _ _] {:type :object}) 57 | (defmethod parse-schema-node 'vector? [_ _ _ _] {:type :array}) 58 | (defmethod parse-schema-node 'list? [_ _ _ _] {:type :array}) 59 | (defmethod parse-schema-node 'seq? [_ _ _ _] {:type :array}) 60 | (defmethod parse-schema-node 'char? [_ _ _ _] {:type :string}) 61 | (defmethod parse-schema-node 'set? [_ _ _ _] {:type :array}) 62 | (defmethod parse-schema-node 'nil? [_ _ _ _] {:type :undefined}) 63 | (defmethod parse-schema-node 'false? [_ _ _ _] {:type :boolean}) 64 | (defmethod parse-schema-node 'true? [_ _ _ _] {:type :boolean}) 65 | (defmethod parse-schema-node 'zero? [_ _ _ _] {:type :number}) 66 | (defmethod parse-schema-node 'coll? [_ _ _ _] {:type :object}) 67 | (defmethod parse-schema-node 'empty? [_ _ _ _] {:type :array}) 68 | (defmethod parse-schema-node 'associative? [_ _ _ _] {:type :object}) 69 | (defmethod parse-schema-node 'sequential? [_ _ _ _] {:type :array}) 70 | (defmethod parse-schema-node 'bytes? [_ _ _ _] {:type :string}) 71 | (defmethod parse-schema-node :> [_ _ _ _] {:type :number}) 72 | (defmethod parse-schema-node :>= [_ _ _ _] {:type :number}) 73 | (defmethod parse-schema-node :< [_ _ _ _] {:type :number}) 74 | (defmethod parse-schema-node :<= [_ _ _ _] {:type :number}) 75 | (defmethod parse-schema-node := [_ _ [value] _] {:const value}) 76 | (defmethod parse-schema-node :not= [_ _ _ _] {}) 77 | (defmethod parse-schema-node :not [_ _ _ _] {}) 78 | 79 | (defmethod parse-schema-node :and [_ _ children _] 80 | (let [non-empty-children (filter (comp not empty?) children)] 81 | (if-not (empty? non-empty-children) 82 | {:intersection children} {}))) 83 | 84 | (defmethod parse-schema-node :or [_ _ children _] {:union children}) 85 | (defmethod parse-schema-node :orn [_ _ children _] {:union (map last children)}) 86 | 87 | (defmethod parse-schema-node ::m/val [_ _ children _] (first children)) 88 | 89 | (defmethod parse-schema-node :map [_ _ children {:keys [default-to-camel-case] :as options}] 90 | {:type :object 91 | :properties (->> children 92 | (map 93 | (fn [[k opts s]] 94 | (let [k' (or (-> opts ::mts/clj<->js :prop) 95 | (if default-to-camel-case 96 | (csk/->camelCase (name k)) 97 | (name k)))] 98 | [k' [s opts]]))) 99 | (into {}))}) 100 | 101 | (defmethod parse-schema-node :multi [_ _ children _] {:union (mapv last children)}) 102 | 103 | (defmethod parse-schema-node :map-of [_ _ children _] 104 | {:type :object, :index-signature children}) 105 | 106 | (defmethod parse-schema-node :vector [_ _ children _] {:type :array, :items (first children)}) 107 | (defmethod parse-schema-node :sequential [_ _ children _] {:type :array, :items (first children)}) 108 | (defmethod parse-schema-node :set [_ _ children _] {:type :array, :items (first children)}) 109 | (defmethod parse-schema-node :enum [_ _ children _] {:union (map #(array-map :const %) children)}) 110 | (defmethod parse-schema-node :maybe [_ _ children _] {:union (conj children {:type :undefined})}) 111 | (defmethod parse-schema-node :tuple [_ _ children _] {:type :tuple, :items children}) 112 | (defmethod parse-schema-node :re [_ _ _ _] {:type :string}) 113 | (defmethod parse-schema-node :fn [_ _ _ _] {}) 114 | (defmethod parse-schema-node :any [_ _ _ _] {}) 115 | (defmethod parse-schema-node :nil [_ _ _ _] {:type :undefined}) 116 | (defmethod parse-schema-node :string [_ _ _ _] {:type :string}) 117 | (defmethod parse-schema-node :number [_ _ _ _] {:type :number}) 118 | (defmethod parse-schema-node :int [_ _ _ _] {:type :number}) 119 | (defmethod parse-schema-node :double [_ _ _ _] {:type :number}) 120 | (defmethod parse-schema-node :boolean [_ _ _ _] {:type :boolean}) 121 | (defmethod parse-schema-node :keyword [_ _ _ _] {:type :string}) 122 | (defmethod parse-schema-node :qualified-keyword [_ _ _ _] {:type :string}) 123 | (defmethod parse-schema-node :symbol [_ _ _ _] {:type :string}) 124 | (defmethod parse-schema-node :qualified-symbol [_ _ _ _] {:type :string}) 125 | (defmethod parse-schema-node :uuid [_ _ _ _] {:type :string}) 126 | 127 | (defmethod parse-schema-node :catn [_ _ children _] 128 | {:type :catn :items (map (fn [[n _ type]] [n type]) children)}) 129 | 130 | (defmethod parse-schema-node :cat [_ _ children _] 131 | {:type :cat :items children}) 132 | 133 | (defmethod parse-schema-node :=> [_ _ [args ret] _] 134 | {:type :=> :args args :ret ret}) 135 | 136 | (defmethod parse-schema-node :function [_ _ children _] 137 | {:type :function :items children}) 138 | 139 | (defmethod parse-schema-node :ref [_ schema _ _] (-ref (m/-ref schema))) 140 | (defmethod parse-schema-node :schema [_ schema _ options] (-schema schema options)) 141 | (defmethod parse-schema-node ::m/schema [_ schema _ options] (-schema schema options)) 142 | 143 | (defmethod parse-schema-node :merge [_ schema _ {::keys [transform] :as options}] 144 | (transform (m/deref schema) options)) 145 | 146 | (defmethod parse-schema-node :union [_ schema _ {::keys [transform] :as options}] 147 | (transform (m/deref schema) options)) 148 | 149 | (defmethod parse-schema-node :select-keys [_ schema _ {::keys [transform] :as options}] 150 | (transform (m/deref schema) options)) 151 | 152 | (defn- -ts-schema-walker [schema _ children options] 153 | (merge (if (satisfies? TsSchema schema) 154 | (-parse-schema-node schema children options) 155 | (parse-schema-node (m/type schema) schema children options)) 156 | {:schema schema})) 157 | 158 | (defn- -parse [?schema options] (m/walk ?schema -ts-schema-walker options)) 159 | 160 | (defn ->ast 161 | ([?schema] 162 | (->ast ?schema nil)) 163 | ([?schema options] 164 | (let [definitions (atom {}) 165 | options (merge options {::m/walk-entry-vals true, ::definitions definitions, ::transform -parse})] 166 | (cond-> (-parse ?schema options) (seq @definitions) (assoc :definitions @definitions))))) 167 | 168 | (comment 169 | (-> (->ast 170 | [:schema 171 | {:registry {:flow/poop [:map [:poop [:= "yes"]]]}} 172 | [:map 173 | [:a :int] 174 | [:b {:optional true} float?] 175 | [:c :string] 176 | [:d :boolean] 177 | [:e :flow/poop] 178 | [:f [:=> [:cat :int :string] :string]] 179 | [:g [:function 180 | [:=> [:cat :int] :int] 181 | [:=> [:cat :int :string] :string]]]]]) 182 | prn)) 183 | 184 | (comment (->ast [:catn [:a :string] [:b number?]])) 185 | 186 | (comment 187 | (->ast [:map [:a {"optional" true} :any]])) 188 | 189 | -------------------------------------------------------------------------------- /test/malli_ts/data_mapping_test.cljs: -------------------------------------------------------------------------------- 1 | (ns malli-ts.data-mapping-test 2 | (:require [cljs.test :as t :refer-macros [deftest is testing]] 3 | [malli-ts.core :as-alias mts] 4 | [malli-ts.data-mapping :as sut] 5 | [malli-ts.data-mapping.to-clj :as sut-tc] 6 | [malli-ts.data-mapping.to-js :as sut-tj] 7 | [malli.core :as m])) 8 | 9 | (def schema 10 | (let [order-items-schema [:vector [:map 11 | [:order/item {:optional true 12 | ::mts/clj<->js {:prop "orderItem" 13 | :fn-to nil 14 | :fn-from nil}} 15 | [:map 16 | [:order-item/id uuid?] 17 | [:order-item/type {::mts/clj<->js {:prop "type"}} 18 | string?] 19 | [:order-item/price 20 | [:map 21 | [:order-item/currency [:enum :EUR :USD :ZAR]] 22 | [:order-item/amount number?]]] 23 | [:order-item/test-dummy {::mts/clj<->js {:prop "TESTDummyXYZ"}} 24 | string?] 25 | [:order-item/related-items 26 | [:ref ::order-items]]]] 27 | [:order/credit {:optional true} 28 | [:map 29 | [:order.credit/valid-for-timespan [:enum :milliseconds :seconds :minutes :hours :days]] 30 | [:order.credit/amount number?]]]]] 31 | order-schema [:map 32 | [:model-type [:= ::order]] 33 | [:order/id {::mts/clj<->js {:prop "orderId"}} 34 | string?] 35 | [:order/type {::mts/clj<->js {:prop "orderType"}} 36 | [:or keyword? string?]] 37 | [:order/items {:optional true 38 | ::mts/clj<->js {:prop "orderItems"}} ::order-items] 39 | [:order/total-amount {:optional true 40 | ::mts/clj<->js {:prop "totalAmount"}} 41 | number?] 42 | [:order/user {::mts/clj<->js {:prop "user"} 43 | :optional true} 44 | [:map 45 | [:user/id {::mts/clj<->js {:prop "userId"}} string?] 46 | [:user/name {:optional true} string?]]]]] 47 | (m/schema [:schema {:registry {::order-items order-items-schema}} 48 | order-schema]))) 49 | 50 | (def mapping (sut/clj<->js-mapping schema {:default-to-camel-case true})) 51 | 52 | (deftest root-reference 53 | (let [root (m/schema [:schema {:registry {::root schema}} ::root]) 54 | clj-map 55 | {:model-type ::order 56 | :order/id "a-root-id-1234" 57 | :order/type "Reference Gear"} 58 | js-obj (sut-tj/to-js clj-map {} root)] 59 | (testing "to-js with a one-off mapping to root reference should work" 60 | (is (m/validate root clj-map)) 61 | (is (= "Reference Gear" (:order/type clj-map))) 62 | (is (= "Reference Gear" (aget js-obj "orderType")))))) 63 | 64 | ;; TODO: 65 | ;; 1. Add another test for references 66 | ;; 2. For duplicate property names in different locations in the schema 67 | 68 | (deftest test-a-js-obj-to-clj 69 | (let [clj-map (sut-tc/to-clj 70 | #js {:modelType ::order 71 | :orderId "a-test-id-1234" 72 | :orderType "Sport Gear" 73 | :totalAmount 23456.89 74 | :user #js {:userId "MrTesty" 75 | :name "Testy The QA"} 76 | :orderItems #js [#js {:orderItem 77 | #js {:type "some-test-order-item-type-1" 78 | :price #js {:currency :EUR 79 | :amount 22.3} 80 | :TESTDummyXYZ "TD-A1" 81 | :relatedItems #js [#js {:credit 82 | #js {:amount 676.30}}]}} 83 | #js {:orderItem 84 | #js {:type "some-test-order-item-type-2" 85 | :price #js {:currency :ZAR 86 | :amount 898} 87 | :TESTDummyXYZ "TD-B2"}}]} 88 | mapping)] 89 | (do 90 | #_(testing "`pr-str` should be clojure readable" 91 | (is (= "{:model-type :malli-ts.data-mapping-test/order, :order/id \"a-test-id-1234\", :order/type \"Sport Gear\", :order/total-amount 23456.89, :order/user {:user/id \"MrTesty\", :user/name \"Testy The QA\"}, :order/items [{:order/item {:order-item/type \"some-test-order-item-type-1\", :order-item/price {:order-item/currency :EUR, :order-item/amount 22.3}, :order-item/test-dummy \"TD-A1\", :order-item/related-items [{:order/credit {:order.credit/amount 676.3}}]}} {:order/item {:order-item/type \"some-test-order-item-type-2\", :order-item/price {:order-item/currency :ZAR, :order-item/amount 898}, :order-item/test-dummy \"TD-B2\"}}]}" (pr-str clj-map)))) 92 | (testing "`to-clj` should map a string" 93 | (is (= "a-test-id-1234" (:order/id clj-map)))) 94 | (testing "`to-clj` should map a number" 95 | (is (= 23456.89 (:order/total-amount clj-map)))) 96 | (testing "`to-clj` should map a value from a nested object" 97 | (is (= "MrTesty" (get-in clj-map [:order/user :user/id])))) 98 | (testing "`to-clj` should map a value from a nested array" 99 | (is (= :EUR (get-in clj-map [:order/items 0 :order/item :order-item/price :order-item/currency]))) 100 | (is (= :ZAR (get-in clj-map [:order/items 1 :order/item :order-item/price :order-item/currency])))) 101 | (testing "`to-clj` should map a value from a property with a different name" 102 | (is (= "TD-B2" (get-in clj-map [:order/items 1 :order/item :order-item/test-dummy])))) 103 | (testing "`to-clj` should map a value from a nested array in a nested array" 104 | (is (= 676.30 (get-in clj-map [:order/items 0 :order/item :order-item/related-items 105 | 0 :order/credit :order.credit/amount]))))))) 106 | 107 | (deftest a-regular-clj-object 108 | (let [clj-map 109 | {:model-type ::order 110 | :order/id "a-test-id-1234" 111 | :order/type "Sport Gear" 112 | :order/total-amount 23456.89 113 | :order/user {:user/id "MrTesty" 114 | :user/name "Testy The QA"} 115 | :order/items [ {:order/item 116 | {:order-item/type "some-test-order-item-type-1" 117 | :order-item/price {:order-item/currency :EUR 118 | :order-item/amount 22.3} 119 | :order-item/test-dummy "TD-A1" 120 | :order-item/related-items [ {:order/credit 121 | {:order.credit/amount 676.30}}]}} 122 | {:order/item 123 | {:order-item/type "some-test-order-item-type-2" 124 | :order-item/price {:order-item/currency :ZAR 125 | :order-item/amount 898} 126 | :order-item/test-dummy "TD-B2"}}]}] 127 | (do 128 | #_(testing "`pr-str` should be clojure readable" 129 | (is (= "{:model-type :malli-ts.data-mapping-test/order, :order/id \"a-test-id-1234\", :order/type \"Sport Gear\", :order/total-amount 23456.89, :order/user {:user/id \"MrTesty\", :user/name \"Testy The QA\"}, :order/items [{:order/item {:order-item/type \"some-test-order-item-type-1\", :order-item/price {:order-item/currency :EUR, :order-item/amount 22.3}, :order-item/test-dummy \"TD-A1\", :order-item/related-items [{:order/credit {:order.credit/amount 676.3}}]}} {:order/item {:order-item/type \"some-test-order-item-type-2\", :order-item/price {:order-item/currency :ZAR, :order-item/amount 898}, :order-item/test-dummy \"TD-B2\"}}]}" (pr-str clj-map)))) 130 | (testing "`to-clj` should map a string" 131 | (is (= "a-test-id-1234" (:order/id clj-map)))) 132 | (testing "`to-clj` should map a number" 133 | (is (= 23456.89 (:order/total-amount clj-map)))) 134 | (testing "`to-clj` should map a value from a nested object" 135 | (is (= "MrTesty" (get-in clj-map [:order/user :user/id])))) 136 | (testing "`to-clj` should map a value from a nested array" 137 | (is (= :EUR (get-in clj-map [:order/items 0 :order/item :order-item/price :order-item/currency]))) 138 | (is (= :ZAR (get-in clj-map [:order/items 1 :order/item :order-item/price :order-item/currency])))) 139 | (testing "`to-clj` should map a value from a property with a different name" 140 | (is (= "TD-B2" (get-in clj-map [:order/items 1 :order/item :order-item/test-dummy])))) 141 | (testing "`to-clj` should map a value from a nested array in a nested array" 142 | (is (= 676.30 (get-in clj-map [:order/items 0 :order/item :order-item/related-items 143 | 0 :order/credit :order.credit/amount]))))))) 144 | 145 | (defn- rand-amount [] (* (rand) 100)) 146 | 147 | (deftest test-js-objs-to-clj 148 | (let [item-count 20 149 | js-objs 150 | (->> 151 | (range item-count) 152 | (map 153 | (fn [i] 154 | #js {:modelType ::order 155 | :orderId (str "a-test-id-" i) 156 | :orderType (str "a-test-wf-type" i) 157 | :totalAmount (rand-amount) 158 | :user #js {:userId (str "MrTesty" i) 159 | :name (str "Testy The QA" i)} 160 | :orderItems #js [#js {:orderItem 161 | #js {:type (str "some-test-order-item-type-A" i) 162 | :price #js {:currency :EUR 163 | :amount (rand-amount)} 164 | :TESTDummyXYZ (str "TD-A" i) 165 | :relatedItems #js [#js {:credit 166 | #js {:amount (inc (rand-amount))}}]}} 167 | #js {:orderItem 168 | #js {:type (str "some-test-order-item-type-B" i) 169 | :price #js {:currency :ZAR 170 | :amount (rand-amount)} 171 | :TESTDummyXYZ (str "TD-B" i)}}]})) 172 | (apply array)) 173 | clj-maps (sut-tc/to-clj js-objs mapping)] 174 | (doall 175 | (keep-indexed 176 | (fn [i clj-map] 177 | (testing "`to-clj` given an array, should map a string" 178 | (is (= (str "a-test-id-" i) (:order/id clj-map)))) 179 | (testing "`to-clj` given an array, should map a number" 180 | (is (number? (:order/total-amount clj-map)))) 181 | (testing "`to-clj` given an array, should map a value from a nested object" 182 | (is (= (str "MrTesty" i) (get-in clj-map [:order/user :user/id])))) 183 | (testing "`to-clj` given an array, should map a value from a nested array" 184 | (is (= :EUR (get-in clj-map [:order/items 0 :order/item :order-item/price :order-item/currency]))) 185 | (is (= :ZAR (get-in clj-map [:order/items 1 :order/item :order-item/price :order-item/currency])))) 186 | (testing "`to-clj` given an array, should map a value from a property with a different name" 187 | (is (= (str "TD-B" i) (get-in clj-map [:order/items 1 :order/item :order-item/test-dummy])))) 188 | (testing "`to-clj` given an array, should map a value from a nested array in a nested array" 189 | (is (< 0 (get-in clj-map [:order/items 0 :order/item :order-item/related-items 190 | 0 :order/credit :order.credit/amount]))))) 191 | clj-maps)))) 192 | 193 | (deftest test-a-clj-map-to-js 194 | (let [order-id "a-test-id-1234" 195 | total-amount 23456.89 196 | user-id "MrTesty" 197 | currency1 :EUR 198 | currency2 :ZAR 199 | test-dummy "TD-B2" 200 | credit-amount 676.30 201 | js-obj (sut-tj/to-js {:model-type ::order 202 | :order/id order-id 203 | :order/type "Sport Gear" 204 | :order/total-amount total-amount 205 | :order/user {:user/id user-id 206 | :user/name "Testy The QA"} 207 | :order/items [{:order/item 208 | {:order-item/type "some-test-order-item-type-1" 209 | :order-item/price {:order-item/currency currency1 210 | :order-item/amount 22.3} 211 | :order-item/test-dummy "TD-A1" 212 | :order-item/related-items [{:order/credit 213 | {:order.credit/amount credit-amount}}]}} 214 | {:order/item 215 | {:order-item/type "some-test-order-item-type-2" 216 | :order-item/price {:order-item/currency currency2 217 | :order-item/amount 898} 218 | :order-item/test-dummy test-dummy}}]} 219 | mapping)] 220 | (testing "`to-js` should map a string" 221 | (is (= order-id (aget js-obj "orderId")))) 222 | (testing "`to-js` should map a number" 223 | (is (= total-amount (aget js-obj "totalAmount")))) 224 | (testing "`to-js` should map a value from a nested map" 225 | (is (= user-id (aget js-obj "user" "userId")))) 226 | (testing "`to-js` should map a value from a nested vector" 227 | (is (= currency1 (aget js-obj "orderItems" 0 "orderItem" "price" "currency"))) 228 | (is (= currency2 (aget js-obj "orderItems" 1 "orderItem" "price" "currency")))) 229 | (testing "`to-js` should map a value to a property with a different name" 230 | (is (= test-dummy (aget js-obj "orderItems" 1 "orderItem" "TESTDummyXYZ")))) 231 | (testing "`to-js` should map a value from a nested vector in a nested vector" 232 | (is (= credit-amount 233 | (aget js-obj "orderItems" 0 "orderItem" "relatedItems" 0 "credit" "amount")))))) 234 | 235 | (deftest test-clj-maps-to-js 236 | (let [item-count 20 237 | order-id "a-test-id-" 238 | order-type "a-test-order-type-" 239 | user-id "MrTesty" 240 | user-name "Testy The QA" 241 | currency1 :EUR 242 | currency2 :ZAR 243 | test-dummy "TD-B" 244 | clj-maps (->> item-count 245 | range 246 | (mapv 247 | (fn [i] 248 | {:model-type ::order 249 | :order/id (str order-id i) 250 | :order/type (str order-type i) 251 | :order/total-amount (rand-amount) 252 | :order/user {:user/id (str user-id i) 253 | :user/name (str user-name i)} 254 | :order/items [{:order/item 255 | {:order-item/type "some-test-order-item-type-1" 256 | :order-item/price {:order-item/currency currency1 257 | :order-item/amount (rand-amount)} 258 | :order-item/test-dummy "TD-A1" 259 | :order-item/related-items [{:order/credit 260 | {:order.credit/amount (rand-amount)}}]}} 261 | {:order/item 262 | {:order-item/type "some-test-order-item-type-2" 263 | :order-item/price {:order-item/currency currency2 264 | :order-item/amount (rand-amount)} 265 | :order-item/test-dummy (str test-dummy i)}}]}))) 266 | js-objs (sut-tj/to-js clj-maps mapping)] 267 | (doall (keep-indexed 268 | (fn [i js-obj] 269 | (testing "`to-js` given a vector, should map a string" 270 | (is (= (str order-id i) (aget js-obj 'orderId)))) 271 | (testing "`to-js` given a vector, should map a number" 272 | (is (number? (aget js-obj "totalAmount")))) 273 | (testing "`to-js` given a vector, should map a value from a nested map" 274 | (is (= (str user-id i) (aget js-obj "user" "userId")))) 275 | (testing "`to-js` given a vector, should map a value from a nested verctor" 276 | (is (= currency1 (aget js-obj "orderItems" 0 "orderItem" "price" "currency"))) 277 | (is (= currency2 (aget js-obj "orderItems" 1 "orderItem" "price" "currency")))) 278 | (testing "`to-js` given a vector, should map a value to a property with a different name" 279 | (is (= (str test-dummy i) (aget js-obj "orderItems" 1 "orderItem" "TESTDummyXYZ")))) 280 | (testing "`to-js` should map a value from a nested vector in a nested vector" 281 | (is (number? (aget js-obj "orderItems" 0 282 | "orderItem" "relatedItems" 0 "credit" "amount"))))) 283 | js-objs)))) 284 | 285 | (doseq [x (range 7)] 286 | ;; Benchmark run above tests 287 | (simple-benchmark [] (a-regular-clj-object) 10000) 288 | (simple-benchmark [] (test-a-js-obj-to-clj) 10000) 289 | (simple-benchmark [] (test-a-clj-map-to-js) 10000) 290 | (println)) 291 | 292 | (comment 293 | (t/run-tests 'malli-ts.data-mapping-test) 294 | 295 | (t/test-vars [#'malli-ts.data-mapping-test/test-a-clj-map-to-js]) 296 | (t/test-vars [#'malli-ts.data-mapping-test/test-js-objs-to-clj]) 297 | 298 | ) 299 | -------------------------------------------------------------------------------- /src/malli_ts/core.cljc: -------------------------------------------------------------------------------- 1 | (ns malli-ts.core 2 | (:require [malli-ts.ast :refer [->ast]] 3 | [malli-ts.core-schema :as core-schemas] 4 | [malli.core :as m] 5 | [malli.registry :as mr] 6 | [camel-snake-kebab.core :as csk] 7 | [clojure.string :as string] 8 | [clojure.set :as set] 9 | #?(:cljs ["path" :as path]))) 10 | 11 | #?(:clj 12 | (defn- get-path 13 | [f] 14 | (java.nio.file.Paths/get f (into-array String [])))) 15 | 16 | (defn- import-path-relative 17 | [f1 f2] 18 | (if-let [absolute (get f2 :absolute)] 19 | absolute 20 | (str "./" 21 | #?(:cljs (path/relative (path/dirname f1) f2) 22 | :clj (let [p1 (.resolve (get-path f1) 23 | ;; quick way to get the parent directory if import has .d.ts extension 24 | (if-not (re-matches #"[.]" f1) ".." ".")) 25 | p2 (get-path f2)] 26 | (.relativize p1 p2)))))) 27 | 28 | (defn- -schema-properties 29 | [?schema options] 30 | (if ?schema 31 | (-> (if (= (m/type ?schema options) ::m/val) 32 | (-> ?schema (m/children options) first) 33 | ?schema) 34 | (m/properties options)) 35 | nil)) 36 | 37 | (defn- -dispatch-parse-ast-node 38 | [node options] 39 | (cond 40 | (if-let [{:keys [schema]} node] 41 | (-> schema (-schema-properties options) ::external-type) 42 | nil) 43 | :external-type 44 | (not (some? node)) :nil-node 45 | (:$ref node) :$ref 46 | (:type node) [:type (:type node)] 47 | (:union node) :union 48 | (:intersection node) :intersection 49 | (some? (:const node)) :const 50 | :else [:type :any])) 51 | 52 | (defmulti ^:private -parse-ast-node 53 | #'-dispatch-parse-ast-node) 54 | 55 | (defn parse-ast-node 56 | ([node options] 57 | (-parse-ast-node node options)) 58 | ([node] 59 | (-parse-ast-node node {}))) 60 | 61 | (defmethod -parse-ast-node :$ref 62 | [{:keys [$ref] :as node} {:keys [deref-type 63 | schema-id->type-options 64 | files-import-alias* 65 | file-imports* 66 | file] 67 | :as options}] 68 | (if (or (= deref-type $ref) (not (get schema-id->type-options $ref))) 69 | (-parse-ast-node 70 | (or (get-in node [:definitions $ref]) 71 | (->ast (:schema node))) 72 | (dissoc options :deref-type)) 73 | (let [ref-file (get-in schema-id->type-options [$ref :file]) 74 | import-alias (or (get @files-import-alias* ref-file) 75 | (get 76 | (swap! 77 | files-import-alias* assoc ref-file 78 | (as-> ref-file $ 79 | (string/split $ #"[./]") 80 | (if (= (take-last 2 $) ["d" "ts"]) 81 | (drop-last 2 $) 82 | $) 83 | (string/join "_" $))) 84 | ref-file)) 85 | ref-type-name (get-in schema-id->type-options [$ref :t-name]) 86 | same-file? (= file ref-file)] 87 | (when-not same-file? 88 | (swap! file-imports* update file set/union #{ref-file})) 89 | (str (if-not same-file? (str import-alias ".") nil) ref-type-name)))) 90 | 91 | (defmethod -parse-ast-node [:type :number] [_ _] "number") 92 | (defmethod -parse-ast-node [:type :string] [_ _] "string") 93 | (defmethod -parse-ast-node [:type :boolean] [_ _] "boolean") 94 | (defmethod -parse-ast-node [:type :date] [_ _] "Date") 95 | (defmethod -parse-ast-node [:type :any] [_ _] "any") 96 | (defmethod -parse-ast-node [:type :undefined] [_ _] "undefined") 97 | (defmethod -parse-ast-node :const [{:keys [const]} options] 98 | (cond 99 | (keyword? const) (str \" (name const) \") 100 | (string? const) (str \" const \") 101 | (some #(% const) [boolean? number?]) (str const) 102 | :else (-parse-ast-node {:type :any} options))) 103 | 104 | (defmethod -parse-ast-node [:type :array] [{:keys [items]} options] 105 | (str "Array<" (-parse-ast-node items options) ">")) 106 | 107 | (comment 108 | (-parse-ast-node {:type :array :items {:type :number}} nil)) 109 | 110 | (defmethod -parse-ast-node [:type :tuple] [{:keys [items]} options] 111 | (str "[" (string/join "," (map #(-parse-ast-node % options) items)) "]")) 112 | 113 | (defmethod -parse-ast-node :union [{items :union} options] 114 | (str "(" (string/join "|" (map #(-parse-ast-node % options) items)) ")")) 115 | 116 | (defmethod -parse-ast-node :intersection [{items :intersection} options] 117 | (str "(" (string/join "&" (map #(-parse-ast-node % options) items)) ")")) 118 | 119 | (defmethod -parse-ast-node [:type :object] [{:keys [properties 120 | index-signature]} 121 | options] 122 | (let [idx-sign-literal (if index-signature 123 | (str "[k:" (-parse-ast-node (first index-signature) options) "]:" 124 | (-parse-ast-node (second index-signature) options)) 125 | nil) 126 | properties-literal (if-not (empty? properties) 127 | (string/join 128 | "," 129 | (map (fn [[k [v opts]]] 130 | (let [property-name (name k)] 131 | (str \" property-name \" 132 | (if (:optional opts) "?" nil) ":" 133 | (-parse-ast-node v options)))) 134 | properties)) 135 | nil)] 136 | (str "{" (string/join "," (filter (comp not string/blank?) 137 | [idx-sign-literal properties-literal])) 138 | "}"))) 139 | 140 | (comment 141 | (-parse-ast-node 142 | (->ast [:map [:a :int] [:b {:optional true} :string]]) 143 | {}) 144 | (-parse-ast-node 145 | (->ast [:map [:a :int] [:b {"optional" true} :string]]) 146 | {})) 147 | 148 | (defmethod -parse-ast-node :external-type [{:keys [schema]} 149 | {:keys [deref-type 150 | file 151 | files-import-alias* 152 | file-imports* 153 | schema-id->type-options] 154 | :as options}] 155 | (let [{:keys [::external-type ::t-path ::t-alias]} (-schema-properties schema options) 156 | 157 | {:keys [external-type t-path t-alias] 158 | :or {external-type external-type t-path t-path t-alias t-alias}} 159 | (get schema-id->type-options deref-type) 160 | 161 | t-path-str (or (:absolute t-path) t-path) 162 | is-imported-already (if t-path-str (@file-imports* t-path) nil) 163 | canonical-alias (if t-path-str (get @files-import-alias* t-path-str) nil) 164 | import-alias (or canonical-alias 165 | (cond 166 | t-alias t-alias 167 | t-path-str (as-> t-path-str $ 168 | (string/replace $ #"\.d\.ts" "") 169 | (string/split $ #"[./]") 170 | (string/join "-" $) 171 | (csk/->snake_case $)) 172 | :else nil))] 173 | (when (and t-path-str (not is-imported-already)) 174 | (swap! file-imports* update file set/union #{t-path})) 175 | (when (and import-alias (not canonical-alias)) 176 | (swap! files-import-alias* assoc t-path-str import-alias)) 177 | (str (if import-alias (str import-alias ".") nil) external-type))) 178 | 179 | (defn- letter-args 180 | ([letter-arg] 181 | (if letter-arg 182 | (let [letter-count (.substring letter-arg 1) 183 | next-count (if-not (empty? letter-count) 184 | (-> letter-count 185 | #?(:cljs js/Number 186 | :clj Integer/parseInt) 187 | inc) 188 | 1) 189 | char-code #?(:cljs (.charCodeAt letter-arg 0) 190 | :clj (int (.charAt letter-arg 0))) 191 | z? (= char-code 122) 192 | next-letter (if-not z? 193 | (let [next-char-code (inc char-code)] 194 | #?(:cljs (.fromCharCode js/String next-char-code) 195 | :clj (char next-char-code))) 196 | "a") 197 | next-letter-arg (str next-letter (if z? next-count letter-count))] 198 | (cons next-letter-arg 199 | (lazy-seq (letter-args next-letter-arg)))) 200 | (cons "a" (lazy-seq (letter-args "a"))))) 201 | ([] (letter-args nil))) 202 | 203 | (comment (->> (take 69 (letter-args)) (take-last 5))) 204 | 205 | (defmethod -parse-ast-node [:type :=>] [{:keys [args ret]} 206 | {:keys [args-names] 207 | :as options}] 208 | (let [args-type (get args :type) 209 | args-items (get args :items) 210 | args-names (cond 211 | args-names args-names 212 | (= args-type :catn) (map (fn [[n]] (csk/->camelCaseString n)) 213 | args-items) 214 | :else (take (count args) (letter-args))) 215 | args (if (= args-type :catn) 216 | (map (fn [[_ a]] a) args-items) 217 | args-items)] 218 | (str "(" 219 | (string/join ", " (map (fn [arg-name arg] 220 | (str arg-name ":" (-parse-ast-node arg options))) 221 | args-names args)) 222 | ") => " (-parse-ast-node ret options)))) 223 | 224 | (comment 225 | (-parse-ast-node 226 | (->ast [:=> [:cat :string :int] [:map [:a :int] [:b :string]]]) {})) 227 | 228 | (defn import-literal 229 | [from alias] 230 | (str "import * as " alias " from " \' from \' \;)) 231 | 232 | (comment 233 | #?(:cljs (import-literal 234 | (path/relative (path/dirname "flow/person/index.d.ts") "flow/index.d.ts") 235 | "flow"))) 236 | 237 | (defn ->type-declaration-str 238 | [type-name literal jsdoc-literal options] 239 | (let [{:keys [export declare]} options] 240 | (str (if jsdoc-literal (str jsdoc-literal \newline) nil) 241 | (if export "export " nil) 242 | (if declare "var " "type ") 243 | type-name (if declare ": " " = ") literal ";"))) 244 | 245 | (defn -dispatch-provide-jsdoc [jsdoc-k _ _ _] jsdoc-k) 246 | 247 | (defmulti provide-jsdoc #'-dispatch-provide-jsdoc) 248 | 249 | #_{:clj-kondo/ignore [:unused-binding]} 250 | (defmethod provide-jsdoc ::schema 251 | [jsdoc-k schema-id t-options options] 252 | ["schema" (binding [*print-namespace-maps* true] (-> schema-id (m/deref options) m/form str))]) 253 | 254 | (defn -jsdoc-literal 255 | [jsdoc-pairs] 256 | (if-not (empty? jsdoc-pairs) 257 | (str "/**\n" 258 | (->> jsdoc-pairs 259 | (map (fn [[attribute value]] (str " * @" attribute " " value))) 260 | (string/join "\n")) 261 | "\n */") 262 | nil)) 263 | 264 | (comment 265 | (println (-jsdoc-literal [["schema" (str '[:map-of any?])] 266 | ["author" "Mr. Poopybutthole"]]))) 267 | 268 | (m/=> transform-parse-files-input-into-schema-id->type-options 269 | [:=> 270 | [:catn 271 | [:file->schema-type-vectors core-schemas/file->schema-type-vectors] 272 | [:options core-schemas/parse-files-options]] 273 | core-schemas/schema-id->type-options]) 274 | 275 | (defn- transform-parse-files-input-into-schema-id->type-options 276 | [file->schema-type-vectors options] 277 | (reduce 278 | (fn [m [file schema-type-vectors]] 279 | (merge m (reduce 280 | (fn [m schema-type-vector] 281 | (let [[schema-id type-options] (if (seqable? schema-type-vector) 282 | schema-type-vector 283 | [schema-type-vector]) 284 | schema-type-options 285 | (into {} 286 | (comp 287 | (filter (fn [[k _]] (= (namespace k) "malli-ts.core"))) 288 | (map (fn [[k v]] [(-> k name keyword) v]))) 289 | (m/properties (m/deref schema-id options))) 290 | type-options (merge type-options 291 | schema-type-options) 292 | type-options (if-not (:t-name type-options) 293 | (assoc type-options :t-name (csk/->snake_case (name schema-id))) 294 | type-options)] 295 | (assoc m schema-id (assoc type-options :file file)))) 296 | {} schema-type-vectors))) 297 | {} file->schema-type-vectors)) 298 | 299 | (m/=> assoc-literals 300 | [:=> 301 | [:catn 302 | [:file->schema-type-vectors core-schemas/file->schema-type-vectors] 303 | [:options core-schemas/assoc-literals-options]] 304 | core-schemas/schema-id->type-options]) 305 | 306 | (defn- assoc-literals 307 | [file->schema-type-vectors 308 | {:keys [schema-id->type-options jsdoc-default] :as options}] 309 | (reduce 310 | (fn [m [file schema-type-vectors]] 311 | (reduce 312 | (fn [m schema-type-vector] 313 | (let [[schema-id type-options] (if (seqable? schema-type-vector) 314 | schema-type-vector 315 | [schema-type-vector]) 316 | {:keys [jsdoc] :as type-options} (merge type-options (get m schema-id)) 317 | literal 318 | (-parse-ast-node 319 | (->ast schema-id options) 320 | (merge options 321 | {:deref-type schema-id 322 | :file file 323 | :t-options type-options})) 324 | jsdoc-literal 325 | (->> (concat jsdoc-default jsdoc) 326 | (map #(provide-jsdoc % schema-id type-options options)) 327 | -jsdoc-literal)] 328 | (-> m 329 | (assoc-in [schema-id :literal] literal) 330 | (assoc-in [schema-id :jsdoc-literal] jsdoc-literal)))) 331 | m schema-type-vectors)) 332 | schema-id->type-options file->schema-type-vectors)) 333 | 334 | (m/=> aggregate-into-file-maps 335 | [:=> 336 | [:catn 337 | [:file->schema-type-vectors core-schemas/file->schema-type-vectors] 338 | [:options core-schemas/assoc-literals-options]] 339 | [:catn 340 | [:file->import-literals [:map-of string? [:sequential string?]]] 341 | [:file->type-literals [:map-of string? [:sequential string?]]]]]) 342 | 343 | (defn- aggregate-into-file-maps 344 | [file->schema-type-vectors 345 | {:keys [schema-id->type-options export-default files-import-alias* file-imports*]}] 346 | (reduce 347 | (fn [[m-import m-type] [file scheva-type-vectors]] 348 | [(assoc 349 | m-import file 350 | (sort (map 351 | (fn [import-file] 352 | (import-literal 353 | (import-path-relative file import-file) 354 | (get @files-import-alias* (or (:absolute import-file) import-file)))) 355 | (get @file-imports* file)))) 356 | (assoc 357 | m-type file 358 | (map 359 | (fn [schema-type-vector] 360 | (let [[schema-id _] (if (seqable? schema-type-vector) 361 | schema-type-vector 362 | [schema-type-vector]) 363 | {:keys [t-name literal jsdoc-literal export] :as t-options} 364 | (get schema-id->type-options schema-id) 365 | t-name (or t-name 366 | (munge (name schema-id)))] 367 | (->type-declaration-str 368 | t-name literal jsdoc-literal 369 | (merge t-options 370 | {:export (if (some? export) export export-default)})))) 371 | scheva-type-vectors))]) 372 | [{} {}] file->schema-type-vectors)) 373 | 374 | (m/=> parse-files 375 | [:=> 376 | [:catn 377 | [:file->schema-type-vectors core-schemas/file->schema-type-vectors] 378 | [:options core-schemas/assoc-literals-options]] 379 | core-schemas/parse-files-return]) 380 | 381 | (defn parse-files 382 | [file->schema-type-vectors options] 383 | (let [{:keys [registry use-default-schemas files-import-alias] 384 | :or {registry {}, use-default-schemas true, files-import-alias {}}} options 385 | 386 | options (merge options {:registry (if use-default-schemas 387 | (merge registry (m/default-schemas)) 388 | registry)}) 389 | 390 | schema-id->type-options 391 | (transform-parse-files-input-into-schema-id->type-options 392 | file->schema-type-vectors options) 393 | 394 | ;; Normalize symbols to strings 395 | files-import-alias (into {} (map (fn [[k v]] [(str k) (str v)]) files-import-alias)) 396 | 397 | options (merge options {:schema-id->type-options schema-id->type-options 398 | :file-imports* (atom {}) 399 | :files-import-alias* (atom files-import-alias)}) 400 | 401 | schema-id->type-options 402 | (assoc-literals file->schema-type-vectors options) 403 | 404 | options (assoc options :schema-id->type-options schema-id->type-options) 405 | 406 | [file->import-literals file->type-literals] 407 | (aggregate-into-file-maps file->schema-type-vectors options) 408 | 409 | file-contents 410 | (reduce (fn [m [file]] 411 | (assoc 412 | m (str file (if-not (re-matches #".*\.d\.ts$" file) ".d.ts" nil)) 413 | (let [import (string/join "\n" (get file->import-literals file)) 414 | types (string/join "\n" (get file->type-literals file))] 415 | (str (if-not (string/blank? import) (str import "\n\n") nil) 416 | types)))) 417 | {} file->schema-type-vectors)] 418 | file-contents)) 419 | 420 | (defn parse-matching-schemas 421 | "Only applicable to qualified schema-types and not defined in malli.core" 422 | {:arglists '([options] 423 | [pred options])} 424 | ([pred {:keys [registry transform] :as options}] 425 | (let [schemas (into [] 426 | (comp (filter (fn [[k s]] 427 | (and (qualified-keyword? k) 428 | (not= "malli.core" (namespace k)) 429 | (pred k s)))) 430 | (map (fn [[k _]] [k {}])) 431 | (map (or transform identity))) 432 | (mr/schemas (mr/composite-registry registry m/default-registry))) 433 | parse-files-arg (persistent! 434 | (reduce 435 | (fn [acc [k opts]] 436 | (let [file-name (csk/->snake_case (namespace k))] 437 | (if-let [asdf (get acc file-name)] 438 | (assoc! acc file-name (conj asdf [k opts])) 439 | (assoc! acc file-name [[k opts]])))) 440 | (transient {}) schemas))] 441 | (parse-files parse-files-arg options))) 442 | ([options] 443 | (parse-matching-schemas (constantly true) options)) 444 | ([] 445 | (parse-matching-schemas (constantly true) {}))) 446 | 447 | (defn parse-ns-schemas 448 | ([ns-coll options] 449 | (parse-matching-schemas 450 | (let [ns-set (into #{} (map str) ns-coll)] 451 | (fn [k _] 452 | (let [k-ns (namespace k)] 453 | (contains? ns-set k-ns)))) 454 | options)) 455 | ([ns-coll] 456 | (parse-ns-schemas ns-coll {}))) 457 | 458 | (defn external-type 459 | [external-type-name & {:keys [import-path import-alias type-name schema]}] 460 | (letfn [(?assoc [m k v] (if v (assoc m k v) m))] 461 | [(or schema any?) 462 | (-> (?assoc {} ::external-type external-type-name) 463 | (?assoc ::t-name type-name) 464 | (?assoc ::t-path (cond 465 | (nil? import-path) nil 466 | (map? import-path) import-path 467 | :else {:absolute import-path})) 468 | (?assoc ::t-alias import-alias))])) 469 | -------------------------------------------------------------------------------- /examples/malli-ts.ts_gen-types.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | (function(){ 3 | var shadow$provide = {}; 4 | 5 | var SHADOW_IMPORT_PATH = __dirname + '/../.shadow-cljs/builds/malli-ts.ts_gen-types/dev/out/cljs-runtime'; 6 | if (__dirname == '.') { SHADOW_IMPORT_PATH = "/home/tiago/flow/malli-ts/.shadow-cljs/builds/malli-ts.ts_gen-types/dev/out/cljs-runtime"; } 7 | global.$CLJS = global; 8 | global.shadow$provide = {}; 9 | try {require('source-map-support').install();} catch (e) {console.warn('no "source-map-support" (run "npm install source-map-support --save-dev" to get it)');} 10 | 11 | global.CLOSURE_NO_DEPS = true; 12 | 13 | global.CLOSURE_DEFINES = {"goog.DEBUG":true,"goog.LOCALE":"en","goog.TRANSPILE":"never","goog.ENABLE_DEBUG_LOADER":false,"cljs.core._STAR_target_STAR_":"nodejs"}; 14 | 15 | var goog = global.goog = {}; 16 | 17 | var SHADOW_IMPORTED = global.SHADOW_IMPORTED = {}; 18 | var PATH = require("path"); 19 | var VM = require("vm"); 20 | var FS = require("fs"); 21 | 22 | var SHADOW_PROVIDE = function(name) { 23 | return goog.exportPath_(name, undefined); 24 | }; 25 | 26 | var SHADOW_REQUIRE = function(name) { 27 | if (goog.isInModuleLoader_()) { 28 | return goog.module.getInternal_(name); 29 | } 30 | return true; 31 | }; 32 | 33 | var SHADOW_WRAP = function(js) { 34 | var code = "(function (require, module, __filename, __dirname) {\n"; 35 | // this is part of goog/base.js and for some reason the only global var not on goog or goog.global 36 | code += "var COMPILED = false;\n" 37 | code += js; 38 | code += "\n});"; 39 | return code; 40 | }; 41 | 42 | var SHADOW_IMPORT = global.SHADOW_IMPORT = function(src) { 43 | if (CLOSURE_DEFINES["shadow.debug"]) { 44 | console.info("SHADOW load:", src); 45 | } 46 | 47 | SHADOW_IMPORTED[src] = true; 48 | 49 | // SHADOW_IMPORT_PATH is an absolute path 50 | var filePath = PATH.resolve(SHADOW_IMPORT_PATH, src); 51 | 52 | var js = FS.readFileSync(filePath); 53 | 54 | var code = SHADOW_WRAP(js); 55 | 56 | var fn = VM.runInThisContext(code, 57 | {filename: filePath, 58 | lineOffset: -2, // see SHADOW_WRAP, adds 2 lines 59 | displayErrors: true 60 | }); 61 | 62 | // the comment is for source-map-support which unfortunately shows the wrong piece of code but the stack is correct 63 | try { 64 | /* ignore this, look at stacktrace */ fn.call(global, require, module, __filename, __dirname); 65 | } catch (e) { 66 | console.error("SHADOW import error", filePath); 67 | throw e; 68 | } 69 | 70 | return true; 71 | }; 72 | 73 | // strip a leading comment as generated for (defn x "foo" [a] a) 74 | // /** 75 | // * foo 76 | // */ 77 | // (function (){ 78 | 79 | function SHADOW_STRIP_COMMENT(js) { 80 | if (!js.startsWith("/*")) { 81 | return js; 82 | } else { 83 | return js.substring(js.indexOf("*/") + 2).trimLeft(); 84 | } 85 | }; 86 | 87 | global.SHADOW_NODE_EVAL = function(js, smJson) { 88 | // special case handling for require since it may otherwise not be available 89 | // FIXME: source maps get destroyed by the strip 90 | js = "(function cljsEval(require) {\n return " + SHADOW_STRIP_COMMENT(js) + "\n});"; 91 | 92 | if (smJson) { 93 | js += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,"; 94 | js += Buffer.from(smJson).toString('base64'); 95 | } 96 | 97 | // console.log(js); 98 | 99 | var fn = VM.runInThisContext.call(global, js, 100 | {filename: "", 101 | lineOffset: -1, // wrapper adds one line on top 102 | displayErrors: true}); 103 | 104 | // console.log("result", fn); 105 | 106 | return fn(require); 107 | }; 108 | 109 | var COMPILED = false; 110 | var goog = goog || {}; 111 | goog.global = global; 112 | goog.global.CLOSURE_UNCOMPILED_DEFINES; 113 | goog.global.CLOSURE_DEFINES; 114 | goog.exportPath_ = function(name, object, overwriteImplicit, objectToExportTo) { 115 | var parts = name.split("."); 116 | var cur = objectToExportTo || goog.global; 117 | if (!(parts[0] in cur) && typeof cur.execScript != "undefined") { 118 | cur.execScript("var " + parts[0]); 119 | } 120 | for (var part; parts.length && (part = parts.shift());) { 121 | if (!parts.length && object !== undefined) { 122 | if (!overwriteImplicit && goog.isObject(object) && goog.isObject(cur[part])) { 123 | for (var prop in object) { 124 | if (object.hasOwnProperty(prop)) { 125 | cur[part][prop] = object[prop]; 126 | } 127 | } 128 | } else { 129 | cur[part] = object; 130 | } 131 | } else { 132 | if (cur[part] && cur[part] !== Object.prototype[part]) { 133 | cur = cur[part]; 134 | } else { 135 | cur = cur[part] = {}; 136 | } 137 | } 138 | } 139 | }; 140 | goog.define = function(name, defaultValue) { 141 | var value = defaultValue; 142 | if (!COMPILED) { 143 | var uncompiledDefines = goog.global.CLOSURE_UNCOMPILED_DEFINES; 144 | var defines = goog.global.CLOSURE_DEFINES; 145 | if (uncompiledDefines && uncompiledDefines.nodeType === undefined && Object.prototype.hasOwnProperty.call(uncompiledDefines, name)) { 146 | value = uncompiledDefines[name]; 147 | } else { 148 | if (defines && defines.nodeType === undefined && Object.prototype.hasOwnProperty.call(defines, name)) { 149 | value = defines[name]; 150 | } 151 | } 152 | } 153 | return value; 154 | }; 155 | goog.FEATURESET_YEAR = goog.define("goog.FEATURESET_YEAR", 2012); 156 | goog.DEBUG = goog.define("goog.DEBUG", true); 157 | goog.LOCALE = goog.define("goog.LOCALE", "en"); 158 | goog.TRUSTED_SITE = goog.define("goog.TRUSTED_SITE", true); 159 | goog.DISALLOW_TEST_ONLY_CODE = goog.define("goog.DISALLOW_TEST_ONLY_CODE", COMPILED && !goog.DEBUG); 160 | goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING = goog.define("goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING", false); 161 | goog.provide = function(name) { 162 | if (goog.isInModuleLoader_()) { 163 | throw new Error("goog.provide cannot be used within a module."); 164 | } 165 | if (!COMPILED) { 166 | if (goog.isProvided_(name)) { 167 | throw new Error('Namespace "' + name + '" already declared.'); 168 | } 169 | } 170 | goog.constructNamespace_(name); 171 | }; 172 | goog.constructNamespace_ = function(name, object, overwriteImplicit) { 173 | if (!COMPILED) { 174 | delete goog.implicitNamespaces_[name]; 175 | var namespace = name; 176 | while (namespace = namespace.substring(0, namespace.lastIndexOf("."))) { 177 | if (goog.getObjectByName(namespace)) { 178 | break; 179 | } 180 | goog.implicitNamespaces_[namespace] = true; 181 | } 182 | } 183 | goog.exportPath_(name, object, overwriteImplicit); 184 | }; 185 | goog.getScriptNonce = function(opt_window) { 186 | if (opt_window && opt_window != goog.global) { 187 | return goog.getScriptNonce_(opt_window.document); 188 | } 189 | if (goog.cspNonce_ === null) { 190 | goog.cspNonce_ = goog.getScriptNonce_(goog.global.document); 191 | } 192 | return goog.cspNonce_; 193 | }; 194 | goog.NONCE_PATTERN_ = /^[\w+/_-]+[=]{0,2}$/; 195 | goog.cspNonce_ = null; 196 | goog.getScriptNonce_ = function(doc) { 197 | var script = doc.querySelector && doc.querySelector("script[nonce]"); 198 | if (script) { 199 | var nonce = script["nonce"] || script.getAttribute("nonce"); 200 | if (nonce && goog.NONCE_PATTERN_.test(nonce)) { 201 | return nonce; 202 | } 203 | } 204 | return ""; 205 | }; 206 | goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/; 207 | goog.module = function(name) { 208 | if (typeof name !== "string" || !name || name.search(goog.VALID_MODULE_RE_) == -1) { 209 | throw new Error("Invalid module identifier"); 210 | } 211 | if (!goog.isInGoogModuleLoader_()) { 212 | throw new Error("Module " + name + " has been loaded incorrectly. Note, " + "modules cannot be loaded as normal scripts. They require some kind of " + "pre-processing step. You're likely trying to load a module via a " + "script tag or as a part of a concatenated bundle without rewriting the " + "module. For more info see: " + "https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide."); 213 | } 214 | if (goog.moduleLoaderState_.moduleName) { 215 | throw new Error("goog.module may only be called once per module."); 216 | } 217 | goog.moduleLoaderState_.moduleName = name; 218 | if (!COMPILED) { 219 | if (goog.isProvided_(name)) { 220 | throw new Error('Namespace "' + name + '" already declared.'); 221 | } 222 | delete goog.implicitNamespaces_[name]; 223 | } 224 | }; 225 | goog.module.get = function(name) { 226 | return goog.module.getInternal_(name); 227 | }; 228 | goog.module.getInternal_ = function(name) { 229 | if (!COMPILED) { 230 | if (name in goog.loadedModules_) { 231 | return goog.loadedModules_[name].exports; 232 | } else { 233 | if (!goog.implicitNamespaces_[name]) { 234 | var ns = goog.getObjectByName(name); 235 | return ns != null ? ns : null; 236 | } 237 | } 238 | } 239 | return null; 240 | }; 241 | goog.ModuleType = {ES6:"es6", GOOG:"goog"}; 242 | goog.moduleLoaderState_ = null; 243 | goog.isInModuleLoader_ = function() { 244 | return goog.isInGoogModuleLoader_() || goog.isInEs6ModuleLoader_(); 245 | }; 246 | goog.isInGoogModuleLoader_ = function() { 247 | return !!goog.moduleLoaderState_ && goog.moduleLoaderState_.type == goog.ModuleType.GOOG; 248 | }; 249 | goog.isInEs6ModuleLoader_ = function() { 250 | var inLoader = !!goog.moduleLoaderState_ && goog.moduleLoaderState_.type == goog.ModuleType.ES6; 251 | if (inLoader) { 252 | return true; 253 | } 254 | var jscomp = goog.global["$jscomp"]; 255 | if (jscomp) { 256 | if (typeof jscomp.getCurrentModulePath != "function") { 257 | return false; 258 | } 259 | return !!jscomp.getCurrentModulePath(); 260 | } 261 | return false; 262 | }; 263 | goog.module.declareLegacyNamespace = function() { 264 | if (!COMPILED && !goog.isInGoogModuleLoader_()) { 265 | throw new Error("goog.module.declareLegacyNamespace must be called from " + "within a goog.module"); 266 | } 267 | if (!COMPILED && !goog.moduleLoaderState_.moduleName) { 268 | throw new Error("goog.module must be called prior to " + "goog.module.declareLegacyNamespace."); 269 | } 270 | goog.moduleLoaderState_.declareLegacyNamespace = true; 271 | }; 272 | goog.declareModuleId = function(namespace) { 273 | if (!COMPILED) { 274 | if (!goog.isInEs6ModuleLoader_()) { 275 | throw new Error("goog.declareModuleId may only be called from " + "within an ES6 module"); 276 | } 277 | if (goog.moduleLoaderState_ && goog.moduleLoaderState_.moduleName) { 278 | throw new Error("goog.declareModuleId may only be called once per module."); 279 | } 280 | if (namespace in goog.loadedModules_) { 281 | throw new Error('Module with namespace "' + namespace + '" already exists.'); 282 | } 283 | } 284 | if (goog.moduleLoaderState_) { 285 | goog.moduleLoaderState_.moduleName = namespace; 286 | } else { 287 | var jscomp = goog.global["$jscomp"]; 288 | if (!jscomp || typeof jscomp.getCurrentModulePath != "function") { 289 | throw new Error('Module with namespace "' + namespace + '" has been loaded incorrectly.'); 290 | } 291 | var exports = jscomp.require(jscomp.getCurrentModulePath()); 292 | goog.loadedModules_[namespace] = {exports:exports, type:goog.ModuleType.ES6, moduleId:namespace}; 293 | } 294 | }; 295 | goog.setTestOnly = function(opt_message) { 296 | if (goog.DISALLOW_TEST_ONLY_CODE) { 297 | opt_message = opt_message || ""; 298 | throw new Error("Importing test-only code into non-debug environment" + (opt_message ? ": " + opt_message : ".")); 299 | } 300 | }; 301 | goog.forwardDeclare = function(name) { 302 | }; 303 | goog.forwardDeclare("Document"); 304 | goog.forwardDeclare("HTMLScriptElement"); 305 | goog.forwardDeclare("XMLHttpRequest"); 306 | if (!COMPILED) { 307 | goog.isProvided_ = function(name) { 308 | return name in goog.loadedModules_ || !goog.implicitNamespaces_[name] && goog.getObjectByName(name) != null; 309 | }; 310 | goog.implicitNamespaces_ = {"goog.module":true}; 311 | } 312 | goog.getObjectByName = function(name, opt_obj) { 313 | var parts = name.split("."); 314 | var cur = opt_obj || goog.global; 315 | for (var i = 0; i < parts.length; i++) { 316 | cur = cur[parts[i]]; 317 | if (cur == null) { 318 | return null; 319 | } 320 | } 321 | return cur; 322 | }; 323 | goog.addDependency = function(relPath, provides, requires, opt_loadFlags) { 324 | if (!COMPILED && goog.DEPENDENCIES_ENABLED) { 325 | goog.debugLoader_.addDependency(relPath, provides, requires, opt_loadFlags); 326 | } 327 | }; 328 | goog.ENABLE_DEBUG_LOADER = goog.define("goog.ENABLE_DEBUG_LOADER", true); 329 | goog.logToConsole_ = function(msg) { 330 | if (goog.global.console) { 331 | goog.global.console["error"](msg); 332 | } 333 | }; 334 | goog.require = function(namespace) { 335 | if (!COMPILED) { 336 | if (goog.ENABLE_DEBUG_LOADER) { 337 | goog.debugLoader_.requested(namespace); 338 | } 339 | if (goog.isProvided_(namespace)) { 340 | if (goog.isInModuleLoader_()) { 341 | return goog.module.getInternal_(namespace); 342 | } 343 | } else { 344 | if (goog.ENABLE_DEBUG_LOADER) { 345 | var moduleLoaderState = goog.moduleLoaderState_; 346 | goog.moduleLoaderState_ = null; 347 | try { 348 | goog.debugLoader_.load_(namespace); 349 | } finally { 350 | goog.moduleLoaderState_ = moduleLoaderState; 351 | } 352 | } 353 | } 354 | return null; 355 | } 356 | }; 357 | goog.requireType = function(namespace) { 358 | return {}; 359 | }; 360 | goog.basePath = ""; 361 | goog.global.CLOSURE_BASE_PATH; 362 | goog.global.CLOSURE_NO_DEPS; 363 | goog.global.CLOSURE_IMPORT_SCRIPT; 364 | goog.nullFunction = function() { 365 | }; 366 | goog.abstractMethod = function() { 367 | throw new Error("unimplemented abstract method"); 368 | }; 369 | goog.addSingletonGetter = function(ctor) { 370 | ctor.instance_ = undefined; 371 | ctor.getInstance = function() { 372 | if (ctor.instance_) { 373 | return ctor.instance_; 374 | } 375 | if (goog.DEBUG) { 376 | goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor; 377 | } 378 | return ctor.instance_ = new ctor; 379 | }; 380 | }; 381 | goog.instantiatedSingletons_ = []; 382 | goog.LOAD_MODULE_USING_EVAL = goog.define("goog.LOAD_MODULE_USING_EVAL", true); 383 | goog.SEAL_MODULE_EXPORTS = goog.define("goog.SEAL_MODULE_EXPORTS", goog.DEBUG); 384 | goog.loadedModules_ = {}; 385 | goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER; 386 | goog.TRANSPILE = goog.define("goog.TRANSPILE", "detect"); 387 | goog.ASSUME_ES_MODULES_TRANSPILED = goog.define("goog.ASSUME_ES_MODULES_TRANSPILED", false); 388 | goog.TRANSPILE_TO_LANGUAGE = goog.define("goog.TRANSPILE_TO_LANGUAGE", ""); 389 | goog.TRANSPILER = goog.define("goog.TRANSPILER", "transpile.js"); 390 | goog.hasBadLetScoping = null; 391 | goog.loadModule = function(moduleDef) { 392 | var previousState = goog.moduleLoaderState_; 393 | try { 394 | goog.moduleLoaderState_ = {moduleName:"", declareLegacyNamespace:false, type:goog.ModuleType.GOOG}; 395 | var origExports = {}; 396 | var exports = origExports; 397 | if (typeof moduleDef === "function") { 398 | exports = moduleDef.call(undefined, exports); 399 | } else { 400 | if (typeof moduleDef === "string") { 401 | exports = goog.loadModuleFromSource_.call(undefined, exports, moduleDef); 402 | } else { 403 | throw new Error("Invalid module definition"); 404 | } 405 | } 406 | var moduleName = goog.moduleLoaderState_.moduleName; 407 | if (typeof moduleName === "string" && moduleName) { 408 | if (goog.moduleLoaderState_.declareLegacyNamespace) { 409 | var isDefaultExport = origExports !== exports; 410 | goog.constructNamespace_(moduleName, exports, isDefaultExport); 411 | } else { 412 | if (goog.SEAL_MODULE_EXPORTS && Object.seal && typeof exports == "object" && exports != null) { 413 | Object.seal(exports); 414 | } 415 | } 416 | var data = {exports:exports, type:goog.ModuleType.GOOG, moduleId:goog.moduleLoaderState_.moduleName}; 417 | goog.loadedModules_[moduleName] = data; 418 | } else { 419 | throw new Error('Invalid module name "' + moduleName + '"'); 420 | } 421 | } finally { 422 | goog.moduleLoaderState_ = previousState; 423 | } 424 | }; 425 | goog.loadModuleFromSource_ = function(exports) { 426 | eval(goog.CLOSURE_EVAL_PREFILTER_.createScript(arguments[1])); 427 | return exports; 428 | }; 429 | goog.normalizePath_ = function(path) { 430 | var components = path.split("/"); 431 | var i = 0; 432 | while (i < components.length) { 433 | if (components[i] == ".") { 434 | components.splice(i, 1); 435 | } else { 436 | if (i && components[i] == ".." && components[i - 1] && components[i - 1] != "..") { 437 | components.splice(--i, 2); 438 | } else { 439 | i++; 440 | } 441 | } 442 | } 443 | return components.join("/"); 444 | }; 445 | goog.global.CLOSURE_LOAD_FILE_SYNC; 446 | goog.loadFileSync_ = function(src) { 447 | if (goog.global.CLOSURE_LOAD_FILE_SYNC) { 448 | return goog.global.CLOSURE_LOAD_FILE_SYNC(src); 449 | } else { 450 | try { 451 | var xhr = new goog.global["XMLHttpRequest"]; 452 | xhr.open("get", src, false); 453 | xhr.send(); 454 | return xhr.status == 0 || xhr.status == 200 ? xhr.responseText : null; 455 | } catch (err) { 456 | return null; 457 | } 458 | } 459 | }; 460 | goog.transpile_ = function(code, path, target) { 461 | var jscomp = goog.global["$jscomp"]; 462 | if (!jscomp) { 463 | goog.global["$jscomp"] = jscomp = {}; 464 | } 465 | var transpile = jscomp.transpile; 466 | if (!transpile) { 467 | var transpilerPath = goog.basePath + goog.TRANSPILER; 468 | var transpilerCode = goog.loadFileSync_(transpilerPath); 469 | if (transpilerCode) { 470 | (function() { 471 | (0, eval)(transpilerCode + "\n//# sourceURL\x3d" + transpilerPath); 472 | }).call(goog.global); 473 | if (goog.global["$gwtExport"] && goog.global["$gwtExport"]["$jscomp"] && !goog.global["$gwtExport"]["$jscomp"]["transpile"]) { 474 | throw new Error('The transpiler did not properly export the "transpile" ' + "method. $gwtExport: " + JSON.stringify(goog.global["$gwtExport"])); 475 | } 476 | goog.global["$jscomp"].transpile = goog.global["$gwtExport"]["$jscomp"]["transpile"]; 477 | jscomp = goog.global["$jscomp"]; 478 | transpile = jscomp.transpile; 479 | } 480 | } 481 | if (!transpile) { 482 | var suffix = " requires transpilation but no transpiler was found."; 483 | transpile = jscomp.transpile = function(code, path) { 484 | goog.logToConsole_(path + suffix); 485 | return code; 486 | }; 487 | } 488 | return transpile(code, path, target); 489 | }; 490 | goog.typeOf = function(value) { 491 | var s = typeof value; 492 | if (s != "object") { 493 | return s; 494 | } 495 | if (!value) { 496 | return "null"; 497 | } 498 | if (Array.isArray(value)) { 499 | return "array"; 500 | } 501 | return s; 502 | }; 503 | goog.isArrayLike = function(val) { 504 | var type = goog.typeOf(val); 505 | return type == "array" || type == "object" && typeof val.length == "number"; 506 | }; 507 | goog.isDateLike = function(val) { 508 | return goog.isObject(val) && typeof val.getFullYear == "function"; 509 | }; 510 | goog.isObject = function(val) { 511 | var type = typeof val; 512 | return type == "object" && val != null || type == "function"; 513 | }; 514 | goog.getUid = function(obj) { 515 | return Object.prototype.hasOwnProperty.call(obj, goog.UID_PROPERTY_) && obj[goog.UID_PROPERTY_] || (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_); 516 | }; 517 | goog.hasUid = function(obj) { 518 | return !!obj[goog.UID_PROPERTY_]; 519 | }; 520 | goog.removeUid = function(obj) { 521 | if (obj !== null && "removeAttribute" in obj) { 522 | obj.removeAttribute(goog.UID_PROPERTY_); 523 | } 524 | try { 525 | delete obj[goog.UID_PROPERTY_]; 526 | } catch (ex) { 527 | } 528 | }; 529 | goog.UID_PROPERTY_ = "closure_uid_" + (Math.random() * 1e9 >>> 0); 530 | goog.uidCounter_ = 0; 531 | goog.cloneObject = function(obj) { 532 | var type = goog.typeOf(obj); 533 | if (type == "object" || type == "array") { 534 | if (typeof obj.clone === "function") { 535 | return obj.clone(); 536 | } 537 | var clone = type == "array" ? [] : {}; 538 | for (var key in obj) { 539 | clone[key] = goog.cloneObject(obj[key]); 540 | } 541 | return clone; 542 | } 543 | return obj; 544 | }; 545 | goog.bindNative_ = function(fn, selfObj, var_args) { 546 | return fn.call.apply(fn.bind, arguments); 547 | }; 548 | goog.bindJs_ = function(fn, selfObj, var_args) { 549 | if (!fn) { 550 | throw new Error; 551 | } 552 | if (arguments.length > 2) { 553 | var boundArgs = Array.prototype.slice.call(arguments, 2); 554 | return function() { 555 | var newArgs = Array.prototype.slice.call(arguments); 556 | Array.prototype.unshift.apply(newArgs, boundArgs); 557 | return fn.apply(selfObj, newArgs); 558 | }; 559 | } else { 560 | return function() { 561 | return fn.apply(selfObj, arguments); 562 | }; 563 | } 564 | }; 565 | goog.bind = function(fn, selfObj, var_args) { 566 | if (Function.prototype.bind && Function.prototype.bind.toString().indexOf("native code") != -1) { 567 | goog.bind = goog.bindNative_; 568 | } else { 569 | goog.bind = goog.bindJs_; 570 | } 571 | return goog.bind.apply(null, arguments); 572 | }; 573 | goog.partial = function(fn, var_args) { 574 | var args = Array.prototype.slice.call(arguments, 1); 575 | return function() { 576 | var newArgs = args.slice(); 577 | newArgs.push.apply(newArgs, arguments); 578 | return fn.apply(this, newArgs); 579 | }; 580 | }; 581 | goog.mixin = function(target, source) { 582 | for (var x in source) { 583 | target[x] = source[x]; 584 | } 585 | }; 586 | goog.now = function() { 587 | return Date.now(); 588 | }; 589 | goog.globalEval = function(script) { 590 | (0, eval)(script); 591 | }; 592 | goog.cssNameMapping_; 593 | goog.cssNameMappingStyle_; 594 | goog.global.CLOSURE_CSS_NAME_MAP_FN; 595 | goog.getCssName = function(className, opt_modifier) { 596 | if (String(className).charAt(0) == ".") { 597 | throw new Error('className passed in goog.getCssName must not start with ".".' + " You passed: " + className); 598 | } 599 | var getMapping = function(cssName) { 600 | return goog.cssNameMapping_[cssName] || cssName; 601 | }; 602 | var renameByParts = function(cssName) { 603 | var parts = cssName.split("-"); 604 | var mapped = []; 605 | for (var i = 0; i < parts.length; i++) { 606 | mapped.push(getMapping(parts[i])); 607 | } 608 | return mapped.join("-"); 609 | }; 610 | var rename; 611 | if (goog.cssNameMapping_) { 612 | rename = goog.cssNameMappingStyle_ == "BY_WHOLE" ? getMapping : renameByParts; 613 | } else { 614 | rename = function(a) { 615 | return a; 616 | }; 617 | } 618 | var result = opt_modifier ? className + "-" + rename(opt_modifier) : rename(className); 619 | if (goog.global.CLOSURE_CSS_NAME_MAP_FN) { 620 | return goog.global.CLOSURE_CSS_NAME_MAP_FN(result); 621 | } 622 | return result; 623 | }; 624 | goog.setCssNameMapping = function(mapping, opt_style) { 625 | goog.cssNameMapping_ = mapping; 626 | goog.cssNameMappingStyle_ = opt_style; 627 | }; 628 | goog.global.CLOSURE_CSS_NAME_MAPPING; 629 | if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) { 630 | goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING; 631 | } 632 | goog.getMsg = function(str, opt_values, opt_options) { 633 | if (opt_options && opt_options.html) { 634 | str = str.replace(/= 0; --i) { 757 | var script = scripts[i]; 758 | var src = script.src; 759 | var qmark = src.lastIndexOf("?"); 760 | var l = qmark == -1 ? src.length : qmark; 761 | if (src.substr(l - 7, 7) == "base.js") { 762 | goog.basePath = src.substr(0, l - 7); 763 | return; 764 | } 765 | } 766 | }; 767 | goog.findBasePath_(); 768 | goog.Transpiler = function() { 769 | this.requiresTranspilation_ = null; 770 | this.transpilationTarget_ = goog.TRANSPILE_TO_LANGUAGE; 771 | }; 772 | goog.Transpiler.prototype.createRequiresTranspilation_ = function() { 773 | var transpilationTarget = "es3"; 774 | var requiresTranspilation = {"es3":false}; 775 | var transpilationRequiredForAllLaterModes = false; 776 | function addNewerLanguageTranspilationCheck(modeName, isSupported) { 777 | if (transpilationRequiredForAllLaterModes) { 778 | requiresTranspilation[modeName] = true; 779 | } else { 780 | if (isSupported()) { 781 | transpilationTarget = modeName; 782 | requiresTranspilation[modeName] = false; 783 | } else { 784 | requiresTranspilation[modeName] = true; 785 | transpilationRequiredForAllLaterModes = true; 786 | } 787 | } 788 | } 789 | function evalCheck(code) { 790 | try { 791 | return !!eval(code); 792 | } catch (ignored) { 793 | return false; 794 | } 795 | } 796 | var userAgent = goog.global.navigator && goog.global.navigator.userAgent ? goog.global.navigator.userAgent : ""; 797 | addNewerLanguageTranspilationCheck("es5", function() { 798 | return evalCheck("[1,].length\x3d\x3d1"); 799 | }); 800 | addNewerLanguageTranspilationCheck("es6", function() { 801 | var re = /Edge\/(\d+)(\.\d)*/i; 802 | var edgeUserAgent = userAgent.match(re); 803 | if (edgeUserAgent) { 804 | return false; 805 | } 806 | var es6fullTest = "class X{constructor(){if(new.target!\x3dString)throw 1;this.x\x3d42}}" + "let q\x3dReflect.construct(X,[],String);if(q.x!\x3d42||!(q instanceof " + "String))throw 1;for(const a of[2,3]){if(a\x3d\x3d2)continue;function " + "f(z\x3d{a}){let a\x3d0;return z.a}{function f(){return 0;}}return f()" + "\x3d\x3d3}"; 807 | return evalCheck('(()\x3d\x3e{"use strict";' + es6fullTest + "})()"); 808 | }); 809 | addNewerLanguageTranspilationCheck("es7", function() { 810 | return evalCheck("2**3\x3d\x3d8"); 811 | }); 812 | addNewerLanguageTranspilationCheck("es8", function() { 813 | return evalCheck("async()\x3d\x3e1,1"); 814 | }); 815 | addNewerLanguageTranspilationCheck("es9", function() { 816 | return evalCheck("({...rest}\x3d{}),1"); 817 | }); 818 | addNewerLanguageTranspilationCheck("es_2019", function() { 819 | return evalCheck('let r;try{throw 0}catch{r\x3d"\u2029"};r'); 820 | }); 821 | addNewerLanguageTranspilationCheck("es_2020", function() { 822 | return evalCheck("null?.x??1"); 823 | }); 824 | addNewerLanguageTranspilationCheck("es_next", function() { 825 | return false; 826 | }); 827 | return {target:transpilationTarget, map:requiresTranspilation}; 828 | }; 829 | goog.Transpiler.prototype.needsTranspile = function(lang, module) { 830 | if (goog.TRANSPILE == "always") { 831 | return true; 832 | } else { 833 | if (goog.TRANSPILE == "never") { 834 | return false; 835 | } else { 836 | if (!this.requiresTranspilation_) { 837 | var obj = this.createRequiresTranspilation_(); 838 | this.requiresTranspilation_ = obj.map; 839 | this.transpilationTarget_ = this.transpilationTarget_ || obj.target; 840 | } 841 | } 842 | } 843 | if (lang in this.requiresTranspilation_) { 844 | if (this.requiresTranspilation_[lang]) { 845 | return true; 846 | } else { 847 | if (goog.inHtmlDocument_() && module == "es6" && !("noModule" in goog.global.document.createElement("script"))) { 848 | return true; 849 | } else { 850 | return false; 851 | } 852 | } 853 | } else { 854 | throw new Error("Unknown language mode: " + lang); 855 | } 856 | }; 857 | goog.Transpiler.prototype.transpile = function(code, path) { 858 | return goog.transpile_(code, path, this.transpilationTarget_); 859 | }; 860 | goog.transpiler_ = new goog.Transpiler; 861 | goog.protectScriptTag_ = function(str) { 862 | return str.replace(/<\/(SCRIPT)/ig, "\\x3c/$1"); 863 | }; 864 | goog.DebugLoader_ = function() { 865 | this.dependencies_ = {}; 866 | this.idToPath_ = {}; 867 | this.written_ = {}; 868 | this.loadingDeps_ = []; 869 | this.depsToLoad_ = []; 870 | this.paused_ = false; 871 | this.factory_ = new goog.DependencyFactory(goog.transpiler_); 872 | this.deferredCallbacks_ = {}; 873 | this.deferredQueue_ = []; 874 | }; 875 | goog.DebugLoader_.prototype.bootstrap = function(namespaces, callback) { 876 | var cb = callback; 877 | function resolve() { 878 | if (cb) { 879 | goog.global.setTimeout(cb, 0); 880 | cb = null; 881 | } 882 | } 883 | if (!namespaces.length) { 884 | resolve(); 885 | return; 886 | } 887 | var deps = []; 888 | for (var i = 0; i < namespaces.length; i++) { 889 | var path = this.getPathFromDeps_(namespaces[i]); 890 | if (!path) { 891 | throw new Error("Unregonized namespace: " + namespaces[i]); 892 | } 893 | deps.push(this.dependencies_[path]); 894 | } 895 | var require = goog.require; 896 | var loaded = 0; 897 | for (var i = 0; i < namespaces.length; i++) { 898 | require(namespaces[i]); 899 | deps[i].onLoad(function() { 900 | if (++loaded == namespaces.length) { 901 | resolve(); 902 | } 903 | }); 904 | } 905 | }; 906 | goog.DebugLoader_.prototype.loadClosureDeps = function() { 907 | var relPath = "deps.js"; 908 | this.depsToLoad_.push(this.factory_.createDependency(goog.normalizePath_(goog.basePath + relPath), relPath, [], [], {}, false)); 909 | this.loadDeps_(); 910 | }; 911 | goog.DebugLoader_.prototype.requested = function(absPathOrId, opt_force) { 912 | var path = this.getPathFromDeps_(absPathOrId); 913 | if (path && (opt_force || this.areDepsLoaded_(this.dependencies_[path].requires))) { 914 | var callback = this.deferredCallbacks_[path]; 915 | if (callback) { 916 | delete this.deferredCallbacks_[path]; 917 | callback(); 918 | } 919 | } 920 | }; 921 | goog.DebugLoader_.prototype.setDependencyFactory = function(factory) { 922 | this.factory_ = factory; 923 | }; 924 | goog.DebugLoader_.prototype.load_ = function(namespace) { 925 | if (!this.getPathFromDeps_(namespace)) { 926 | var errorMessage = "goog.require could not find: " + namespace; 927 | goog.logToConsole_(errorMessage); 928 | } else { 929 | var loader = this; 930 | var deps = []; 931 | var visit = function(namespace) { 932 | var path = loader.getPathFromDeps_(namespace); 933 | if (!path) { 934 | throw new Error("Bad dependency path or symbol: " + namespace); 935 | } 936 | if (loader.written_[path]) { 937 | return; 938 | } 939 | loader.written_[path] = true; 940 | var dep = loader.dependencies_[path]; 941 | for (var i = 0; i < dep.requires.length; i++) { 942 | if (!goog.isProvided_(dep.requires[i])) { 943 | visit(dep.requires[i]); 944 | } 945 | } 946 | deps.push(dep); 947 | }; 948 | visit(namespace); 949 | var wasLoading = !!this.depsToLoad_.length; 950 | this.depsToLoad_ = this.depsToLoad_.concat(deps); 951 | if (!this.paused_ && !wasLoading) { 952 | this.loadDeps_(); 953 | } 954 | } 955 | }; 956 | goog.DebugLoader_.prototype.loadDeps_ = function() { 957 | var loader = this; 958 | var paused = this.paused_; 959 | while (this.depsToLoad_.length && !paused) { 960 | (function() { 961 | var loadCallDone = false; 962 | var dep = loader.depsToLoad_.shift(); 963 | var loaded = false; 964 | loader.loading_(dep); 965 | var controller = {pause:function() { 966 | if (loadCallDone) { 967 | throw new Error("Cannot call pause after the call to load."); 968 | } else { 969 | paused = true; 970 | } 971 | }, resume:function() { 972 | if (loadCallDone) { 973 | loader.resume_(); 974 | } else { 975 | paused = false; 976 | } 977 | }, loaded:function() { 978 | if (loaded) { 979 | throw new Error("Double call to loaded."); 980 | } 981 | loaded = true; 982 | loader.loaded_(dep); 983 | }, pending:function() { 984 | var pending = []; 985 | for (var i = 0; i < loader.loadingDeps_.length; i++) { 986 | pending.push(loader.loadingDeps_[i]); 987 | } 988 | return pending; 989 | }, setModuleState:function(type) { 990 | goog.moduleLoaderState_ = {type:type, moduleName:"", declareLegacyNamespace:false}; 991 | }, registerEs6ModuleExports:function(path, exports, opt_closureNamespace) { 992 | if (opt_closureNamespace) { 993 | goog.loadedModules_[opt_closureNamespace] = {exports:exports, type:goog.ModuleType.ES6, moduleId:opt_closureNamespace || ""}; 994 | } 995 | }, registerGoogModuleExports:function(moduleId, exports) { 996 | goog.loadedModules_[moduleId] = {exports:exports, type:goog.ModuleType.GOOG, moduleId:moduleId}; 997 | }, clearModuleState:function() { 998 | goog.moduleLoaderState_ = null; 999 | }, defer:function(callback) { 1000 | if (loadCallDone) { 1001 | throw new Error("Cannot register with defer after the call to load."); 1002 | } 1003 | loader.defer_(dep, callback); 1004 | }, areDepsLoaded:function() { 1005 | return loader.areDepsLoaded_(dep.requires); 1006 | }}; 1007 | try { 1008 | dep.load(controller); 1009 | } finally { 1010 | loadCallDone = true; 1011 | } 1012 | })(); 1013 | } 1014 | if (paused) { 1015 | this.pause_(); 1016 | } 1017 | }; 1018 | goog.DebugLoader_.prototype.pause_ = function() { 1019 | this.paused_ = true; 1020 | }; 1021 | goog.DebugLoader_.prototype.resume_ = function() { 1022 | if (this.paused_) { 1023 | this.paused_ = false; 1024 | this.loadDeps_(); 1025 | } 1026 | }; 1027 | goog.DebugLoader_.prototype.loading_ = function(dep) { 1028 | this.loadingDeps_.push(dep); 1029 | }; 1030 | goog.DebugLoader_.prototype.loaded_ = function(dep) { 1031 | for (var i = 0; i < this.loadingDeps_.length; i++) { 1032 | if (this.loadingDeps_[i] == dep) { 1033 | this.loadingDeps_.splice(i, 1); 1034 | break; 1035 | } 1036 | } 1037 | for (var i = 0; i < this.deferredQueue_.length; i++) { 1038 | if (this.deferredQueue_[i] == dep.path) { 1039 | this.deferredQueue_.splice(i, 1); 1040 | break; 1041 | } 1042 | } 1043 | if (this.loadingDeps_.length == this.deferredQueue_.length && !this.depsToLoad_.length) { 1044 | while (this.deferredQueue_.length) { 1045 | this.requested(this.deferredQueue_.shift(), true); 1046 | } 1047 | } 1048 | dep.loaded(); 1049 | }; 1050 | goog.DebugLoader_.prototype.areDepsLoaded_ = function(pathsOrIds) { 1051 | for (var i = 0; i < pathsOrIds.length; i++) { 1052 | var path = this.getPathFromDeps_(pathsOrIds[i]); 1053 | if (!path || !(path in this.deferredCallbacks_) && !goog.isProvided_(pathsOrIds[i])) { 1054 | return false; 1055 | } 1056 | } 1057 | return true; 1058 | }; 1059 | goog.DebugLoader_.prototype.getPathFromDeps_ = function(absPathOrId) { 1060 | if (absPathOrId in this.idToPath_) { 1061 | return this.idToPath_[absPathOrId]; 1062 | } else { 1063 | if (absPathOrId in this.dependencies_) { 1064 | return absPathOrId; 1065 | } else { 1066 | return null; 1067 | } 1068 | } 1069 | }; 1070 | goog.DebugLoader_.prototype.defer_ = function(dependency, callback) { 1071 | this.deferredCallbacks_[dependency.path] = callback; 1072 | this.deferredQueue_.push(dependency.path); 1073 | }; 1074 | goog.LoadController = function() { 1075 | }; 1076 | goog.LoadController.prototype.pause = function() { 1077 | }; 1078 | goog.LoadController.prototype.resume = function() { 1079 | }; 1080 | goog.LoadController.prototype.loaded = function() { 1081 | }; 1082 | goog.LoadController.prototype.pending = function() { 1083 | }; 1084 | goog.LoadController.prototype.registerEs6ModuleExports = function(path, exports, opt_closureNamespace) { 1085 | }; 1086 | goog.LoadController.prototype.setModuleState = function(type) { 1087 | }; 1088 | goog.LoadController.prototype.clearModuleState = function() { 1089 | }; 1090 | goog.LoadController.prototype.defer = function(callback) { 1091 | }; 1092 | goog.LoadController.prototype.areDepsLoaded = function() { 1093 | }; 1094 | goog.Dependency = function(path, relativePath, provides, requires, loadFlags) { 1095 | this.path = path; 1096 | this.relativePath = relativePath; 1097 | this.provides = provides; 1098 | this.requires = requires; 1099 | this.loadFlags = loadFlags; 1100 | this.loaded_ = false; 1101 | this.loadCallbacks_ = []; 1102 | }; 1103 | goog.Dependency.prototype.getPathName = function() { 1104 | var pathName = this.path; 1105 | var protocolIndex = pathName.indexOf("://"); 1106 | if (protocolIndex >= 0) { 1107 | pathName = pathName.substring(protocolIndex + 3); 1108 | var slashIndex = pathName.indexOf("/"); 1109 | if (slashIndex >= 0) { 1110 | pathName = pathName.substring(slashIndex + 1); 1111 | } 1112 | } 1113 | return pathName; 1114 | }; 1115 | goog.Dependency.prototype.onLoad = function(callback) { 1116 | if (this.loaded_) { 1117 | callback(); 1118 | } else { 1119 | this.loadCallbacks_.push(callback); 1120 | } 1121 | }; 1122 | goog.Dependency.prototype.loaded = function() { 1123 | this.loaded_ = true; 1124 | var callbacks = this.loadCallbacks_; 1125 | this.loadCallbacks_ = []; 1126 | for (var i = 0; i < callbacks.length; i++) { 1127 | callbacks[i](); 1128 | } 1129 | }; 1130 | goog.Dependency.defer_ = false; 1131 | goog.Dependency.callbackMap_ = {}; 1132 | goog.Dependency.registerCallback_ = function(callback) { 1133 | var key = Math.random().toString(32); 1134 | goog.Dependency.callbackMap_[key] = callback; 1135 | return key; 1136 | }; 1137 | goog.Dependency.unregisterCallback_ = function(key) { 1138 | delete goog.Dependency.callbackMap_[key]; 1139 | }; 1140 | goog.Dependency.callback_ = function(key, var_args) { 1141 | if (key in goog.Dependency.callbackMap_) { 1142 | var callback = goog.Dependency.callbackMap_[key]; 1143 | var args = []; 1144 | for (var i = 1; i < arguments.length; i++) { 1145 | args.push(arguments[i]); 1146 | } 1147 | callback.apply(undefined, args); 1148 | } else { 1149 | var errorMessage = "Callback key " + key + " does not exist (was base.js loaded more than once?)."; 1150 | throw Error(errorMessage); 1151 | } 1152 | }; 1153 | goog.Dependency.prototype.load = function(controller) { 1154 | if (goog.global.CLOSURE_IMPORT_SCRIPT) { 1155 | if (goog.global.CLOSURE_IMPORT_SCRIPT(this.path)) { 1156 | controller.loaded(); 1157 | } else { 1158 | controller.pause(); 1159 | } 1160 | return; 1161 | } 1162 | if (!goog.inHtmlDocument_()) { 1163 | goog.logToConsole_("Cannot use default debug loader outside of HTML documents."); 1164 | if (this.relativePath == "deps.js") { 1165 | goog.logToConsole_("Consider setting CLOSURE_IMPORT_SCRIPT before loading base.js, " + "or setting CLOSURE_NO_DEPS to true."); 1166 | controller.loaded(); 1167 | } else { 1168 | controller.pause(); 1169 | } 1170 | return; 1171 | } 1172 | var doc = goog.global.document; 1173 | if (doc.readyState == "complete" && !goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) { 1174 | var isDeps = /\bdeps.js$/.test(this.path); 1175 | if (isDeps) { 1176 | controller.loaded(); 1177 | return; 1178 | } else { 1179 | throw Error('Cannot write "' + this.path + '" after document load'); 1180 | } 1181 | } 1182 | var nonce = goog.getScriptNonce(); 1183 | if (!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING && goog.isDocumentLoading_()) { 1184 | var key; 1185 | var callback = function(script) { 1186 | if (script.readyState && script.readyState != "complete") { 1187 | script.onload = callback; 1188 | return; 1189 | } 1190 | goog.Dependency.unregisterCallback_(key); 1191 | controller.loaded(); 1192 | }; 1193 | key = goog.Dependency.registerCallback_(callback); 1194 | var defer = goog.Dependency.defer_ ? " defer" : ""; 1195 | var nonceAttr = nonce ? ' nonce\x3d"' + nonce + '"' : ""; 1196 | var script = '\x3cscript src\x3d"' + this.path + '"' + nonceAttr + defer + ' id\x3d"script-' + key + '"\x3e\x3c/script\x3e'; 1197 | script += "\x3cscript" + nonceAttr + "\x3e"; 1198 | if (goog.Dependency.defer_) { 1199 | script += "document.getElementById('script-" + key + "').onload \x3d function() {\n" + " goog.Dependency.callback_('" + key + "', this);\n" + "};\n"; 1200 | } else { 1201 | script += "goog.Dependency.callback_('" + key + "', document.getElementById('script-" + key + "'));"; 1202 | } 1203 | script += "\x3c/script\x3e"; 1204 | doc.write(goog.TRUSTED_TYPES_POLICY_ ? goog.TRUSTED_TYPES_POLICY_.createHTML(script) : script); 1205 | } else { 1206 | var scriptEl = doc.createElement("script"); 1207 | scriptEl.defer = goog.Dependency.defer_; 1208 | scriptEl.async = false; 1209 | if (nonce) { 1210 | scriptEl.nonce = nonce; 1211 | } 1212 | if (goog.DebugLoader_.IS_OLD_IE_) { 1213 | controller.pause(); 1214 | scriptEl.onreadystatechange = function() { 1215 | if (scriptEl.readyState == "loaded" || scriptEl.readyState == "complete") { 1216 | controller.loaded(); 1217 | controller.resume(); 1218 | } 1219 | }; 1220 | } else { 1221 | scriptEl.onload = function() { 1222 | scriptEl.onload = null; 1223 | controller.loaded(); 1224 | }; 1225 | } 1226 | scriptEl.src = goog.TRUSTED_TYPES_POLICY_ ? goog.TRUSTED_TYPES_POLICY_.createScriptURL(this.path) : this.path; 1227 | doc.head.appendChild(scriptEl); 1228 | } 1229 | }; 1230 | goog.Es6ModuleDependency = function(path, relativePath, provides, requires, loadFlags) { 1231 | goog.Es6ModuleDependency.base(this, "constructor", path, relativePath, provides, requires, loadFlags); 1232 | }; 1233 | goog.inherits(goog.Es6ModuleDependency, goog.Dependency); 1234 | goog.Es6ModuleDependency.prototype.load = function(controller) { 1235 | if (goog.global.CLOSURE_IMPORT_SCRIPT) { 1236 | if (goog.global.CLOSURE_IMPORT_SCRIPT(this.path)) { 1237 | controller.loaded(); 1238 | } else { 1239 | controller.pause(); 1240 | } 1241 | return; 1242 | } 1243 | if (!goog.inHtmlDocument_()) { 1244 | goog.logToConsole_("Cannot use default debug loader outside of HTML documents."); 1245 | controller.pause(); 1246 | return; 1247 | } 1248 | var doc = goog.global.document; 1249 | var dep = this; 1250 | function write(src, contents) { 1251 | var nonceAttr = ""; 1252 | var nonce = goog.getScriptNonce(); 1253 | if (nonce) { 1254 | nonceAttr = ' nonce\x3d"' + nonce + '"'; 1255 | } 1256 | if (contents) { 1257 | var script = '\x3cscript type\x3d"module" crossorigin' + nonceAttr + "\x3e" + contents + "\x3c/" + "script\x3e"; 1258 | doc.write(goog.TRUSTED_TYPES_POLICY_ ? goog.TRUSTED_TYPES_POLICY_.createHTML(script) : script); 1259 | } else { 1260 | var script = '\x3cscript type\x3d"module" crossorigin src\x3d"' + src + '"' + nonceAttr + "\x3e\x3c/" + "script\x3e"; 1261 | doc.write(goog.TRUSTED_TYPES_POLICY_ ? goog.TRUSTED_TYPES_POLICY_.createHTML(script) : script); 1262 | } 1263 | } 1264 | function append(src, contents) { 1265 | var scriptEl = doc.createElement("script"); 1266 | scriptEl.defer = true; 1267 | scriptEl.async = false; 1268 | scriptEl.type = "module"; 1269 | scriptEl.setAttribute("crossorigin", true); 1270 | var nonce = goog.getScriptNonce(); 1271 | if (nonce) { 1272 | scriptEl.nonce = nonce; 1273 | } 1274 | if (contents) { 1275 | scriptEl.text = goog.TRUSTED_TYPES_POLICY_ ? goog.TRUSTED_TYPES_POLICY_.createScript(contents) : contents; 1276 | } else { 1277 | scriptEl.src = goog.TRUSTED_TYPES_POLICY_ ? goog.TRUSTED_TYPES_POLICY_.createScriptURL(src) : src; 1278 | } 1279 | doc.head.appendChild(scriptEl); 1280 | } 1281 | var create; 1282 | if (goog.isDocumentLoading_()) { 1283 | create = write; 1284 | goog.Dependency.defer_ = true; 1285 | } else { 1286 | create = append; 1287 | } 1288 | var beforeKey = goog.Dependency.registerCallback_(function() { 1289 | goog.Dependency.unregisterCallback_(beforeKey); 1290 | controller.setModuleState(goog.ModuleType.ES6); 1291 | }); 1292 | create(undefined, 'goog.Dependency.callback_("' + beforeKey + '")'); 1293 | create(this.path, undefined); 1294 | var registerKey = goog.Dependency.registerCallback_(function(exports) { 1295 | goog.Dependency.unregisterCallback_(registerKey); 1296 | controller.registerEs6ModuleExports(dep.path, exports, goog.moduleLoaderState_.moduleName); 1297 | }); 1298 | create(undefined, 'import * as m from "' + this.path + '"; goog.Dependency.callback_("' + registerKey + '", m)'); 1299 | var afterKey = goog.Dependency.registerCallback_(function() { 1300 | goog.Dependency.unregisterCallback_(afterKey); 1301 | controller.clearModuleState(); 1302 | controller.loaded(); 1303 | }); 1304 | create(undefined, 'goog.Dependency.callback_("' + afterKey + '")'); 1305 | }; 1306 | goog.TransformedDependency = function(path, relativePath, provides, requires, loadFlags) { 1307 | goog.TransformedDependency.base(this, "constructor", path, relativePath, provides, requires, loadFlags); 1308 | this.contents_ = null; 1309 | this.lazyFetch_ = !goog.inHtmlDocument_() || !("noModule" in goog.global.document.createElement("script")); 1310 | }; 1311 | goog.inherits(goog.TransformedDependency, goog.Dependency); 1312 | goog.TransformedDependency.prototype.load = function(controller) { 1313 | var dep = this; 1314 | function fetch() { 1315 | dep.contents_ = goog.loadFileSync_(dep.path); 1316 | if (dep.contents_) { 1317 | dep.contents_ = dep.transform(dep.contents_); 1318 | if (dep.contents_) { 1319 | dep.contents_ += "\n//# sourceURL\x3d" + dep.path; 1320 | } 1321 | } 1322 | } 1323 | if (goog.global.CLOSURE_IMPORT_SCRIPT) { 1324 | fetch(); 1325 | if (this.contents_ && goog.global.CLOSURE_IMPORT_SCRIPT("", this.contents_)) { 1326 | this.contents_ = null; 1327 | controller.loaded(); 1328 | } else { 1329 | controller.pause(); 1330 | } 1331 | return; 1332 | } 1333 | var isEs6 = this.loadFlags["module"] == goog.ModuleType.ES6; 1334 | if (!this.lazyFetch_) { 1335 | fetch(); 1336 | } 1337 | function load() { 1338 | if (dep.lazyFetch_) { 1339 | fetch(); 1340 | } 1341 | if (!dep.contents_) { 1342 | return; 1343 | } 1344 | if (isEs6) { 1345 | controller.setModuleState(goog.ModuleType.ES6); 1346 | } 1347 | var namespace; 1348 | try { 1349 | var contents = dep.contents_; 1350 | dep.contents_ = null; 1351 | goog.globalEval(contents); 1352 | if (isEs6) { 1353 | namespace = goog.moduleLoaderState_.moduleName; 1354 | } 1355 | } finally { 1356 | if (isEs6) { 1357 | controller.clearModuleState(); 1358 | } 1359 | } 1360 | if (isEs6) { 1361 | goog.global["$jscomp"]["require"]["ensure"]([dep.getPathName()], function() { 1362 | controller.registerEs6ModuleExports(dep.path, goog.global["$jscomp"]["require"](dep.getPathName()), namespace); 1363 | }); 1364 | } 1365 | controller.loaded(); 1366 | } 1367 | function fetchInOwnScriptThenLoad() { 1368 | var doc = goog.global.document; 1369 | var key = goog.Dependency.registerCallback_(function() { 1370 | goog.Dependency.unregisterCallback_(key); 1371 | load(); 1372 | }); 1373 | var nonce = goog.getScriptNonce(); 1374 | var nonceAttr = nonce ? ' nonce\x3d"' + nonce + '"' : ""; 1375 | var script = "\x3cscript" + nonceAttr + "\x3e" + goog.protectScriptTag_('goog.Dependency.callback_("' + key + '");') + "\x3c/" + "script\x3e"; 1376 | doc.write(goog.TRUSTED_TYPES_POLICY_ ? goog.TRUSTED_TYPES_POLICY_.createHTML(script) : script); 1377 | } 1378 | var anythingElsePending = controller.pending().length > 1; 1379 | var useOldIeWorkAround = anythingElsePending && goog.DebugLoader_.IS_OLD_IE_; 1380 | var needsAsyncLoading = goog.Dependency.defer_ && (anythingElsePending || goog.isDocumentLoading_()); 1381 | if (useOldIeWorkAround || needsAsyncLoading) { 1382 | controller.defer(function() { 1383 | load(); 1384 | }); 1385 | return; 1386 | } 1387 | var doc = goog.global.document; 1388 | var isInternetExplorer = goog.inHtmlDocument_() && "ActiveXObject" in goog.global; 1389 | if (isEs6 && goog.inHtmlDocument_() && goog.isDocumentLoading_() && !isInternetExplorer) { 1390 | goog.Dependency.defer_ = true; 1391 | controller.pause(); 1392 | var oldCallback = doc.onreadystatechange; 1393 | doc.onreadystatechange = function() { 1394 | if (doc.readyState == "interactive") { 1395 | doc.onreadystatechange = oldCallback; 1396 | load(); 1397 | controller.resume(); 1398 | } 1399 | if (typeof oldCallback === "function") { 1400 | oldCallback.apply(undefined, arguments); 1401 | } 1402 | }; 1403 | } else { 1404 | if (goog.DebugLoader_.IS_OLD_IE_ || !goog.inHtmlDocument_() || !goog.isDocumentLoading_()) { 1405 | load(); 1406 | } else { 1407 | fetchInOwnScriptThenLoad(); 1408 | } 1409 | } 1410 | }; 1411 | goog.TransformedDependency.prototype.transform = function(contents) { 1412 | }; 1413 | goog.TranspiledDependency = function(path, relativePath, provides, requires, loadFlags, transpiler) { 1414 | goog.TranspiledDependency.base(this, "constructor", path, relativePath, provides, requires, loadFlags); 1415 | this.transpiler = transpiler; 1416 | }; 1417 | goog.inherits(goog.TranspiledDependency, goog.TransformedDependency); 1418 | goog.TranspiledDependency.prototype.transform = function(contents) { 1419 | return this.transpiler.transpile(contents, this.getPathName()); 1420 | }; 1421 | goog.PreTranspiledEs6ModuleDependency = function(path, relativePath, provides, requires, loadFlags) { 1422 | goog.PreTranspiledEs6ModuleDependency.base(this, "constructor", path, relativePath, provides, requires, loadFlags); 1423 | }; 1424 | goog.inherits(goog.PreTranspiledEs6ModuleDependency, goog.TransformedDependency); 1425 | goog.PreTranspiledEs6ModuleDependency.prototype.transform = function(contents) { 1426 | return contents; 1427 | }; 1428 | goog.GoogModuleDependency = function(path, relativePath, provides, requires, loadFlags, needsTranspile, transpiler) { 1429 | goog.GoogModuleDependency.base(this, "constructor", path, relativePath, provides, requires, loadFlags); 1430 | this.needsTranspile_ = needsTranspile; 1431 | this.transpiler_ = transpiler; 1432 | }; 1433 | goog.inherits(goog.GoogModuleDependency, goog.TransformedDependency); 1434 | goog.GoogModuleDependency.prototype.transform = function(contents) { 1435 | if (this.needsTranspile_) { 1436 | contents = this.transpiler_.transpile(contents, this.getPathName()); 1437 | } 1438 | if (!goog.LOAD_MODULE_USING_EVAL || goog.global.JSON === undefined) { 1439 | return "" + "goog.loadModule(function(exports) {" + '"use strict";' + contents + "\n" + ";return exports" + "});" + "\n//# sourceURL\x3d" + this.path + "\n"; 1440 | } else { 1441 | return "" + "goog.loadModule(" + goog.global.JSON.stringify(contents + "\n//# sourceURL\x3d" + this.path + "\n") + ");"; 1442 | } 1443 | }; 1444 | goog.DebugLoader_.IS_OLD_IE_ = !!(!goog.global.atob && goog.global.document && goog.global.document["all"]); 1445 | goog.DebugLoader_.prototype.addDependency = function(relPath, provides, requires, opt_loadFlags) { 1446 | provides = provides || []; 1447 | relPath = relPath.replace(/\\/g, "/"); 1448 | var path = goog.normalizePath_(goog.basePath + relPath); 1449 | if (!opt_loadFlags || typeof opt_loadFlags === "boolean") { 1450 | opt_loadFlags = opt_loadFlags ? {"module":goog.ModuleType.GOOG} : {}; 1451 | } 1452 | var dep = this.factory_.createDependency(path, relPath, provides, requires, opt_loadFlags, goog.transpiler_.needsTranspile(opt_loadFlags["lang"] || "es3", opt_loadFlags["module"])); 1453 | this.dependencies_[path] = dep; 1454 | for (var i = 0; i < provides.length; i++) { 1455 | this.idToPath_[provides[i]] = path; 1456 | } 1457 | this.idToPath_[relPath] = path; 1458 | }; 1459 | goog.DependencyFactory = function(transpiler) { 1460 | this.transpiler = transpiler; 1461 | }; 1462 | goog.DependencyFactory.prototype.createDependency = function(path, relativePath, provides, requires, loadFlags, needsTranspile) { 1463 | if (loadFlags["module"] == goog.ModuleType.GOOG) { 1464 | return new goog.GoogModuleDependency(path, relativePath, provides, requires, loadFlags, needsTranspile, this.transpiler); 1465 | } else { 1466 | if (needsTranspile) { 1467 | return new goog.TranspiledDependency(path, relativePath, provides, requires, loadFlags, this.transpiler); 1468 | } else { 1469 | if (loadFlags["module"] == goog.ModuleType.ES6) { 1470 | if (goog.TRANSPILE == "never" && goog.ASSUME_ES_MODULES_TRANSPILED) { 1471 | return new goog.PreTranspiledEs6ModuleDependency(path, relativePath, provides, requires, loadFlags); 1472 | } else { 1473 | return new goog.Es6ModuleDependency(path, relativePath, provides, requires, loadFlags); 1474 | } 1475 | } else { 1476 | return new goog.Dependency(path, relativePath, provides, requires, loadFlags); 1477 | } 1478 | } 1479 | } 1480 | }; 1481 | goog.debugLoader_ = new goog.DebugLoader_; 1482 | goog.loadClosureDeps = function() { 1483 | goog.debugLoader_.loadClosureDeps(); 1484 | }; 1485 | goog.setDependencyFactory = function(factory) { 1486 | goog.debugLoader_.setDependencyFactory(factory); 1487 | }; 1488 | goog.TRUSTED_TYPES_POLICY_ = goog.TRUSTED_TYPES_POLICY_NAME ? goog.createTrustedTypesPolicy(goog.TRUSTED_TYPES_POLICY_NAME + "#base") : null; 1489 | if (!goog.global.CLOSURE_NO_DEPS) { 1490 | goog.debugLoader_.loadClosureDeps(); 1491 | } 1492 | goog.bootstrap = function(namespaces, callback) { 1493 | goog.debugLoader_.bootstrap(namespaces, callback); 1494 | }; 1495 | } 1496 | goog.TRUSTED_TYPES_POLICY_NAME = goog.define("goog.TRUSTED_TYPES_POLICY_NAME", "goog"); 1497 | goog.identity_ = function(s) { 1498 | return s; 1499 | }; 1500 | goog.createTrustedTypesPolicy = function(name) { 1501 | var policy = null; 1502 | var policyFactory = goog.global.trustedTypes; 1503 | if (!policyFactory || !policyFactory.createPolicy) { 1504 | return policy; 1505 | } 1506 | try { 1507 | policy = policyFactory.createPolicy(name, {createHTML:goog.identity_, createScript:goog.identity_, createScriptURL:goog.identity_}); 1508 | } catch (e) { 1509 | goog.logToConsole_(e.message); 1510 | } 1511 | return policy; 1512 | }; 1513 | if (!COMPILED) { 1514 | var isChrome87 = false; 1515 | try { 1516 | isChrome87 = eval(goog.global.trustedTypes.emptyScript) !== goog.global.trustedTypes.emptyScript; 1517 | } catch (err) { 1518 | } 1519 | goog.CLOSURE_EVAL_PREFILTER_ = goog.global.trustedTypes && isChrome87 && goog.createTrustedTypesPolicy("goog#base#devonly#eval") || {createScript:goog.identity_}; 1520 | } 1521 | 1522 | goog.provide = SHADOW_PROVIDE; 1523 | goog.require = SHADOW_REQUIRE; 1524 | SHADOW_IMPORT("goog.debug.error.js"); 1525 | SHADOW_IMPORT("goog.dom.nodetype.js"); 1526 | SHADOW_IMPORT("goog.asserts.asserts.js"); 1527 | SHADOW_IMPORT("goog.reflect.reflect.js"); 1528 | SHADOW_IMPORT("goog.math.long.js"); 1529 | SHADOW_IMPORT("goog.math.integer.js"); 1530 | SHADOW_IMPORT("goog.dom.asserts.js"); 1531 | SHADOW_IMPORT("goog.functions.functions.js"); 1532 | SHADOW_IMPORT("goog.array.array.js"); 1533 | SHADOW_IMPORT("goog.dom.htmlelement.js"); 1534 | SHADOW_IMPORT("goog.dom.tagname.js"); 1535 | SHADOW_IMPORT("goog.object.object.js"); 1536 | SHADOW_IMPORT("goog.dom.tags.js"); 1537 | SHADOW_IMPORT("goog.string.typedstring.js"); 1538 | SHADOW_IMPORT("goog.string.const.js"); 1539 | SHADOW_IMPORT("goog.html.trustedtypes.js"); 1540 | SHADOW_IMPORT("goog.html.safescript.js"); 1541 | SHADOW_IMPORT("goog.fs.url.js"); 1542 | SHADOW_IMPORT("goog.fs.blob.js"); 1543 | SHADOW_IMPORT("goog.i18n.bidi.js"); 1544 | SHADOW_IMPORT("goog.html.trustedresourceurl.js"); 1545 | SHADOW_IMPORT("goog.string.internal.js"); 1546 | SHADOW_IMPORT("goog.html.safeurl.js"); 1547 | SHADOW_IMPORT("goog.html.safestyle.js"); 1548 | SHADOW_IMPORT("goog.html.safestylesheet.js"); 1549 | SHADOW_IMPORT("goog.labs.useragent.util.js"); 1550 | SHADOW_IMPORT("goog.labs.useragent.browser.js"); 1551 | SHADOW_IMPORT("goog.html.safehtml.js"); 1552 | SHADOW_IMPORT("goog.html.uncheckedconversions.js"); 1553 | SHADOW_IMPORT("goog.dom.safe.js"); 1554 | SHADOW_IMPORT("goog.string.string.js"); 1555 | SHADOW_IMPORT("goog.structs.structs.js"); 1556 | SHADOW_IMPORT("goog.math.math.js"); 1557 | SHADOW_IMPORT("goog.iter.iter.js"); 1558 | SHADOW_IMPORT("goog.structs.map.js"); 1559 | SHADOW_IMPORT("goog.uri.utils.js"); 1560 | SHADOW_IMPORT("goog.uri.uri.js"); 1561 | SHADOW_IMPORT("goog.string.stringbuffer.js"); 1562 | SHADOW_IMPORT("cljs.core.js"); 1563 | SHADOW_IMPORT("borkdude.dynaload.js"); 1564 | SHADOW_IMPORT("malli.sci.js"); 1565 | SHADOW_IMPORT("malli.impl.util.js"); 1566 | SHADOW_IMPORT("malli.impl.regex.js"); 1567 | SHADOW_IMPORT("malli.registry.js"); 1568 | SHADOW_IMPORT("malli.core.js"); 1569 | SHADOW_IMPORT("malli_ts.ast.js"); 1570 | SHADOW_IMPORT("clojure.string.js"); 1571 | SHADOW_IMPORT("camel_snake_kebab.internals.string_separator.js"); 1572 | SHADOW_IMPORT("camel_snake_kebab.internals.misc.js"); 1573 | SHADOW_IMPORT("camel_snake_kebab.internals.alter_name.js"); 1574 | SHADOW_IMPORT("camel_snake_kebab.core.js"); 1575 | SHADOW_IMPORT("clojure.set.js"); 1576 | SHADOW_IMPORT("shadow.js.shim.module$path.js"); 1577 | SHADOW_IMPORT("malli_ts.core.js"); 1578 | SHADOW_IMPORT("cljs_bean.from.cljs.core.js"); 1579 | SHADOW_IMPORT("cljs_bean.core.js"); 1580 | SHADOW_IMPORT("shadow.js.shim.module$fs.js"); 1581 | SHADOW_IMPORT("malli_ts.ts.js"); 1582 | SHADOW_IMPORT("shadow.module.main.append.js"); 1583 | 1584 | })(); 1585 | --------------------------------------------------------------------------------