perf: cache d/entity result for immutable entities

This commit is contained in:
rcmerci
2024-12-27 19:46:07 +08:00
parent 41f878c7ce
commit d36f1c35f2
18 changed files with 181 additions and 81 deletions

View File

@@ -80,11 +80,11 @@ frontend.ui/_emoji-init-data
frontend.worker.rtc.op-mem-layer/_sync-loop-canceler
;; Used by shadow.cljs
frontend.worker.db-worker/init
;; WIP fn, remove when it's ready
frontend.worker.rtc.asset-sync/<loop-for-assets-sync
;; Future use?
frontend.worker.rtc.hash/hash-blocks
;; Repl fn
frontend.rum/use-atom-in
;; missionary utils
frontend.common.missionary-util/<!
;; Not sure why carve said it's unused...
frontend.flows/current-repo-flow

View File

@@ -3,22 +3,22 @@
For shared file graph only fns, use logseq.graph-parser.db"
(:require [clojure.set :as set]
[clojure.string :as string]
[clojure.walk :as walk]
[datascript.core :as d]
[datascript.impl.entity :as de]
[logseq.common.config :as common-config]
[logseq.common.util :as common-util]
[logseq.common.util.namespace :as ns-util]
[logseq.common.util.page-ref :as page-ref]
[logseq.common.uuid :as common-uuid]
[logseq.db.frontend.class :as db-class]
[logseq.db.frontend.delete-blocks :as delete-blocks] ;; Load entity extensions
[logseq.db.frontend.entity-plus]
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.property :as db-property]
[logseq.db.frontend.rules :as rules]
[logseq.db.sqlite.common-db :as sqlite-common-db]
[logseq.db.sqlite.util :as sqlite-util]
[logseq.db.frontend.property :as db-property]
[logseq.common.util.namespace :as ns-util]
[logseq.common.util.page-ref :as page-ref]
[clojure.walk :as walk])
[logseq.db.sqlite.util :as sqlite-util])
(:refer-clojure :exclude [object?]))
(defonce *transact-fn (atom nil))
@@ -192,7 +192,7 @@
(def get-first-page-by-name sqlite-common-db/get-first-page-by-name)
(def db-based-graph? entity-util/db-based-graph?)
(def db-based-graph? entity-plus/db-based-graph?)
(defn page-exists?
"Returns truthy value if page exists.

View File

@@ -4,7 +4,8 @@
[logseq.common.util.page-ref :as page-ref]
[datascript.core :as d]
[logseq.common.util :as common-util]
[logseq.db.frontend.entity-util :as entity-util]))
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.entity-plus :as entity-plus]))
;; [[uuid]]
(def id-ref-pattern
@@ -121,7 +122,7 @@
(defn update-block-content
"Replace `[[internal-id]]` with `[[page name]]`"
[db item eid]
(if (entity-util/db-based-graph? db)
(if (entity-plus/db-based-graph? db)
(if-let [content (:block/title item)]
(let [refs (:block/refs (d/entity db eid))]
(assoc item :block/title (id-ref->title-ref content refs false)))

View File

@@ -5,7 +5,8 @@
[logseq.common.util.page-ref :as page-ref]
[datascript.core :as d]
[clojure.string :as string]
[logseq.db.frontend.entity-util :as entity-util]))
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.entity-plus :as entity-plus]))
(defn- replace-ref-with-deleted-block-title
[block ref-raw-title]
@@ -57,7 +58,7 @@
(when (seq retracted-block-ids)
(let [retracted-blocks (map #(d/entity db %) retracted-block-ids)
retracted-tx (build-retracted-tx retracted-blocks)
macros-tx (when-not (entity-util/db-based-graph? db)
macros-tx (when-not (entity-plus/db-based-graph? db)
(mapcat (fn [b]
;; Only delete if last reference
(keep #(when (<= (count (:block/_macros (d/entity db (:db/id %))))

View File

@@ -5,23 +5,80 @@
;; Disable clj linters since we don't support clj
#?(:clj {:clj-kondo/config {:linters {:unresolved-namespace {:level :off}
:unresolved-symbol {:level :off}}}})
(:require [cljs.core]
#?(:org.babashka/nbb [datascript.db])
(:require #?(:org.babashka/nbb [datascript.db])
[cljs.core]
[datascript.core :as d]
[datascript.impl.entity :as entity :refer [Entity]]
[logseq.db.frontend.content :as db-content]
[logseq.db.frontend.property :as db-property]
[logseq.db.frontend.entity-util :as entity-util]
[frontend.common.missionary-util :as c.m]
[frontend.flows :as flows]
[logseq.common.util.date-time :as date-time-util]
[datascript.core :as d]))
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.property :as db-property]
[missionary.core :as m]))
(def db-based-graph? entity-util/db-based-graph?)
(def immutable-db-idents
"These db-ident entities are immutable,
it means `(db/entity :block/title)` always return same result"
#{:block/created-at :block/updated-at
:block/uuid :block/title :block/tags :block/name :block/format
:block/schema :block/path-refs :block/refs :block/tx-id :block/type
:block/page :block/parent :block/order :block/journal-day :block/closed-value-property
:block/link :block/marker :block/warning :block/collapsed? :block/deadline :block/scheduled :block/level
:block/pre-block? :block/heading-level
(def lookup-entity @#'entity/lookup-entity)
:block/_refs :logseq.property/_query
:block.temp/search? :block.temp/ast-title :block.temp/ast-body
:block.temp/fully-loaded? :block.temp/top? :block.temp/bottom?
:db/cardinality :db/ident :db/index :db/valueType
:logseq.kv/db-type
:logseq.property.node/display-type :logseq.property/icon
:logseq.property.asset/type :logseq.property.asset/checksum
:logseq.property/created-from-property
:logseq.class/Query :logseq.class/Journal :logseq.class/Cards :logseq.class/Task})
(def ^:private lookup-entity @#'entity/lookup-entity)
(def ^:private *seen-immutable-entities (volatile! {}))
(defn reset-immutable-entities-cache!
[]
(vreset! *seen-immutable-entities {}))
(c.m/run-background-task
::reset-immutable-entities-cache!
(m/reduce
(fn [_ repo]
(when (some? repo)
(prn :reset-immutable-entities-cache!)
(reset-immutable-entities-cache!)))
flows/current-repo-flow))
(defn entity-memoized
[db eid]
(if (and (qualified-keyword? eid)
(contains? immutable-db-idents eid))
(if-let [e (find @*seen-immutable-entities eid)]
(val e)
(let [r (d/entity db eid)]
(vswap! *seen-immutable-entities assoc eid r)
r))
(d/entity db eid)))
(defn db-based-graph?
"Whether the current graph is db-only"
[db]
(when db
(= "db" (:kv/value (entity-memoized db :logseq.kv/db-type)))))
(defn- get-journal-title
[db e]
(date-time-util/int->journal-title (:block/journal-day e)
(:logseq.property.journal/title-format (d/entity db :logseq.class/Journal))))
(:logseq.property.journal/title-format (entity-memoized db :logseq.class/Journal))))
(defn- get-block-title
[^Entity e k default-value]
@@ -36,7 +93,7 @@
(let [result (lookup-entity e k default-value)
refs (:block/refs e)
result' (if (and (string? result) refs)
(db-content/id-ref->title-ref result refs search?)
((resolve 'logseq.db.frontend.content/id-ref->title-ref) result refs search?)
result)]
(or result' default-value)))))))
@@ -51,7 +108,7 @@
result
;; property default value
(when (qualified-keyword? k)
(when-let [property (d/entity db k)]
(when-let [property (entity-memoized db k)]
(let [schema (lookup-entity property :block/schema nil)]
(if (= :checkbox (:type schema))
(lookup-entity property :logseq.property/scalar-default-value nil)
@@ -107,7 +164,9 @@
[^js this]
(let [v @(.-cache this)
v' (if (:block/title v)
(assoc v :block/title (db-content/id-ref->title-ref (:block/title v) (:block/refs this) (:block.temp/search? this)))
(assoc v :block/title
((resolve 'logseq.db.frontend.content/id-ref->title-ref)
(:block/title v) (:block/refs this) (:block.temp/search? this)))
v)]
(concat (seq v')
(seq (.-kv this)))))

View File

@@ -1,16 +1,9 @@
(ns logseq.db.frontend.entity-util
"Lower level entity util fns used across db namespaces"
(:require [datascript.core :as d]
[clojure.string :as string]
(:require [clojure.string :as string]
[datascript.impl.entity :as de])
(:refer-clojure :exclude [object?]))
(defn db-based-graph?
"Whether the current graph is db-only"
[db]
(when db
(= "db" (:kv/value (d/entity db :logseq.kv/db-type)))))
(defn- has-tag?
[entity tag-ident]
(let [tags (:block/tags entity)]
@@ -92,4 +85,4 @@
:logseq.class/Journal :journal
:logseq.class/Whiteboard :whiteboard
:logseq.class/Page :page}]
(set (map #(ident->type (:db/ident %)) (:block/tags entity)))))
(set (map #(ident->type (:db/ident %)) (:block/tags entity)))))

View File

@@ -1,15 +1,16 @@
(ns logseq.db.sqlite.common-db
"Common sqlite db fns for browser and node"
(:require [datascript.core :as d]
["path" :as node-path]
[clojure.string :as string]
[logseq.db.sqlite.util :as sqlite-util]
[logseq.common.util.date-time :as date-time-util]
[logseq.common.util :as common-util]
[logseq.common.config :as common-config]
[logseq.db.frontend.entity-util :as entity-util]
(:require ["path" :as node-path]
[clojure.set :as set]
[logseq.db.frontend.order :as db-order]))
[clojure.string :as string]
[datascript.core :as d]
[logseq.common.config :as common-config]
[logseq.common.util :as common-util]
[logseq.common.util.date-time :as date-time-util]
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.order :as db-order]
[logseq.db.sqlite.util :as sqlite-util]))
(defn- get-pages-by-name
[db page-name]
@@ -81,7 +82,7 @@
(defn- property-with-values
[db block]
(when (entity-util/db-based-graph? db)
(when (entity-plus/db-based-graph? db)
(let [block (d/entity db (:db/id block))]
(->> (:block/properties block)
vals
@@ -242,7 +243,7 @@
"Returns current database schema and initial data.
NOTE: This fn is called by DB and file graphs"
[db]
(let [db-graph? (entity-util/db-based-graph? db)
(let [db-graph? (entity-plus/db-based-graph? db)
_ (when db-graph?
(reset! db-order/*max-key (db-order/get-max-order db)))
schema (:schema db)

View File

@@ -1,9 +1,10 @@
(ns ^:node-only logseq.db.test.helper
"Main ns for providing test fns for DB graphs"
(:require [datascript.core :as d]
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.db.frontend.schema :as db-schema]
[logseq.db.sqlite.build :as sqlite-build]
[logseq.db.sqlite.create-graph :as sqlite-create-graph]
[logseq.db.frontend.schema :as db-schema]))
[logseq.db.sqlite.create-graph :as sqlite-create-graph]))
(defn find-block-by-content
"Find first block by exact block string or by fuzzier regex"
@@ -43,6 +44,7 @@
[]
(let [conn (d/create-conn db-schema/schema-for-db-based-graph)
_ (d/transact! conn (sqlite-create-graph/build-db-initial-data "{}"))]
(entity-plus/reset-immutable-entities-cache!)
conn))
(defn create-conn-with-blocks
@@ -50,4 +52,4 @@
[opts]
(let [conn (create-conn)
_ (sqlite-build/create-blocks conn opts)]
conn))
conn))

View File

@@ -439,7 +439,7 @@
(defn- property-with-position?
[db property-id block position]
(let [property (d/entity db property-id)
(let [property (entity-plus/entity-memoized db property-id)
schema (:block/schema property)]
(and
(= (:position schema) position)

View File

@@ -1,8 +1,9 @@
(ns logseq.outliner.validate-test
(:require [cljs.test :refer [deftest is are testing]]
(:require [cljs.test :refer [are deftest is testing]]
[datascript.core :as d]
[logseq.outliner.validate :as outliner-validate]
[logseq.db.test.helper :as db-test]))
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.db.test.helper :as db-test]
[logseq.outliner.validate :as outliner-validate]))
(deftest validate-block-title-unique-for-properties
(let [conn (db-test/create-conn-with-blocks
@@ -99,8 +100,8 @@
(is (thrown-with-msg?
js/Error
#"Can't change.*built-in"
(outliner-validate/validate-parent-property (d/entity @conn :logseq.class/Task)
[(d/entity @conn :logseq.class/Cards)]))))))
(outliner-validate/validate-parent-property (entity-plus/entity-memoized @conn :logseq.class/Task)
[(entity-plus/entity-memoized @conn :logseq.class/Cards)]))))))
(deftest validate-tags-property
(let [conn (db-test/create-conn-with-blocks

View File

@@ -1,17 +1,17 @@
(ns logseq.publishing.db
"Provides db fns and associated util fns for publishing"
(:require [datascript.core :as d]
[logseq.db.frontend.rules :as rules]
[clojure.set :as set]
(:require [clojure.set :as set]
[clojure.string :as string]
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.malli-schema :as db-malli-schema]))
[datascript.core :as d]
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.db.frontend.malli-schema :as db-malli-schema]
[logseq.db.frontend.rules :as rules]))
(defn ^:api get-area-block-asset-url
"Returns asset url for an area block used by pdf assets. This lives in this ns
because it is used by this dep and needs to be independent from the frontend app"
[db block page]
(let [db-based? (entity-util/db-based-graph? db)]
(let [db-based? (entity-plus/db-based-graph? db)]
(when-some [uuid' (:block/uuid block)]
(if db-based?
(when-let [image (:logseq.property.pdf/hl-image block)]
@@ -105,7 +105,7 @@
(defn- hl-type-area-fn
[db]
(if (entity-util/db-based-graph? db)
(if (entity-plus/db-based-graph? db)
(fn [datom]
(and (= :logseq.property.pdf/hl-type (:a datom))
(= (keyword (:v datom)) :area)))

View File

@@ -19,8 +19,8 @@
[frontend.components.property.value :as pv]
[frontend.components.query :as query]
[frontend.components.query.builder :as query-builder-component]
[frontend.components.svg :as svg]
[frontend.components.select :as select]
[frontend.components.svg :as svg]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.date :as date]
@@ -78,6 +78,7 @@
[logseq.common.util.page-ref :as page-ref]
[logseq.db :as ldb]
[logseq.db.frontend.content :as db-content]
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.graph-parser.block :as gp-block]
[logseq.graph-parser.mldoc :as gp-mldoc]
[logseq.graph-parser.text :as text]
@@ -2285,7 +2286,9 @@
;; highlight ref block (area)
(when area? [(hl-ref)])
(when (and (seq block-ast-title) (ldb/class-instance? (db/entity :logseq.class/Cards) block))
(when (and (seq block-ast-title) (ldb/class-instance?
(entity-plus/entity-memoized (db/get-db) :logseq.class/Cards)
block))
[(ui/tooltip
(shui/button
{:variant :ghost
@@ -2302,7 +2305,8 @@
(let [collapsed? (:collapsed? config)
block' (db/entity (:db/id block))
node-display-type (:logseq.property.node/display-type block')
query? (ldb/class-instance? (db/entity :logseq.class/Query) block')
db (db/get-db)
query? (ldb/class-instance? (entity-plus/entity-memoized db :logseq.class/Query) block')
query (:logseq.property/query block')
advanced-query? (and query? (= :code node-display-type))]
(cond
@@ -3278,7 +3282,8 @@
(rum/defc query-property-cp < rum/reactive db-mixins/query
[block config collapsed?]
(let [block (db/entity (:db/id block))
query? (ldb/class-instance? (db/entity :logseq.class/Query) block)]
db (db/get-db)
query? (ldb/class-instance? (entity-plus/entity-memoized db :logseq.class/Query) block)]
(when (and query? (not collapsed?))
(let [query-id (:db/id (:logseq.property/query block))
query (some-> query-id db/sub-block)
@@ -3497,7 +3502,7 @@
(db-properties-cp config block {:in-block-container? true})])
(when (and db-based? (not collapsed?) (not (or table? property?))
(ldb/class-instance? (db/entity :logseq.class/Query) block))
(ldb/class-instance? (entity-plus/entity-memoized (db/get-db) :logseq.class/Query) block))
(let [query-block (:logseq.property/query (db/entity (:db/id block)))
query-block (if query-block (db/sub-block (:db/id query-block)) query-block)
query (:block/title query-block)
@@ -3506,7 +3511,9 @@
[:div {:style {:padding-left 42}}
(query/custom-query (wrap-query-components (assoc config
:dsl-query? (not advanced-query?)
:cards? (ldb/class-instance? (db/entity :logseq.class/Cards) block)))
:cards? (ldb/class-instance? (entity-plus/entity-memoized
(db/get-db)
:logseq.class/Cards) block)))
(if advanced-query? result {:builder nil
:query (query-builder-component/sanitize-q query)}))]))

View File

@@ -2,6 +2,7 @@
"Entry ns for the mobile, browser and electron frontend apps"
{:dev/always true}
(:require [frontend.common-keywords]
[frontend.common.schema-register :as sr]
[frontend.components.plugins :as plugins]
[frontend.config :as config]
[frontend.fs.sync :as sync]
@@ -11,7 +12,6 @@
[frontend.log]
[frontend.page :as page]
[frontend.routes :as routes]
[frontend.common.schema-register :as sr]
[frontend.spec]
[logseq.api]
[malli.dev.cljs :as md]
@@ -58,11 +58,28 @@
(when config/dev?
(js/setTimeout #(sync/<sync-start) 1000))))
(comment
(defn- setup-entity-profile!
[]
(let [origin-d-entity d/entity
d-entity-count (volatile! 0)
ident->count (volatile! {})]
(set! d/entity (fn [& args]
(let [r (apply origin-d-entity args)
k (last args)]
(when (= :logseq.class/Query k)
(js/console.trace))
(vswap! d-entity-count inc)
(vswap! ident->count update k inc)
(prn @d-entity-count (:db/id r) k (get @ident->count k))
r))))))
(defn ^:export init []
;; init is called ONCE when the page loads
;; this is called in the index.html and must be exported
;; so it is available even in :advanced release builds
;; (setup-entity-profile!)
(plugin-handler/setup!
#(handler/start! start)))

View File

@@ -18,6 +18,7 @@
[frontend.ui :as ui]
[frontend.util :as util]
[logseq.db :as ldb]
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.shui.ui :as shui]
[missionary.core :as m]
[open-spaced-repetition.cljc-fsrs.core :as fsrs.core]
@@ -247,7 +248,7 @@
all-cards (concat
[{:db/id :global
:block/title "All cards"}]
(db-model/get-class-objects repo (:db/id (db/entity :logseq.class/Cards))))
(db-model/get-class-objects repo (:db/id (entity-plus/entity-memoized (db/get-db) :logseq.class/Cards))))
*block-ids (::block-ids state)
block-ids (rum/react *block-ids)
loading? (rum/react (::loading? state))

View File

@@ -0,0 +1,11 @@
(ns frontend.flows
"This ns contains some event flows."
(:require [missionary.core :as m]))
(def *current-repo (atom nil))
(def current-repo-flow
"Like get-current-repo."
(m/eduction
(dedupe)
(m/watch *current-repo)))

View File

@@ -2,6 +2,7 @@
(:require [clojure.set :as set]
[clojure.string :as string]
[clojure.walk :as w]
[datascript.core :as d]
[dommy.core :as dom]
[frontend.commands :as commands]
[frontend.config :as config]
@@ -54,18 +55,18 @@
[logseq.common.util.block-ref :as block-ref]
[logseq.common.util.page-ref :as page-ref]
[logseq.db :as ldb]
[logseq.db.frontend.schema :as db-schema]
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.db.frontend.property :as db-property]
[logseq.db.frontend.schema :as db-schema]
[logseq.graph-parser.block :as gp-block]
[logseq.graph-parser.mldoc :as gp-mldoc]
[logseq.graph-parser.property :as gp-property]
[logseq.graph-parser.text :as text]
[logseq.graph-parser.utf8 :as utf8]
[logseq.outliner.core :as outliner-core]
[promesa.core :as p]
[rum.core :as rum]
[logseq.outliner.property :as outliner-property]
[datascript.core :as d]))
[promesa.core :as p]
[rum.core :as rum]))
;; FIXME: should support multiple images concurrently uploading
@@ -3457,15 +3458,16 @@
(defn- db-collapsable?
[block]
(let [class-properties (:classes-properties (outliner-property/get-block-classes-properties (db/get-db) (:db/id block)))
properties (->> (map :a (d/datoms (db/get-db) :eavt (:db/id block)))
(map db/entity)
db (db/get-db)
properties (->> (map :a (d/datoms db :eavt (:db/id block)))
(map (partial entity-plus/entity-memoized db))
(concat class-properties)
(remove (fn [e] (db-property/db-attribute-properties (:db/ident e))))
(remove outliner-property/property-with-other-position?)
(remove (fn [e] (:hide? (:block/schema e))))
(remove nil?))]
(or (seq properties)
(ldb/class-instance? (db/entity :logseq.class/Query) block))))
(ldb/class-instance? (entity-plus/entity-memoized db :logseq.class/Query) block))))
(defn collapsable?
([block-id]

View File

@@ -11,6 +11,7 @@
[electron.ipc :as ipc]
[frontend.db.conn-state :as db-conn-state]
[frontend.db.transact :as db-transact]
[frontend.flows :as flows]
[frontend.mobile.util :as mobile-util]
[frontend.rum :as r]
[frontend.spec.storage :as storage-spec]
@@ -21,6 +22,7 @@
[goog.object :as gobj]
[logseq.common.config :as common-config]
[logseq.db :as ldb]
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.db.sqlite.util :as sqlite-util]
[logseq.shui.dialog.core :as shui-dialog]
[logseq.shui.ui :as shui]
@@ -599,7 +601,7 @@ should be done through this fn in order to get global config and config defaults
(let [repo (get-current-repo)]
(if (sqlite-util/db-based-graph? repo)
(when-let [conn (db-conn-state/get-conn repo)]
(get (d/entity @conn :logseq.class/Journal)
(get (entity-plus/entity-memoized @conn :logseq.class/Journal)
:logseq.property.journal/title-format
"MMM do, yyyy"))
(common-config/get-date-formatter (get-config)))))
@@ -978,6 +980,7 @@ Similar to re-frame subscriptions"
(defn set-current-repo!
[repo]
(swap! state assoc :git/current-repo repo)
(reset! flows/*current-repo repo)
(if repo
(storage/set :git/current-repo repo)
(storage/remove :git/current-repo))

View File

@@ -7,15 +7,16 @@
[logseq.common.util.namespace :as ns-util]
[logseq.db :as ldb]
[logseq.db.frontend.class :as db-class]
[logseq.db.frontend.entity-plus :as entity-plus]
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.malli-schema :as db-malli-schema]
[logseq.db.frontend.order :as db-order]
[logseq.db.frontend.property.build :as db-property-build]
[logseq.db.frontend.property.util :as db-property-util]
[logseq.db.sqlite.util :as sqlite-util]
[logseq.graph-parser.block :as gp-block]
[logseq.graph-parser.text :as text]
[logseq.outliner.validate :as outliner-validate]
[logseq.db.frontend.entity-util :as entity-util]
[logseq.db.frontend.malli-schema :as db-malli-schema]))
[logseq.outliner.validate :as outliner-validate]))
(defn- build-page-tx [conn properties page {:keys [whiteboard? class? tags]}]
(when (:block/uuid page)
@@ -168,7 +169,7 @@
skip-existing-page-check? false}
:as options}]
(let [db @conn
date-formatter (:logseq.property.journal/title-format (d/entity db :logseq.class/Journal))
date-formatter (:logseq.property.journal/title-format (entity-plus/entity-memoized db :logseq.class/Journal))
title (sanitize-title title*)
types (cond class?
#{:logseq.class/Tag}