(ns frontend.db.react "Transact the tx with some specified relationship so that the components will be refreshed when subscribed data changed. It'll be great if we can find an automatically resolving and performant solution. " (:require [clojure.core.async :as async] [datascript.core :as d] [frontend.date :as date] [frontend.db.async.util :as db-async-util] [frontend.db.conn :as conn] [frontend.db.utils :as db-utils] [frontend.state :as state] [frontend.util :as util] [logseq.common.util :as common-util] [logseq.db :as ldb] [promesa.core :as p])) ;; Query atom of map of Key ([repo q inputs]) -> atom ;; TODO: replace with LRUCache, only keep the latest 20 or 50 items? (defonce query-state (atom {})) ;; Current dynamic component (def ^:dynamic *query-component* nil) ;; Which reactive queries are triggered by the current component (def ^:dynamic *reactive-queries* nil) ;; component -> query-key (defonce component->query-key (volatile! {})) ;; query-key -> component-set (defonce query-key->components (volatile! {})) (defn set-new-result! [k new-result] (when-let [result-atom (get-in @query-state [k :result])] (reset! result-atom new-result))) (defn clear-query-state! [] (reset! query-state {})) (defn add-q! [k query inputs result-atom transform-fn query-fn async-query-fn inputs-fn] (swap! query-state assoc k {:query query :inputs inputs :result result-atom :transform-fn transform-fn :query-fn query-fn :async-query-fn async-query-fn :inputs-fn inputs-fn}) result-atom) (defn remove-q! [k] (swap! query-state dissoc k)) (defn add-query-component! [k component] (when (and k component) (vswap! component->query-key update component (fnil conj #{}) k) (vswap! query-key->components update k (fnil conj #{}) component))) (defn remove-query-component! [component] (when-let [queries (get @component->query-key component)] (doseq [query queries] (vswap! query-key->components (fn [m] (if-let [components* (not-empty (disj (get m query) component))] (assoc m query components*) (dissoc m query)))) (when (empty? (get @query-key->components query)) (remove-q! query)))) (vswap! component->query-key dissoc component)) ;; Reactive query (defn get-query-cached-result [k] (when-let [result (get @query-state k)] (when (satisfies? IWithMeta @(:result result)) (set! (.-state (:result result)) @(:result result))) (:result result))) (defn- > (keep (fn [[k cache]] (let [k' (vec (rest k))] (when (and (= (first k) repo-url) (or (contains? affected-keys-set k') (contains? #{:custom :kv} (first k')))) [k' cache]))) @query-state) (into {})) all-keys (concat (distinct affected-keys) (filter #(contains? #{:custom :kv} (first %)) (keys state)))] (doseq [k all-keys] (when-let [cache (get state k)] (let [{:keys [query query-fn]} cache custom? (= :custom (first k))] (when (or query query-fn) (try (let [f #(execute-query! repo-url db (vec (cons repo-url k)) cache)] (when-not custom? (f))) (catch :default e (js/console.error e) nil))))))))) (defn refresh! "Re-compute corresponding queries (from tx) and refresh the related react components." [repo-url affected-keys] (when (and repo-url (seq affected-keys)) (refresh-affected-queries! repo-url affected-keys))) (defn run-custom-queries-when-idle! [] (let [chan (state/get-reactive-custom-queries-chan)] (async/go-loop [] (let [[f query] (async/