<commit_before>(ns ^:figwheel-always dato.lib.db
    (:require [clojure.set :as sets]
              [datascript :as d]
              [fipp.edn :as fipp]
              [dato.datascript-utils :as dsu]
              [dato.client.utils.seq :refer [dissoc-in]]
              [om.core :as om])
    (:import [goog.ui IdGenerator]))

(defonce listeners (atom {}))

(defn ^:export inspect-listeners []
  (clj->js @listeners))

;; Things that aren't reified in the persistent/ephemeral datomic
;; instance
(def cs-schema
  {:local/current-session {:db/valueType :db.type/ref}
   :session/user          {:db/valueType :db.type/ref}
   :summon/session        {:db/valueType :db.type/ref}})

(defn enum [db db-id]
  ;; Try to support both straight db-id and {:db/id db-id}.
  ;; Messy, but seems to be a common mistake
  (when-let [eid (get db-id :db/id db-id)]
    (:db/ident (d/entity db eid))))

(defn enum-id [db enum]
  (dsu/q1-by db :db/ident enum))

;; Could probably speed this up with a few different conditionals
;; (compare db/ids directly if they're both number/map)
(defn enum= [db a b]
  (let [a (if (keyword? a) a (if (number? a)
                               (enum db a)
                               (enum db (:db/id a))))
        b (if (keyword? b) b (if (number? b)
                               (enum db b)
                               (enum db (:db/id b))))]
    (= a b)))

(defn handle-callbacks [db tx-report]
  (let [[eids attrs] (reduce (fn [[eids attrs] datom]
                               [(conj eids (:e datom))
                                (conj attrs (:a datom))])
                             [#{} #{}] (:tx-data tx-report))]
    (doseq [eid eids
            [k callback] (get-in @listeners [db :entity-listeners (str eid)])]
      (callback tx-report))
    (doseq [attr attrs
            [k callback] (get-in @listeners [db :attribute-listeners attr])]
      (callback tx-report))))

(defn setup-listener! [db key]
  (d/listen!
   db
   key
   (fn [tx-report]
     (js/setTimeout
      #(handle-callbacks db tx-report) 10))))

(defn add-entity-listener [conn eid key callback]
  (swap! listeners assoc-in [conn :entity-listeners (str eid) key] callback))

(defn add-attribute-listener [conn attribute key callback]
  (swap! listeners assoc-in [conn :attribute-listeners attribute key] callback))

(defn remove-entity-listener [conn eid key]
  (swap! listeners dissoc-in [conn :entity-listeners (str eid) key]))

(defn remove-attribute-listener [conn attribute key]
  (swap! listeners dissoc-in [conn :attribute-listeners attribute key]))

(defn add-unmounting-entity-listener [owner conn eid callback]
  (om/update-state-nr! owner (fn [s]
                               (update-in s [::listener-key] #(or % (.getNextUniqueId (.getInstance IdGenerator))))))
  (let [key (om/get-state owner ::listener-key)]
    (add-entity-listener conn eid key (fn [tx-report]
                                        (if (om/mounted? owner)
                                          (callback tx-report)
                                          (remove-entity-listener conn eid key))))))

(defn add-unmounting-attribute-listener [owner conn attribute callback]
  (om/update-state-nr! owner (fn [s]
                               (update-in s [::listener-key] #(or % (.getNextUniqueId (.getInstance IdGenerator))))))
  (let [key (om/get-state owner ::listener-key)]
    (add-attribute-listener conn attribute key (fn [tx-report]
                                                 (if (om/mounted? owner)
                                                   (callback tx-report)
                                                   (remove-attribute-listener conn attribute key))))))

(defn refresh-on! [owner conn listen-kvs]
  (doseq [listen-trigger listen-kvs]
    (let [[trigger-type trigger] listen-trigger
          add-trigger-fn         (case trigger-type
                                   :attr add-attribute-listener
                                   :eid  add-entity-listener)
          remove-trigger-fn      (case trigger-type
                                   :attr remove-attribute-listener
                                   :eid  remove-entity-listener)]
      (om/update-state-nr! owner
                           (fn [s]
                             (update-in s [::listener-keys listen-trigger]
                                        #(or % (.getNextUniqueId (.getInstance IdGenerator))))))
      (let [key (om/get-state owner [::listener-key listen-trigger])]
        (add-trigger-fn conn trigger key (fn [tx-report]
                                           (if (om/mounted? owner)
                                             (om/refresh! owner)
                                             (remove-trigger-fn conn trigger key))))))))

(defn datom->tx [datom fids]
  (let [[e a v d added?] datom]
    [(if added?
       :db/add
       :db/retract)
     ;; we know that eid will be unique to each entity. Pass it through
     ;; d/tempid in case future DS behavior changes
     (d/tempid :db.part/user e)
     a
     (get fids v v)]))

;; fid -> ds db/id
(def all-fids
  (atom {}))

(def pending-tx-info
  (atom {}))

(def pending-datoms
  (atom #{}))

(defn fast-fid [db e]
  (:v (first (d/datoms db :eavt e :dato/guid))))

(defn find-fid [tx-report e]
  (or (fast-fid (:db-after tx-report) e)
      (fast-fid (:db-before tx-report) e)))

(defn add-entity-fid [fid-map tx-report e]
  (if (contains? fid-map e)
    fid-map
    ;; TODO: write retractEntity fn that preserves fid
    (assoc fid-map e (find-fid tx-report e))))

(defn add-ref-fid [fid-map tx-report a v]
  (if (= :db.type/ref (get-in tx-report [:db-after :schema a :db/valueType]))
    (if (contains? fid-map v)
      fid-map
      (assoc fid-map v (find-fid tx-report v)))
    fid-map))

(defn fid-map [tx-report]
  (reduce (fn [acc {:keys [e a v]}]
            (-> acc
              (add-entity-fid tx-report e)
              (add-ref-fid tx-report a v)))
          {} (:tx-data tx-report)))

(defn prep-broadcastable-tx-report [tx-report]
  (let [fids    (fid-map tx-report)
        tx-guid (d/squuid)]
    (assert (every? identity (vals fids)) (str "Missing fid value in " (pr-str fids)))
    (def l-tx-report tx-report)
    ;; We're broadcasting this, so we need to track the fids for the
    ;; lifetime of this db
    (def tx-fids fids)
    (swap! all-fids merge (sets/map-invert fids))<commit_msg>Better error message whenever fid-map has a nil value<commit_after>(ns ^:figwheel-always dato.lib.db
    (:require [clojure.set :as sets]
              [datascript :as d]
              [fipp.edn :as fipp]
              [dato.datascript-utils :as dsu]
              [dato.client.utils.seq :refer [dissoc-in]]
              [om.core :as om])
    (:import [goog.ui IdGenerator]))

(defonce listeners (atom {}))

(defn ^:export inspect-listeners []
  (clj->js @listeners))

;; Things that aren't reified in the persistent/ephemeral datomic
;; instance
(def cs-schema
  {:local/current-session {:db/valueType :db.type/ref}
   :session/user          {:db/valueType :db.type/ref}
   :summon/session        {:db/valueType :db.type/ref}})

(defn enum [db db-id]
  ;; Try to support both straight db-id and {:db/id db-id}.
  ;; Messy, but seems to be a common mistake
  (when-let [eid (get db-id :db/id db-id)]
    (:db/ident (d/entity db eid))))

(defn enum-id [db enum]
  (dsu/q1-by db :db/ident enum))

;; Could probably speed this up with a few different conditionals
;; (compare db/ids directly if they're both number/map)
(defn enum= [db a b]
  (let [a (if (keyword? a) a (if (number? a)
                               (enum db a)
                               (enum db (:db/id a))))
        b (if (keyword? b) b (if (number? b)
                               (enum db b)
                               (enum db (:db/id b))))]
    (= a b)))

(defn handle-callbacks [db tx-report]
  (let [[eids attrs] (reduce (fn [[eids attrs] datom]
                               [(conj eids (:e datom))
                                (conj attrs (:a datom))])
                             [#{} #{}] (:tx-data tx-report))]
    (doseq [eid eids
            [k callback] (get-in @listeners [db :entity-listeners (str eid)])]
      (callback tx-report))
    (doseq [attr attrs
            [k callback] (get-in @listeners [db :attribute-listeners attr])]
      (callback tx-report))))

(defn setup-listener! [db key]
  (d/listen!
   db
   key
   (fn [tx-report]
     (js/setTimeout
      #(handle-callbacks db tx-report) 10))))

(defn add-entity-listener [conn eid key callback]
  (swap! listeners assoc-in [conn :entity-listeners (str eid) key] callback))

(defn add-attribute-listener [conn attribute key callback]
  (swap! listeners assoc-in [conn :attribute-listeners attribute key] callback))

(defn remove-entity-listener [conn eid key]
  (swap! listeners dissoc-in [conn :entity-listeners (str eid) key]))

(defn remove-attribute-listener [conn attribute key]
  (swap! listeners dissoc-in [conn :attribute-listeners attribute key]))

(defn add-unmounting-entity-listener [owner conn eid callback]
  (om/update-state-nr! owner (fn [s]
                               (update-in s [::listener-key] #(or % (.getNextUniqueId (.getInstance IdGenerator))))))
  (let [key (om/get-state owner ::listener-key)]
    (add-entity-listener conn eid key (fn [tx-report]
                                        (if (om/mounted? owner)
                                          (callback tx-report)
                                          (remove-entity-listener conn eid key))))))

(defn add-unmounting-attribute-listener [owner conn attribute callback]
  (om/update-state-nr! owner (fn [s]
                               (update-in s [::listener-key] #(or % (.getNextUniqueId (.getInstance IdGenerator))))))
  (let [key (om/get-state owner ::listener-key)]
    (add-attribute-listener conn attribute key (fn [tx-report]
                                                 (if (om/mounted? owner)
                                                   (callback tx-report)
                                                   (remove-attribute-listener conn attribute key))))))

(defn refresh-on! [owner conn listen-kvs]
  (doseq [listen-trigger listen-kvs]
    (let [[trigger-type trigger] listen-trigger
          add-trigger-fn         (case trigger-type
                                   :attr add-attribute-listener
                                   :eid  add-entity-listener)
          remove-trigger-fn      (case trigger-type
                                   :attr remove-attribute-listener
                                   :eid  remove-entity-listener)]
      (om/update-state-nr! owner
                           (fn [s]
                             (update-in s [::listener-keys listen-trigger]
                                        #(or % (.getNextUniqueId (.getInstance IdGenerator))))))
      (let [key (om/get-state owner [::listener-key listen-trigger])]
        (add-trigger-fn conn trigger key (fn [tx-report]
                                           (if (om/mounted? owner)
                                             (om/refresh! owner)
                                             (remove-trigger-fn conn trigger key))))))))

(defn datom->tx [datom fids]
  (let [[e a v d added?] datom]
    [(if added?
       :db/add
       :db/retract)
     ;; we know that eid will be unique to each entity. Pass it through
     ;; d/tempid in case future DS behavior changes
     (d/tempid :db.part/user e)
     a
     (get fids v v)]))

;; fid -> ds db/id
(def all-fids
  (atom {}))

(def pending-tx-info
  (atom {}))

(def pending-datoms
  (atom #{}))

(defn fast-fid [db e]
  (:v (first (d/datoms db :eavt e :dato/guid))))

(defn find-fid [tx-report e]
  (or (fast-fid (:db-after tx-report) e)
      (fast-fid (:db-before tx-report) e)))

(defn add-entity-fid [fid-map tx-report e]
  (if (contains? fid-map e)
    fid-map
    ;; TODO: write retractEntity fn that preserves fid
    (assoc fid-map e (find-fid tx-report e))))

(defn add-ref-fid [fid-map tx-report a v]
  (if (= :db.type/ref (get-in tx-report [:db-after :schema a :db/valueType]))
    (if (contains? fid-map v)
      fid-map
      (assoc fid-map v (find-fid tx-report v)))
    fid-map))

(defn fid-map [tx-report]
  (reduce (fn [acc {:keys [e a v]}]
            (-> acc
              (add-entity-fid tx-report e)
              (add-ref-fid tx-report a v)))
          {} (:tx-data tx-report)))

(defn prep-broadcastable-tx-report [tx-report]
  (let [fids    (fid-map tx-report)
        tx-guid (d/squuid)]
    (assert (every? identity (vals fids)) (str "Missing guid value in broadcast tx-report: " (pr-str fids) " - did you forget to add a :dato/guid to a new entity?"))
    (def l-tx-report tx-report)
    ;; We're broadcasting this, so we need to track the fids for the
    ;; lifetime of this db
    (def tx-fids fids)
    (swap! all-fids merge (sets/map-invert fids))
{'fraction_non_alphanumeric': 0.13690890797925856, 'fraction_numerical': 0.0004643603436266543, 'mean_word_length': 1.8275711159737418, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 48, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6580849', 'n_tokens_mistral': 4116, 'n_tokens_neox': 4038, 'n_words': 1202}