react.cljs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. (ns frontend.db.react
  2. "Transact the tx with some specified relationship so that the components will
  3. be refreshed when subscribed data changed.
  4. It'll be great if we can find an automatically resolving and performant
  5. solution.
  6. "
  7. (:require [clojure.core.async :as async]
  8. [datascript.core :as d]
  9. [frontend.db.async.util :as db-async-util]
  10. [frontend.db.conn :as conn]
  11. [frontend.db.utils :as db-utils]
  12. [frontend.state :as state]
  13. [frontend.util :as util]
  14. [promesa.core :as p]))
  15. ;; Query atom of map of Key ([repo q inputs]) -> atom
  16. ;; TODO: replace with LRUCache, only keep the latest 20 or 50 items?
  17. (defonce *query-state (atom {}))
  18. ;; [[repo q]]
  19. (defonce *collapsed-queries (atom {}))
  20. (defn set-q-collapsed!
  21. [k collapsed?]
  22. (swap! *collapsed-queries assoc k collapsed?))
  23. (defn- query-collapsed?
  24. [k]
  25. (@*collapsed-queries k))
  26. ;; Current dynamic component
  27. (def ^:dynamic *query-component* nil)
  28. ;; Which reactive queries are triggered by the current component
  29. (def ^:dynamic *reactive-queries* nil)
  30. ;; component -> query-key
  31. (defonce component->query-key (volatile! {}))
  32. ;; query-key -> component-set
  33. (defonce query-key->components (volatile! {}))
  34. (defn set-new-result!
  35. [k new-result]
  36. (when-let [result-atom (get-in @*query-state [k :result])]
  37. (reset! result-atom new-result)))
  38. (defn clear-query-state!
  39. []
  40. (reset! *query-state {})
  41. (reset! *collapsed-queries {}))
  42. (defn add-q!
  43. [k query inputs result-atom transform-fn query-fn inputs-fn]
  44. (swap! *query-state assoc k {:query query
  45. :inputs inputs
  46. :result result-atom
  47. :transform-fn transform-fn
  48. :query-fn query-fn
  49. :inputs-fn inputs-fn})
  50. result-atom)
  51. (defn remove-q!
  52. [k]
  53. (swap! *query-state dissoc k))
  54. (defn add-query-component!
  55. [k component]
  56. (when (and k component)
  57. (vswap! component->query-key update component (fnil conj #{}) k)
  58. (vswap! query-key->components update k (fnil conj #{}) component)))
  59. (defn remove-query-component!
  60. [component]
  61. (when-let [queries (get @component->query-key component)]
  62. (doseq [query queries]
  63. (vswap! query-key->components
  64. (fn [m]
  65. (if-let [components* (not-empty (disj (get m query) component))]
  66. (assoc m query components*)
  67. (dissoc m query))))
  68. (when (empty? (get @query-key->components query))
  69. (remove-q! query))))
  70. (vswap! component->query-key dissoc component))
  71. ;; Reactive query
  72. (defn get-query-cached-result
  73. [k]
  74. (when-let [result (get @*query-state k)]
  75. (when (satisfies? IWithMeta @(:result result))
  76. (set! (.-state (:result result))
  77. @(:result result)))
  78. (:result result)))
  79. (defn- <q-aux
  80. [repo db query-fn inputs-fn k query inputs built-in-query?]
  81. (let [kv? (and (vector? k) (= :kv (second k)))
  82. q (if util/node-test?
  83. (fn [query inputs] (apply d/q query db inputs))
  84. (fn [query inputs]
  85. (let [q-f #(apply db-async-util/<q repo {:transact-db? false} (cons query inputs))]
  86. (if built-in-query?
  87. ;; delay built-in-queries to not block journal rendering
  88. (p/let [_ (p/delay 100)]
  89. (q-f))
  90. (q-f)))))]
  91. (when (or query-fn query kv?)
  92. (cond
  93. query-fn
  94. (query-fn db nil)
  95. kv?
  96. (db-utils/entity db (last k))
  97. inputs-fn
  98. (let [inputs (inputs-fn)]
  99. (q query inputs))
  100. (seq inputs)
  101. (q query inputs)
  102. :else
  103. (q query nil)))))
  104. (defn q
  105. [repo k {:keys [use-cache? transform-fn query-fn inputs-fn
  106. disable-reactive? return-promise? built-in-query?]
  107. :or {use-cache? true
  108. transform-fn identity}} query & inputs]
  109. ;; {:pre [(s/valid? :frontend.worker.react/block k)]}
  110. (let [origin-key k
  111. k (vec (cons repo k))]
  112. (when-let [db (conn/get-db repo)]
  113. (let [result-atom (get-query-cached-result k)]
  114. (when-let [component *query-component*]
  115. (add-query-component! k component))
  116. (when-let [queries *reactive-queries*]
  117. (swap! queries conj origin-key))
  118. (if (and use-cache? result-atom)
  119. result-atom
  120. (let [result-atom (or result-atom (atom nil))
  121. p-or-value (<q-aux repo db query-fn inputs-fn k query inputs built-in-query?)]
  122. (when-not disable-reactive?
  123. (add-q! k query inputs result-atom transform-fn query-fn inputs-fn))
  124. (cond
  125. return-promise?
  126. p-or-value
  127. (p/promise? p-or-value)
  128. (do
  129. (p/let [result p-or-value
  130. result' (transform-fn result)]
  131. (reset! result-atom result'))
  132. result-atom)
  133. :else
  134. (let [result' (transform-fn p-or-value)]
  135. ;; Don't notify watches now
  136. (set! (.-state result-atom) result')
  137. result-atom))))))))
  138. (defn- execute-query!
  139. [graph db k {:keys [query inputs transform-fn query-fn inputs-fn result built-in-query?]
  140. :or {transform-fn identity}}]
  141. (p/let [p-or-value (<q-aux graph db query-fn inputs-fn k query inputs built-in-query?)
  142. result' (transform-fn p-or-value)]
  143. (when-not (= result' result)
  144. (set-new-result! k result'))))
  145. (defn refresh-affected-queries!
  146. [repo-url affected-keys & {:keys [skip-kv-custom-keys?]
  147. :or {skip-kv-custom-keys? false}}]
  148. (util/profile
  149. "refresh!"
  150. (let [db (conn/get-db repo-url)
  151. affected-keys-set (set affected-keys)
  152. state (->> (keep (fn [[k cache]]
  153. (let [k' (vec (rest k))]
  154. (when (and (= (first k) repo-url)
  155. (or (contains? affected-keys-set k')
  156. (contains? #{:custom :kv} (first k'))))
  157. [k' cache]))) @*query-state)
  158. (into {}))
  159. all-keys (concat (distinct affected-keys)
  160. (when-not skip-kv-custom-keys?
  161. (filter #(contains? #{:custom :kv} (first %)) (keys state))))]
  162. (doseq [k all-keys]
  163. (when-let [cache (get state k)]
  164. (let [{:keys [query query-fn]} cache
  165. custom? (= :custom (first k))]
  166. (when (or query query-fn)
  167. (try
  168. (let [f #(execute-query! repo-url db (vec (cons repo-url k)) cache)]
  169. (if custom?
  170. ;; perf: don't execute custom queries if they were collapsed
  171. (when-not (query-collapsed? k)
  172. (async/put! (state/get-reactive-custom-queries-chan) [f query]))
  173. (f)))
  174. (catch :default e
  175. (js/console.error e)
  176. nil)))))))))
  177. (defn refresh!
  178. "Re-compute corresponding queries (from tx) and refresh the related react components."
  179. [repo-url affected-keys]
  180. (when (and repo-url (seq affected-keys))
  181. (refresh-affected-queries! repo-url affected-keys)))
  182. (defn run-custom-queries-when-idle!
  183. []
  184. (let [chan (state/get-reactive-custom-queries-chan)]
  185. (async/go-loop []
  186. (let [[f query] (async/<! chan)]
  187. (try
  188. (if (state/input-idle? (state/get-current-repo))
  189. (f)
  190. (do
  191. (async/<! (async/timeout 2000))
  192. (async/put! chan [f query])))
  193. (catch :default error
  194. (let [type :custom-query/failed]
  195. (js/console.error (str type "\n" query))
  196. (js/console.error error)))))
  197. (recur))
  198. chan))