       out)))
 
 (defn notify-listeners [db-snap reports]
-  (doseq [listener (vals (get-in* db-snap [:listeners :tx-log]))]
+  (when (seq (get-in* db-snap [:listeners :entity]))
+    (doseq [eid (set (map first reports))]
+      (doseq [f (get-in* db-snap [:listeners :entity (get db-snap eid :id)])]
+        (f (entity db-snap eid)))))
+
+  (when (seq (get-in* db-snap [:listeners :entity-attr]))
+    (doseq [[eid attr] (set (map #(do [(first %) (second %)]) reports))]
+      (doseq [f (get-in* db-snap [:listeners :entity-attr (get db-snap eid :id) attr])]
+        (f (get db-snap eid attr)))))
+
+  (doseq [listener (get-in* db-snap [:listeners :tx-log])]
     (listener (remove nil? reports))))
 
 (defn transact! [db txs]

   (transact! db [{:db/id id
                   attr   val}])))

(defn update! [db id & args]
  (transact! db [(into [:db/update id] args)]))

(defn retract!
  ([db id]

          :else (do
                  (throw (js/Error (str "Not a valid id: " id)))))))

(defn has? [db-snap e]
  (contains? (:data db-snap) (resolve-id db-snap e)))

(defn create-id! [db]
  (:entity-count (swap! db update* :entity-count
                        (fn [c] (first (filter #(not (has? @db %))
                                               (iterate inc (inc c))))))))


(defn- listen-path!
  [db path f]
  (doto db
    (swap! update-in path #((fnil conj #{}) % f))))

(defn listen!
  ([db f]
   (listen-path! db [:listeners :tx-log] f))
  ([db id f]
   (listen-path! db [:listeners :entity id] f))
  ([db id attr f]
   (listen-path! db [:listeners :entity-attr id attr] f)))

(defn- unlisten-path!
  [db path f]
  (doto db
    (swap! update-in path #(disj % f))))

(defn unlisten!
  ([db f]
   (unlisten-path! db [:listeners :tx-log] f))
  ([db id f]
   (unlisten-path! db [:listeners :entity id] f))
  ([db id attr f]
   (unlisten-path! db [:listeners :entity-attr id attr] f)))

(defn upsert-attr? [id]
  (and (number? id) (neg? id)))
