mirror of
https://github.com/penpot/penpot.git
synced 2025-01-06 14:50:20 -05:00
✨ Add facility for create anonymous objects
Speciailly designed to be work in plugins where code is submited to hard deep freeze on the sandboxing process.
This commit is contained in:
parent
59e5656bd7
commit
e16ec9c719
6 changed files with 348 additions and 151 deletions
|
@ -43,7 +43,9 @@
|
||||||
{:extra-paths ["dev"]
|
{:extra-paths ["dev"]
|
||||||
:extra-deps
|
:extra-deps
|
||||||
{thheller/shadow-cljs {:mvn/version "2.28.18"}
|
{thheller/shadow-cljs {:mvn/version "2.28.18"}
|
||||||
|
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
||||||
org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
||||||
|
criterium/criterium {:mvn/version "RELEASE"}
|
||||||
cider/cider-nrepl {:mvn/version "0.48.0"}}}
|
cider/cider-nrepl {:mvn/version "0.48.0"}}}
|
||||||
|
|
||||||
:shadow-cljs
|
:shadow-cljs
|
||||||
|
|
34
frontend/dev/user.clj
Normal file
34
frontend/dev/user.clj
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns user
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.pprint :as pp]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clojure.tools.namespace.repl :as repl]
|
||||||
|
[clojure.pprint :refer [pprint print-table]]
|
||||||
|
[clojure.repl :refer :all]
|
||||||
|
[clojure.walk :refer [macroexpand-all]]
|
||||||
|
[criterium.core :as crit]))
|
||||||
|
|
||||||
|
;; --- Benchmarking Tools
|
||||||
|
|
||||||
|
(defmacro run-quick-bench
|
||||||
|
[& exprs]
|
||||||
|
`(crit/with-progress-reporting (crit/quick-bench (do ~@exprs) :verbose)))
|
||||||
|
|
||||||
|
(defmacro run-quick-bench'
|
||||||
|
[& exprs]
|
||||||
|
`(crit/quick-bench (do ~@exprs)))
|
||||||
|
|
||||||
|
(defmacro run-bench
|
||||||
|
[& exprs]
|
||||||
|
`(crit/with-progress-reporting (crit/bench (do ~@exprs) :verbose)))
|
||||||
|
|
||||||
|
(defmacro run-bench'
|
||||||
|
[& exprs]
|
||||||
|
`(crit/bench (do ~@exprs)))
|
6
frontend/scripts/repl
Executable file
6
frontend/scripts/repl
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
export OPTIONS="-A:dev -J-XX:-OmitStackTraceInFastThrow";
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
exec clojure $OPTIONS -M -m rebel-readline.main
|
|
@ -28,92 +28,88 @@
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[beicon.v2.core :as rx]))
|
[beicon.v2.core :as rx]))
|
||||||
|
|
||||||
(declare file-version-proxy)
|
|
||||||
|
|
||||||
(deftype FileVersionProxy [$plugin $file $version $data]
|
|
||||||
Object
|
|
||||||
(restore
|
|
||||||
[_]
|
|
||||||
(cond
|
|
||||||
(not (r/check-permission $plugin "content:write"))
|
|
||||||
(u/display-not-valid :restore "Plugin doesn't have 'content:write' permission")
|
|
||||||
|
|
||||||
:else
|
|
||||||
(let [project-id (:current-project-id @st/state)]
|
|
||||||
(st/emit! (dwv/restore-version project-id $file $version :plugin)))))
|
|
||||||
|
|
||||||
(remove
|
|
||||||
[_]
|
|
||||||
(js/Promise.
|
|
||||||
(fn [resolve reject]
|
|
||||||
(cond
|
|
||||||
(not (r/check-permission $plugin "content:write"))
|
|
||||||
(u/reject-not-valid reject :remove "Plugin doesn't have 'content:write' permission")
|
|
||||||
|
|
||||||
:else
|
|
||||||
(->> (rp/cmd! :delete-file-snapshot {:id $version})
|
|
||||||
(rx/subs! #(resolve) reject))))))
|
|
||||||
|
|
||||||
(pin
|
|
||||||
[_]
|
|
||||||
(js/Promise.
|
|
||||||
(fn [resolve reject]
|
|
||||||
(cond
|
|
||||||
(not (r/check-permission $plugin "content:write"))
|
|
||||||
(u/reject-not-valid reject :pin "Plugin doesn't have 'content:write' permission")
|
|
||||||
|
|
||||||
(not= "system" (:created-by $data))
|
|
||||||
(u/reject-not-valid reject :pin "Only auto-saved versions can be pinned")
|
|
||||||
|
|
||||||
:else
|
|
||||||
(let [params {:id $version
|
|
||||||
:label (dt/format (:created-at $data) :date-full)}]
|
|
||||||
(->> (rx/zip (rp/cmd! :get-team-users {:file-id $file})
|
|
||||||
(rp/cmd! :update-file-snapshot params))
|
|
||||||
(rx/subs! (fn [[users data]]
|
|
||||||
(let [users (d/index-by :id users)]
|
|
||||||
(resolve (file-version-proxy $plugin $file users data))))
|
|
||||||
reject))))))))
|
|
||||||
|
|
||||||
(defn file-version-proxy
|
(defn file-version-proxy
|
||||||
[plugin-id file-id users data]
|
[plugin-id file-id users data]
|
||||||
(let [data (atom data)]
|
(let [data (atom data)]
|
||||||
(crc/add-properties!
|
(obj/reify {:name "FileVersionProxy"}
|
||||||
(FileVersionProxy. plugin-id file-id (:id @data) data)
|
:$plugin {:get (fn [] plugin-id) :enumerable false}
|
||||||
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
|
:$file {:get (fn [] file-id) :enumerable false}
|
||||||
{:name "$file" :enumerable false :get (constantly file-id)}
|
:$version {:get (fn [] (:id @data)) :enumerable false}
|
||||||
{:name "$version" :enumerable false :get (constantly (:id @data))}
|
:$data {:get (fn [] @data) :enumerable false}
|
||||||
{:name "$data" :enumerable false :get (constantly @data)}
|
|
||||||
|
|
||||||
{:name "label"
|
:label
|
||||||
:get (fn [_] (:label @data))
|
{:get #(:label @data)
|
||||||
:set
|
:set
|
||||||
(fn [_ value]
|
(fn [value]
|
||||||
|
(cond
|
||||||
|
(not (r/check-permission plugin-id "content:write"))
|
||||||
|
(u/display-not-valid :label "Plugin doesn't have 'content:write' permission")
|
||||||
|
|
||||||
|
(or (not (string? value)) (empty? value))
|
||||||
|
(u/display-not-valid :label value)
|
||||||
|
|
||||||
|
:else
|
||||||
|
(do (swap! data assoc :label value :created-by "user")
|
||||||
|
(->> (rp/cmd! :update-file-snapshot {:id (:id @data) :label value})
|
||||||
|
(rx/take 1)
|
||||||
|
(rx/subs! identity)))))}
|
||||||
|
|
||||||
|
:createdBy
|
||||||
|
{:get
|
||||||
|
(fn []
|
||||||
|
(when-let [user-data (get users (:profile-id @data))]
|
||||||
|
(user/user-proxy plugin-id user-data)))}
|
||||||
|
|
||||||
|
:createdAt
|
||||||
|
{:get #(.toJSDate ^js (:created-at @data))}
|
||||||
|
|
||||||
|
:isAutosave
|
||||||
|
{:get #(= "system" (:created-by @data))}
|
||||||
|
|
||||||
|
:restore
|
||||||
|
(fn []
|
||||||
(cond
|
(cond
|
||||||
(not (r/check-permission plugin-id "content:write"))
|
(not (r/check-permission plugin-id "content:write"))
|
||||||
(u/display-not-valid :label "Plugin doesn't have 'content:write' permission")
|
(u/display-not-valid :restore "Plugin doesn't have 'content:write' permission")
|
||||||
|
|
||||||
(or (not (string? value)) (empty? value))
|
|
||||||
(u/display-not-valid :label value)
|
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(do (swap! data assoc :label value :created-by "user")
|
(let [project-id (:current-project-id @st/state)
|
||||||
(->> (rp/cmd! :update-file-snapshot {:id (:id @data) :label value})
|
version-id (get @data :id)]
|
||||||
(rx/take 1)
|
(st/emit! (dwv/restore-version project-id file-id version-id :plugin)))))
|
||||||
(rx/subs! identity)))))}
|
|
||||||
|
|
||||||
{:name "createdBy"
|
:remove
|
||||||
:get (fn [_]
|
(fn []
|
||||||
(when-let [user-data (get users (:profile-id @data))]
|
(js/Promise.
|
||||||
(user/user-proxy plugin-id user-data)))}
|
(fn [resolve reject]
|
||||||
|
(cond
|
||||||
|
(not (r/check-permission plugin-id "content:write"))
|
||||||
|
(u/reject-not-valid reject :remove "Plugin doesn't have 'content:write' permission")
|
||||||
|
|
||||||
{:name "createdAt"
|
:else
|
||||||
:get (fn [_]
|
(let [version-id (:id @data)]
|
||||||
(.toJSDate ^js (:created-at @data)))}
|
(->> (rp/cmd! :delete-file-snapshot {:id version-id})
|
||||||
|
(rx/subs! #(resolve) reject)))))))
|
||||||
|
|
||||||
{:name "isAutosave"
|
:pin
|
||||||
:get (fn [_]
|
(fn []
|
||||||
(= "system" (:created-by @data)))})))
|
(js/Promise.
|
||||||
|
(fn [resolve reject]
|
||||||
|
(cond
|
||||||
|
(not (r/check-permission plugin-id "content:write"))
|
||||||
|
(u/reject-not-valid reject :pin "Plugin doesn't have 'content:write' permission")
|
||||||
|
|
||||||
|
(not= "system" (:created-by @data))
|
||||||
|
(u/reject-not-valid reject :pin "Only auto-saved versions can be pinned")
|
||||||
|
|
||||||
|
:else
|
||||||
|
(let [params {:id (:id @data)
|
||||||
|
:label (dt/format (:created-at @data) :date-full)}]
|
||||||
|
(->> (rx/zip (rp/cmd! :get-team-users {:file-id file-id})
|
||||||
|
(rp/cmd! :update-file-snapshot params))
|
||||||
|
(rx/subs! (fn [[users data]]
|
||||||
|
(let [users (d/index-by :id users)]
|
||||||
|
(resolve (file-version-proxy plugin-id file-id users @data))))
|
||||||
|
reject))))))))))
|
||||||
|
|
||||||
(deftype FileProxy [$plugin $id]
|
(deftype FileProxy [$plugin $id]
|
||||||
Object
|
Object
|
||||||
|
|
235
frontend/src/app/util/object.cljc
Normal file
235
frontend/src/app/util/object.cljc
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.util.object
|
||||||
|
"A collection of helpers for work with javascript objects."
|
||||||
|
(:refer-clojure :exclude [set! new get merge clone contains? array? into-array reify])
|
||||||
|
#?(:cljs (:require-macros [app.util.object]))
|
||||||
|
(:require
|
||||||
|
[clojure.core :as c]))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn array?
|
||||||
|
[o]
|
||||||
|
(.isArray js/Array o)))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn into-array
|
||||||
|
[o]
|
||||||
|
(js/Array.from o)))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn create [] #js {}))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn get
|
||||||
|
([obj k]
|
||||||
|
(when (some? obj)
|
||||||
|
(unchecked-get obj k)))
|
||||||
|
([obj k default]
|
||||||
|
(let [result (get obj k)]
|
||||||
|
(if (undefined? result) default result)))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn contains?
|
||||||
|
[obj k]
|
||||||
|
(when (some? obj)
|
||||||
|
(js/Object.hasOwn obj k))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn clone
|
||||||
|
[a]
|
||||||
|
(js/Object.assign #js {} a)))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn merge!
|
||||||
|
([a b]
|
||||||
|
(js/Object.assign a b))
|
||||||
|
([a b & more]
|
||||||
|
(reduce merge! (merge! a b) more))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn merge
|
||||||
|
([a b]
|
||||||
|
(js/Object.assign #js {} a b))
|
||||||
|
([a b & more]
|
||||||
|
(reduce merge! (merge a b) more))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn set!
|
||||||
|
[obj key value]
|
||||||
|
(unchecked-set obj key value)
|
||||||
|
obj))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn unset!
|
||||||
|
[obj key]
|
||||||
|
(js-delete obj key)
|
||||||
|
obj))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(def ^:private not-found-sym
|
||||||
|
(js/Symbol "not-found")))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn update!
|
||||||
|
[obj key f & args]
|
||||||
|
(let [found (c/get obj key not-found-sym)]
|
||||||
|
(when-not ^boolean (identical? found not-found-sym)
|
||||||
|
(unchecked-set obj key (apply f found args)))
|
||||||
|
obj)))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn ^boolean in?
|
||||||
|
[obj prop]
|
||||||
|
(js* "~{} in ~{}" prop obj)))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn without-empty
|
||||||
|
[^js obj]
|
||||||
|
(when (some? obj)
|
||||||
|
(js* "Object.entries(~{}).reduce((a, [k,v]) => (v == null ? a : (a[k]=v, a)), {}) " obj))))
|
||||||
|
|
||||||
|
(defmacro add-properties!
|
||||||
|
"Adds properties to an object using `.defineProperty`"
|
||||||
|
[rsym & properties]
|
||||||
|
(let [rsym (with-meta rsym {:tag 'js})
|
||||||
|
getf-sym (with-meta (gensym (str rsym "-get-fn-")) {:tag 'js})
|
||||||
|
setf-sym (with-meta (gensym (str rsym "-set-fn-")) {:tag 'js})
|
||||||
|
this-sym (with-meta (gensym (str rsym "-this-")) {:tag 'js})
|
||||||
|
target-sym (with-meta (gensym (str rsym "-target-")) {:tag 'js})]
|
||||||
|
`(let [~target-sym ~rsym]
|
||||||
|
;; Creates the `.defineProperty` per property
|
||||||
|
~@(for [params properties
|
||||||
|
:let [pname (c/get params :name)
|
||||||
|
get-expr (c/get params :get)
|
||||||
|
set-expr (c/get params :set)
|
||||||
|
this? (c/get params :this true)
|
||||||
|
enum? (c/get params :enumerable true)
|
||||||
|
conf? (c/get params :configurable)
|
||||||
|
writ? (c/get params :writable)]]
|
||||||
|
`(let [~@(concat
|
||||||
|
(when get-expr
|
||||||
|
[getf-sym get-expr])
|
||||||
|
(when set-expr
|
||||||
|
[setf-sym set-expr]))]
|
||||||
|
(.defineProperty
|
||||||
|
js/Object
|
||||||
|
~target-sym
|
||||||
|
~pname
|
||||||
|
(cljs.core/js-obj
|
||||||
|
~@(concat
|
||||||
|
["enumerable" (boolean enum?)]
|
||||||
|
|
||||||
|
(when conf?
|
||||||
|
["configurable" true])
|
||||||
|
|
||||||
|
(when (some? writ?)
|
||||||
|
["writable" true])
|
||||||
|
|
||||||
|
(when get-expr
|
||||||
|
(if this?
|
||||||
|
["get" `(fn [] (cljs.core/this-as ~this-sym (~getf-sym ~this-sym)))]
|
||||||
|
["get" getf-sym]))
|
||||||
|
|
||||||
|
(when set-expr
|
||||||
|
(if this?
|
||||||
|
["set" `(fn [v#] (cljs.core/this-as ~this-sym (~setf-sym ~this-sym v#)))]
|
||||||
|
["set" setf-sym])))))))
|
||||||
|
|
||||||
|
;; Returns the object
|
||||||
|
~target-sym)))
|
||||||
|
|
||||||
|
(defn- collect-properties
|
||||||
|
[params]
|
||||||
|
(let [[tmeta params] (if (map? (first params))
|
||||||
|
[(first params) (rest params)]
|
||||||
|
[{} params])]
|
||||||
|
(loop [params (seq params)
|
||||||
|
props []
|
||||||
|
defs {}
|
||||||
|
curr :start
|
||||||
|
ckey nil]
|
||||||
|
(cond
|
||||||
|
(= curr :start)
|
||||||
|
(let [candidate (first params)]
|
||||||
|
(cond
|
||||||
|
(keyword? candidate)
|
||||||
|
(recur (rest params) props defs :property candidate)
|
||||||
|
|
||||||
|
(nil? candidate)
|
||||||
|
(recur (rest params) props defs :end nil)
|
||||||
|
|
||||||
|
:else
|
||||||
|
(recur (rest params) props defs :definition candidate)))
|
||||||
|
|
||||||
|
(= :end curr)
|
||||||
|
[tmeta props defs]
|
||||||
|
|
||||||
|
(= :property curr)
|
||||||
|
(let [definition (first params)]
|
||||||
|
(if (some? definition)
|
||||||
|
(let [definition (if (map? definition)
|
||||||
|
(assoc definition :name (name ckey) :this false)
|
||||||
|
(-> {:enumerable false}
|
||||||
|
(c/merge (meta definition))
|
||||||
|
(assoc :name (name ckey))
|
||||||
|
(assoc :this false)
|
||||||
|
(assoc :get `(fn [] ~definition))))]
|
||||||
|
(recur (rest params)
|
||||||
|
(conj props definition)
|
||||||
|
defs
|
||||||
|
:start
|
||||||
|
nil))
|
||||||
|
(let [hint (str "expected property definition for: " curr)]
|
||||||
|
(throw (ex-info hint {:key curr})))))
|
||||||
|
|
||||||
|
(= :definition curr)
|
||||||
|
(let [[params props defs curr ckey]
|
||||||
|
(loop [params params
|
||||||
|
defs (update defs ckey #(or % []))]
|
||||||
|
(let [candidate (first params)
|
||||||
|
params (rest params)]
|
||||||
|
(cond
|
||||||
|
(nil? candidate)
|
||||||
|
[params props defs :end]
|
||||||
|
|
||||||
|
(keyword? candidate)
|
||||||
|
[params props defs :property candidate]
|
||||||
|
|
||||||
|
(symbol? candidate)
|
||||||
|
[params props defs :definition candidate]
|
||||||
|
|
||||||
|
:else
|
||||||
|
(recur params (update defs ckey conj candidate)))))]
|
||||||
|
(recur params props defs curr ckey))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(throw (ex-info "invalid params" {}))))))
|
||||||
|
|
||||||
|
(defmacro reify
|
||||||
|
"A domain specific variation of reify that creates anonymous objects
|
||||||
|
on demand with the ability to assign protocol implementations and
|
||||||
|
custom properties"
|
||||||
|
[& params]
|
||||||
|
(let [[tmeta properties definitions] (collect-properties params)
|
||||||
|
obj-sym (gensym "obj-")]
|
||||||
|
`(let [~obj-sym (cljs.core/js-obj)]
|
||||||
|
(add-properties! ~obj-sym
|
||||||
|
~@(when-let [tname (:name tmeta)]
|
||||||
|
[`{:name ~'js/Symbol.toStringTag
|
||||||
|
:this false
|
||||||
|
:enumerable false
|
||||||
|
:get (fn [] ~tname)}])
|
||||||
|
~@properties)
|
||||||
|
(let [~obj-sym ~(if-let [definitions (seq definitions)]
|
||||||
|
`(cljs.core/specify! ~obj-sym
|
||||||
|
~@(mapcat (fn [[k v]] (cons k v)) definitions))
|
||||||
|
obj-sym)]
|
||||||
|
|
||||||
|
(cljs.core/specify! ~obj-sym
|
||||||
|
cljs.core/IMeta
|
||||||
|
(~'-meta [_#] ~tmeta))))))
|
|
@ -1,76 +0,0 @@
|
||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) KALEIDOS INC
|
|
||||||
|
|
||||||
(ns app.util.object
|
|
||||||
"A collection of helpers for work with javascript objects."
|
|
||||||
(:refer-clojure :exclude [set! new get merge clone contains? array? into-array]))
|
|
||||||
|
|
||||||
(defn array?
|
|
||||||
[o]
|
|
||||||
(.isArray js/Array o))
|
|
||||||
|
|
||||||
(defn into-array
|
|
||||||
[o]
|
|
||||||
(js/Array.from o))
|
|
||||||
|
|
||||||
(defn create [] #js {})
|
|
||||||
|
|
||||||
(defn get
|
|
||||||
([obj k]
|
|
||||||
(when (some? obj)
|
|
||||||
(unchecked-get obj k)))
|
|
||||||
([obj k default]
|
|
||||||
(let [result (get obj k)]
|
|
||||||
(if (undefined? result) default result))))
|
|
||||||
|
|
||||||
(defn contains?
|
|
||||||
[obj k]
|
|
||||||
(when (some? obj)
|
|
||||||
(js/Object.hasOwn obj k)))
|
|
||||||
|
|
||||||
(defn clone
|
|
||||||
[a]
|
|
||||||
(js/Object.assign #js {} a))
|
|
||||||
|
|
||||||
(defn merge!
|
|
||||||
([a b]
|
|
||||||
(js/Object.assign a b))
|
|
||||||
([a b & more]
|
|
||||||
(reduce merge! (merge! a b) more)))
|
|
||||||
|
|
||||||
(defn merge
|
|
||||||
([a b]
|
|
||||||
(js/Object.assign #js {} a b))
|
|
||||||
([a b & more]
|
|
||||||
(reduce merge! (merge a b) more)))
|
|
||||||
|
|
||||||
(defn set!
|
|
||||||
[obj key value]
|
|
||||||
(unchecked-set obj key value)
|
|
||||||
obj)
|
|
||||||
|
|
||||||
(defn unset!
|
|
||||||
[obj key]
|
|
||||||
(js-delete obj key)
|
|
||||||
obj)
|
|
||||||
|
|
||||||
(def ^:private not-found-sym (js/Symbol "not-found"))
|
|
||||||
|
|
||||||
(defn update!
|
|
||||||
[obj key f & args]
|
|
||||||
(let [found (get obj key not-found-sym)]
|
|
||||||
(when-not ^boolean (identical? found not-found-sym)
|
|
||||||
(unchecked-set obj key (apply f found args)))
|
|
||||||
obj))
|
|
||||||
|
|
||||||
(defn ^boolean in?
|
|
||||||
[obj prop]
|
|
||||||
(js* "~{} in ~{}" prop obj))
|
|
||||||
|
|
||||||
(defn without-empty
|
|
||||||
[^js obj]
|
|
||||||
(when (some? obj)
|
|
||||||
(js* "Object.entries(~{}).reduce((a, [k,v]) => (v == null ? a : (a[k]=v, a)), {}) " obj)))
|
|
Loading…
Reference in a new issue