create_graph.cljs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. (ns logseq.tasks.db-graph.create-graph
  2. "This ns provides fns to create a DB graph using EDN. See `init-conn` for
  3. initializing a DB graph with a datascript connection that syncs to a sqlite DB
  4. at the given directory. See `create-blocks-tx` for the EDN format to create a
  5. graph and current limitations"
  6. (:require [logseq.db.sqlite.db :as sqlite-db]
  7. [logseq.db.sqlite.util :as sqlite-util]
  8. [logseq.db.sqlite.create-graph :as sqlite-create-graph]
  9. [logseq.db.frontend.property.util :as db-property-util]
  10. [logseq.outliner.cli.pipeline :as cli-pipeline]
  11. [logseq.common.util :as common-util]
  12. [logseq.db :as ldb]
  13. [clojure.string :as string]
  14. [datascript.core :as d]
  15. ["fs" :as fs]
  16. ["path" :as node-path]
  17. [nbb.classpath :as cp]))
  18. (defn- find-on-classpath [rel-path]
  19. (some (fn [dir]
  20. (let [f (node-path/join dir rel-path)]
  21. (when (fs/existsSync f) f)))
  22. (string/split (cp/get-classpath) #":")))
  23. (defn- setup-init-data
  24. "Setup initial data same as frontend.handler.repo/create-db"
  25. [conn]
  26. (let [config-content (or (some-> (find-on-classpath "templates/config.edn") fs/readFileSync str)
  27. (do (println "Setting graph's config to empty since no templates/config.edn was found.")
  28. "{}"))]
  29. (d/transact! conn (sqlite-create-graph/build-db-initial-data config-content))))
  30. (defn init-conn
  31. "Create sqlite DB, initialize datascript connection and sync listener and then
  32. transacts initial data"
  33. [dir db-name]
  34. (fs/mkdirSync (node-path/join dir db-name) #js {:recursive true})
  35. ;; Same order as frontend.db.conn/start!
  36. (let [conn (sqlite-db/open-db! dir db-name)]
  37. (cli-pipeline/add-listener conn)
  38. (ldb/create-default-pages! conn {:db-graph? true})
  39. (setup-init-data conn)
  40. conn))
  41. (defn- translate-property-value
  42. "Translates a property value as needed. A value wrapped in vector indicates a reference type
  43. e.g. [:page \"some page\"]"
  44. [val {:keys [page-uuids block-uuids]}]
  45. (if (vector? val)
  46. (case (first val)
  47. :page
  48. (or (page-uuids (second val))
  49. (throw (ex-info (str "No uuid for page '" (second val) "'") {:name (second val)})))
  50. :block
  51. (or (block-uuids (second val))
  52. (throw (ex-info (str "No uuid for block '" (second val) "'") {:name (second val)})))
  53. (throw (ex-info "Invalid property value type. Valid values are :block and :page" {})))
  54. val))
  55. (defn- ->block-properties-tx [properties {:keys [property-uuids] :as uuid-maps}]
  56. (->> properties
  57. (map
  58. (fn [[prop-name val]]
  59. [(or (property-uuids prop-name)
  60. (throw (ex-info "No uuid for property" {:name prop-name})))
  61. ;; set indicates a :many value
  62. (if (set? val)
  63. (set (map #(translate-property-value % uuid-maps) val))
  64. (translate-property-value val uuid-maps))]))
  65. (into {})))
  66. (defn- create-uuid-maps
  67. "Creates maps of unique page names, block contents and property names to their uuids"
  68. [pages-and-blocks properties]
  69. (let [property-uuids (->> pages-and-blocks
  70. (map #(-> (:blocks %) vec (conj (:page %))))
  71. (mapcat #(->> % (map :properties) (mapcat keys)))
  72. set
  73. (map #(vector % (random-uuid)))
  74. ;; TODO: Dedupe with above to avoid squashing a previous definition
  75. (concat (map (fn [[k v]]
  76. [k (or (:block/uuid v) (random-uuid))])
  77. properties))
  78. (into {}))
  79. page-uuids (->> pages-and-blocks
  80. (map :page)
  81. (map (juxt #(or (:block/name %) (common-util/page-name-sanity-lc (:block/original-name %)))
  82. :block/uuid))
  83. (into {}))
  84. block-uuids (->> pages-and-blocks
  85. (mapcat :blocks)
  86. (map (juxt :block/content :block/uuid))
  87. (into {}))]
  88. {:property-uuids property-uuids
  89. :page-uuids page-uuids
  90. :block-uuids block-uuids}))
  91. (defn- build-property-refs [properties property-db-ids]
  92. (mapv
  93. (fn [prop-name]
  94. {:db/id
  95. (or (property-db-ids (name prop-name))
  96. (throw (ex-info (str "No :db/id for property '" prop-name "'") {:property prop-name})))})
  97. (keys properties)))
  98. (def current-db-id (atom 0))
  99. (def new-db-id
  100. "Provides the next temp :db/id to use in a create-graph transact!"
  101. #(swap! current-db-id dec))
  102. (defn- ->block-tx [m uuid-maps property-db-ids page-id last-block]
  103. (merge (dissoc m :properties)
  104. (sqlite-util/block-with-timestamps
  105. {:db/id (new-db-id)
  106. :block/format :markdown
  107. :block/page {:db/id page-id}
  108. :block/left {:db/id (or (:db/id last-block) page-id)}
  109. :block/parent {:db/id page-id}})
  110. (when (seq (:properties m))
  111. {:block/properties (->block-properties-tx (:properties m) uuid-maps)
  112. :block/refs (build-property-refs (:properties m) property-db-ids)})))
  113. (defn create-blocks-tx
  114. "Given an EDN map for defining pages, blocks and properties, this creates a
  115. vector of transactable data for use with d/transact!. The blocks that can be created
  116. have the following limitations:
  117. * Only top level blocks can be easily defined. Other level blocks can be
  118. defined but they require explicit setting of attributes like :block/left and :block/parent
  119. * Block content containing page refs or tags is not supported yet
  120. The EDN map has the following keys:
  121. * :pages-and-blocks - This is a vector of maps containing a :page key and optionally a :blocks
  122. key when defining a page's blocks. More about each key:
  123. * :page - This is a datascript attribute map e.g. `{:block/name \"foo\"}` .
  124. :block/name is required and :properties can be passed to define page properties
  125. * :blocks - This is a vec of datascript attribute maps e.g. `{:block/content \"bar\"}`.
  126. :block/content is required and :properties can be passed to define block properties
  127. * :properties - This is a map to configure properties where the keys are property names
  128. and the values are maps of datascript attributes e.g. `{:block/schema {:type :checkbox}}`.
  129. An additional key `:closed-values` is available to define closed values. The key takes
  130. a vec of maps containing keys :uuid, :value and :icon.
  131. The :properties for :pages-and-blocks is a map of property names to property
  132. values. Multiple property values for a many cardinality property are defined
  133. as a set. The following property types are supported: :default, :url,
  134. :checkbox, :number, :page and :date. :checkbox and :number values are written
  135. as booleans and integers. :page and :block are references that are written as
  136. vectors e.g. `[:page \"PAGE NAME\"]` and `[:block \"block content\"]`
  137. This fn also takes an optional map arg which supports these keys:
  138. * :property-uuids - A map of property keyword names to uuids to provide ids for built-in properties"
  139. [{:keys [pages-and-blocks properties]} & {:as options}]
  140. (let [;; add uuids before tx for refs in :properties
  141. pages-and-blocks' (mapv (fn [{:keys [page blocks]}]
  142. (cond-> {:page (merge {:block/uuid (random-uuid)} page)}
  143. (seq blocks)
  144. (assoc :blocks (mapv #(merge {:block/uuid (random-uuid)} %) blocks))))
  145. pages-and-blocks)
  146. {:keys [property-uuids] :as uuid-maps} (create-uuid-maps pages-and-blocks' properties)
  147. property-db-ids (->> property-uuids
  148. (map #(vector (name (first %)) (new-db-id)))
  149. (into {}))
  150. new-properties-tx (vec
  151. (mapcat
  152. (fn [[prop-name uuid]]
  153. (if (get-in properties [prop-name :closed-values])
  154. (db-property-util/build-closed-values
  155. prop-name
  156. (assoc (get properties prop-name) :block/uuid uuid)
  157. {:icon-id
  158. (get-in options [:property-uuids :icon])
  159. :translate-closed-page-value-fn
  160. #(hash-map :block/uuid (translate-property-value (:value %) uuid-maps))
  161. :property-attributes
  162. {:db/id (or (property-db-ids (name prop-name))
  163. (throw (ex-info "No :db/id for property" {:property prop-name})))}})
  164. [(merge
  165. (sqlite-util/build-new-property prop-name (get-in properties [prop-name :block/schema]) uuid)
  166. {:db/id (or (property-db-ids (name prop-name))
  167. (throw (ex-info "No :db/id for property" {:property prop-name})))}
  168. (when-let [props (not-empty (get-in properties [prop-name :properties]))]
  169. {:block/properties (->block-properties-tx props uuid-maps)
  170. :block/refs (build-property-refs props property-db-ids)}))]))
  171. property-uuids))
  172. pages-and-blocks-tx
  173. (vec
  174. (mapcat
  175. (fn [{:keys [page blocks]}]
  176. (let [page-id (or (:db/id page) (new-db-id))]
  177. (into
  178. ;; page tx
  179. [(sqlite-util/block-with-timestamps
  180. (merge
  181. {:db/id page-id
  182. :block/original-name (or (:block/original-name page) (string/capitalize (:block/name page)))
  183. :block/name (or (:block/name page) (common-util/page-name-sanity-lc (:block/original-name page)))
  184. :block/journal? false
  185. :block/format :markdown}
  186. (dissoc page :properties)
  187. (when (seq (:properties page))
  188. {:block/properties (->block-properties-tx (:properties page) uuid-maps)
  189. :block/refs (build-property-refs (:properties page) property-db-ids)
  190. ;; app doesn't do this yet but it should to link property to page
  191. :block/path-refs (build-property-refs (:properties page) property-db-ids)})))]
  192. ;; blocks tx
  193. (reduce (fn [acc m]
  194. (conj acc
  195. (->block-tx m uuid-maps property-db-ids page-id (last acc))))
  196. []
  197. blocks))))
  198. pages-and-blocks'))]
  199. (into pages-and-blocks-tx new-properties-tx)))