db_worker.cljs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. (ns frontend.worker.db-worker
  2. "Worker used for browser DB implementation"
  3. (:require ["@logseq/sqlite-wasm" :default sqlite3InitModule]
  4. ["comlink" :as Comlink]
  5. [cljs-bean.core :as bean]
  6. [cljs.cache :as cache]
  7. [clojure.edn :as edn]
  8. [clojure.set]
  9. [clojure.string :as string]
  10. [datascript.core :as d]
  11. [datascript.storage :refer [IStorage] :as storage]
  12. [frontend.common.cache :as common.cache]
  13. [frontend.common.graph-view :as graph-view]
  14. [frontend.common.missionary :as c.m]
  15. [frontend.common.thread-api :as thread-api :refer [def-thread-api]]
  16. [frontend.worker-common.util :as worker-util]
  17. [frontend.worker.db-listener :as db-listener]
  18. [frontend.worker.db-metadata :as worker-db-metadata]
  19. [frontend.worker.db.fix :as db-fix]
  20. [frontend.worker.db.migrate :as db-migrate]
  21. [frontend.worker.db.validate :as worker-db-validate]
  22. [frontend.worker.embedding :as embedding]
  23. [frontend.worker.export :as worker-export]
  24. [frontend.worker.file :as file]
  25. [frontend.worker.file.reset :as file-reset]
  26. [frontend.worker.handler.page :as worker-page]
  27. [frontend.worker.handler.page.file-based.rename :as file-worker-page-rename]
  28. [frontend.worker.rtc.asset-db-listener]
  29. [frontend.worker.rtc.client-op :as client-op]
  30. [frontend.worker.rtc.core :as rtc.core]
  31. [frontend.worker.rtc.db-listener]
  32. [frontend.worker.search :as search]
  33. [frontend.worker.shared-service :as shared-service]
  34. [frontend.worker.state :as worker-state]
  35. [frontend.worker.thread-atom]
  36. [goog.object :as gobj]
  37. [lambdaisland.glogi :as log]
  38. [lambdaisland.glogi.console :as glogi-console]
  39. [logseq.common.util :as common-util]
  40. [logseq.db :as ldb]
  41. [logseq.db.common.entity-plus :as entity-plus]
  42. [logseq.db.common.initial-data :as common-initial-data]
  43. [logseq.db.common.order :as db-order]
  44. [logseq.db.common.sqlite :as common-sqlite]
  45. [logseq.db.common.view :as db-view]
  46. [logseq.db.frontend.schema :as db-schema]
  47. [logseq.db.sqlite.create-graph :as sqlite-create-graph]
  48. [logseq.db.sqlite.export :as sqlite-export]
  49. [logseq.db.sqlite.gc :as sqlite-gc]
  50. [logseq.db.sqlite.util :as sqlite-util]
  51. [logseq.outliner.op :as outliner-op]
  52. [me.tonsky.persistent-sorted-set :as set :refer [BTSet]]
  53. [missionary.core :as m]
  54. [promesa.core :as p]))
  55. (defonce *sqlite worker-state/*sqlite)
  56. (defonce *sqlite-conns worker-state/*sqlite-conns)
  57. (defonce *datascript-conns worker-state/*datascript-conns)
  58. (defonce *client-ops-conns worker-state/*client-ops-conns)
  59. (defonce *opfs-pools worker-state/*opfs-pools)
  60. (defonce *publishing? (atom false))
  61. (defn- check-worker-scope!
  62. []
  63. (when (or (gobj/get js/self "React")
  64. (gobj/get js/self "module$react"))
  65. (throw (js/Error. "[db-worker] React is forbidden in worker scope!"))))
  66. (defn- <get-opfs-pool
  67. [graph]
  68. (when-not @*publishing?
  69. (or (worker-state/get-opfs-pool graph)
  70. (p/let [^js pool (.installOpfsSAHPoolVfs ^js @*sqlite #js {:name (worker-util/get-pool-name graph)
  71. :initialCapacity 20})]
  72. (swap! *opfs-pools assoc graph pool)
  73. pool))))
  74. (defn- init-sqlite-module!
  75. []
  76. (when-not @*sqlite
  77. (p/let [sqlite (sqlite3InitModule (clj->js {:print js/console.log
  78. :printErr js/console.error}))]
  79. (reset! *sqlite sqlite)
  80. nil)))
  81. (def repo-path "/db.sqlite")
  82. (defn- <export-db-file
  83. [repo]
  84. (p/let [^js pool (<get-opfs-pool repo)]
  85. (when pool
  86. (.exportFile ^js pool repo-path))))
  87. (defn- <import-db
  88. [^js pool data]
  89. (.importDb ^js pool repo-path data))
  90. (defn- get-all-datoms-from-sqlite-db
  91. [db]
  92. (some->> (.exec db #js {:sql "select * from kvs"
  93. :rowMode "array"})
  94. bean/->clj
  95. (mapcat
  96. (fn [[_addr content _addresses]]
  97. (let [content' (sqlite-util/transit-read content)
  98. datoms (when (map? content')
  99. (:keys content'))]
  100. datoms)))
  101. distinct
  102. (map (fn [[e a v t]]
  103. (d/datom e a v t)))))
  104. (defn- rebuild-db-from-datoms!
  105. "Persistent-sorted-set has been broken, used addresses can't be found"
  106. [datascript-conn sqlite-db]
  107. (let [datoms (get-all-datoms-from-sqlite-db sqlite-db)
  108. db (d/init-db [] db-schema/schema
  109. {:storage (storage/storage @datascript-conn)})
  110. db (d/db-with db
  111. (map (fn [d]
  112. [:db/add (:e d) (:a d) (:v d) (:t d)]) datoms))]
  113. (prn :debug :rebuild-db-from-datoms :datoms-count (count datoms))
  114. (worker-util/post-message :notification ["The SQLite db will be exported to avoid any data-loss." :warning false])
  115. (worker-util/post-message :export-current-db [])
  116. (.exec sqlite-db #js {:sql "delete from kvs"})
  117. (d/reset-conn! datascript-conn db)))
  118. (defn- fix-broken-graph
  119. [graph]
  120. (let [conn (worker-state/get-datascript-conn graph)
  121. sqlite-db (worker-state/get-sqlite-conn graph)]
  122. (when (and conn sqlite-db)
  123. (rebuild-db-from-datoms! conn sqlite-db)
  124. (worker-util/post-message :notification ["The graph has been successfully rebuilt." :success false]))))
  125. (defn upsert-addr-content!
  126. "Upsert addr+data-seq. Update sqlite-cli/upsert-addr-content! when making changes"
  127. [db data]
  128. (assert (some? db) "sqlite db not exists")
  129. (.transaction db (fn [tx]
  130. (doseq [item data]
  131. (.exec tx #js {:sql "INSERT INTO kvs (addr, content, addresses) values ($addr, $content, $addresses) on conflict(addr) do update set content = $content, addresses = $addresses"
  132. :bind item})))))
  133. (defn restore-data-from-addr
  134. "Update sqlite-cli/restore-data-from-addr when making changes"
  135. [db addr]
  136. (assert (some? db) "sqlite db not exists")
  137. (when-let [result (-> (.exec db #js {:sql "select content, addresses from kvs where addr = ?"
  138. :bind #js [addr]
  139. :rowMode "array"})
  140. first)]
  141. (let [[content addresses] (bean/->clj result)
  142. addresses (when addresses
  143. (js/JSON.parse addresses))
  144. data (sqlite-util/transit-read content)]
  145. (if (and addresses (map? data))
  146. (assoc data :addresses addresses)
  147. data))))
  148. (defn new-sqlite-storage
  149. "Update sqlite-cli/new-sqlite-storage when making changes"
  150. [^Object db]
  151. (reify IStorage
  152. (-store [_ addr+data-seq _delete-addrs]
  153. (let [data (map
  154. (fn [[addr data]]
  155. (let [data' (if (map? data) (dissoc data :addresses) data)
  156. addresses (when (map? data)
  157. (when-let [addresses (:addresses data)]
  158. (js/JSON.stringify (bean/->js addresses))))]
  159. #js {:$addr addr
  160. :$content (sqlite-util/transit-write data')
  161. :$addresses addresses}))
  162. addr+data-seq)]
  163. (upsert-addr-content! db data)))
  164. (-restore [_ addr]
  165. (restore-data-from-addr db addr))))
  166. (defn- close-db-aux!
  167. [repo ^Object db ^Object search ^Object client-ops]
  168. (swap! *sqlite-conns dissoc repo)
  169. (swap! *datascript-conns dissoc repo)
  170. (swap! *client-ops-conns dissoc repo)
  171. (when db (.close db))
  172. (when search (.close search))
  173. (when client-ops (.close client-ops))
  174. (when-let [^js pool (worker-state/get-opfs-pool repo)]
  175. (.releaseAccessHandles pool))
  176. (swap! *opfs-pools dissoc repo))
  177. (defn- close-other-dbs!
  178. [repo]
  179. (doseq [[r {:keys [db search client-ops]}] @*sqlite-conns]
  180. (when-not (= repo r)
  181. (close-db-aux! r db search client-ops))))
  182. (defn close-db!
  183. [repo]
  184. (let [{:keys [db search client-ops]} (get @*sqlite-conns repo)]
  185. (close-db-aux! repo db search client-ops)))
  186. (defn reset-db!
  187. [repo db-transit-str]
  188. (when-let [conn (get @*datascript-conns repo)]
  189. (let [new-db (ldb/read-transit-str db-transit-str)
  190. new-db' (update new-db :eavt (fn [^BTSet s]
  191. (set! (.-storage s) (.-storage (:eavt @conn)))
  192. s))]
  193. (d/reset-conn! conn new-db' {:reset-conn! true})
  194. (d/reset-schema! conn (:schema new-db)))))
  195. (defn- get-dbs
  196. [repo]
  197. (if @*publishing?
  198. (p/let [^object DB (.-DB ^object (.-oo1 ^object @*sqlite))
  199. db (new DB "/db.sqlite" "c")
  200. search-db (new DB "/search-db.sqlite" "c")]
  201. [db search-db])
  202. (p/let [^js pool (<get-opfs-pool repo)
  203. capacity (.getCapacity pool)
  204. _ (when (zero? capacity) ; file handle already releases since pool will be initialized only once
  205. (.acquireAccessHandles pool))
  206. db (new (.-OpfsSAHPoolDb pool) repo-path)
  207. search-db (new (.-OpfsSAHPoolDb pool) (str "search" repo-path))
  208. client-ops-db (new (.-OpfsSAHPoolDb pool) (str "client-ops-" repo-path))]
  209. [db search-db client-ops-db])))
  210. (defn- enable-sqlite-wal-mode!
  211. [^Object db]
  212. (.exec db "PRAGMA locking_mode=exclusive")
  213. (.exec db "PRAGMA journal_mode=WAL"))
  214. (defn- gc-sqlite-dbs!
  215. "Gc main db weekly and rtc ops db each time when opening it"
  216. [sqlite-db client-ops-db datascript-conn {:keys [full-gc?]}]
  217. (let [last-gc-at (:kv/value (d/entity @datascript-conn :logseq.kv/graph-last-gc-at))]
  218. (when (or full-gc?
  219. (nil? last-gc-at)
  220. (not (number? last-gc-at))
  221. (> (- (common-util/time-ms) last-gc-at) (* 3 24 3600 1000))) ; 3 days ago
  222. (println :debug "gc current graph")
  223. (doseq [db (if @*publishing? [sqlite-db] [sqlite-db client-ops-db])]
  224. (sqlite-gc/gc-kvs-table! db {:full-gc? full-gc?})
  225. (.exec db "VACUUM"))
  226. (d/transact! datascript-conn [{:db/ident :logseq.kv/graph-last-gc-at
  227. :kv/value (common-util/time-ms)}]))))
  228. (defn- create-or-open-db!
  229. [repo {:keys [config datoms] :as opts}]
  230. (when-not (worker-state/get-sqlite-conn repo)
  231. (p/let [[db search-db client-ops-db :as dbs] (get-dbs repo)
  232. storage (new-sqlite-storage db)
  233. client-ops-storage (when-not @*publishing?
  234. (new-sqlite-storage client-ops-db))
  235. db-based? (sqlite-util/db-based-graph? repo)]
  236. (swap! *sqlite-conns assoc repo {:db db
  237. :search search-db
  238. :client-ops client-ops-db})
  239. (doseq [db' dbs]
  240. (enable-sqlite-wal-mode! db'))
  241. (common-sqlite/create-kvs-table! db)
  242. (when-not @*publishing? (common-sqlite/create-kvs-table! client-ops-db))
  243. (search/create-tables-and-triggers! search-db)
  244. (let [schema (ldb/get-schema repo)
  245. conn (common-sqlite/get-storage-conn storage schema)
  246. _ (db-fix/check-and-fix-schema! repo conn)
  247. _ (when datoms
  248. (let [data (map (fn [datom]
  249. [:db/add (:e datom) (:a datom) (:v datom)]) datoms)]
  250. (d/transact! conn data {:initial-db? true})))
  251. client-ops-conn (when-not @*publishing? (common-sqlite/get-storage-conn
  252. client-ops-storage
  253. client-op/schema-in-db))
  254. initial-data-exists? (when (nil? datoms)
  255. (and (d/entity @conn :logseq.class/Root)
  256. (= "db" (:kv/value (d/entity @conn :logseq.kv/db-type)))))]
  257. (swap! *datascript-conns assoc repo conn)
  258. (swap! *client-ops-conns assoc repo client-ops-conn)
  259. (when (and (not @*publishing?) (not= client-op/schema-in-db (d/schema @client-ops-conn)))
  260. (d/reset-schema! client-ops-conn client-op/schema-in-db))
  261. (when (and db-based? (not initial-data-exists?) (not datoms))
  262. (let [config (or config "")
  263. initial-data (sqlite-create-graph/build-db-initial-data config
  264. (select-keys opts [:import-type :graph-git-sha]))]
  265. (d/transact! conn initial-data {:initial-db? true})))
  266. (gc-sqlite-dbs! db client-ops-db conn {})
  267. (db-migrate/migrate conn)
  268. (db-listener/listen-db-changes! repo (get @*datascript-conns repo))))))
  269. (defn- iter->vec [iter']
  270. (when iter'
  271. (p/loop [acc []]
  272. (p/let [elem (.next iter')]
  273. (if (.-done elem)
  274. acc
  275. (p/recur (conj acc (.-value elem))))))))
  276. (comment
  277. (defn- <list-all-files
  278. []
  279. (let [dir? #(= (.-kind %) "directory")]
  280. (p/let [^js root (.getDirectory js/navigator.storage)]
  281. (p/loop [result []
  282. dirs [root]]
  283. (if (empty? dirs)
  284. result
  285. (p/let [dir (first dirs)
  286. result (conj result dir)
  287. values-iter (when (dir? dir) (.values dir))
  288. values (when values-iter (iter->vec values-iter))
  289. current-dir-dirs (filter dir? values)
  290. result (concat result values)
  291. dirs (concat
  292. current-dir-dirs
  293. (rest dirs))]
  294. (p/recur result dirs))))))))
  295. (defn- <list-all-dbs
  296. []
  297. (let [dir? #(= (.-kind %) "directory")
  298. db-dir-prefix ".logseq-pool-"]
  299. (p/let [^js root (.getDirectory js/navigator.storage)
  300. values-iter (when (dir? root) (.values root))
  301. values (when values-iter (iter->vec values-iter))
  302. current-dir-dirs (filter dir? values)
  303. db-dirs (filter (fn [file]
  304. (string/starts-with? (.-name file) db-dir-prefix))
  305. current-dir-dirs)]
  306. (log/info :db-dirs (map #(.-name %) db-dirs) :all-dirs (map #(.-name %) current-dir-dirs))
  307. (p/all (map (fn [dir]
  308. (p/let [graph-name (-> (.-name dir)
  309. (string/replace-first ".logseq-pool-" "")
  310. ;; TODO: DRY
  311. (string/replace "+3A+" ":")
  312. (string/replace "++" "/"))
  313. repo (str sqlite-util/db-version-prefix graph-name)
  314. metadata (worker-db-metadata/<get repo)]
  315. {:name graph-name
  316. :metadata (edn/read-string metadata)})) db-dirs)))))
  317. (def-thread-api :thread-api/list-db
  318. []
  319. (<list-all-dbs))
  320. (defn- <db-exists?
  321. [graph]
  322. (->
  323. (p/let [^js root (.getDirectory js/navigator.storage)
  324. _dir-handle (.getDirectoryHandle root (str "." (worker-util/get-pool-name graph)))]
  325. true)
  326. (p/catch
  327. (fn [_e] ; not found
  328. false))))
  329. (defn- remove-vfs!
  330. [^js pool]
  331. (when pool
  332. (.removeVfs ^js pool)))
  333. (defn- get-search-db
  334. [repo]
  335. (worker-state/get-sqlite-conn repo :search))
  336. (comment
  337. (def-thread-api :thread-api/get-version
  338. []
  339. (when-let [sqlite @*sqlite]
  340. (.-version sqlite))))
  341. (def-thread-api :thread-api/init
  342. [rtc-ws-url]
  343. (reset! worker-state/*rtc-ws-url rtc-ws-url)
  344. (init-sqlite-module!))
  345. ;; [graph service]
  346. (defonce *service (atom []))
  347. (defonce fns {"remoteInvoke" thread-api/remote-function})
  348. (defn- start-db!
  349. [repo {:keys [close-other-db?]
  350. :or {close-other-db? true}
  351. :as opts}]
  352. (p/do!
  353. (when close-other-db?
  354. (close-other-dbs! repo))
  355. (when @shared-service/*master-client?
  356. (create-or-open-db! repo (dissoc opts :close-other-db?)))
  357. nil))
  358. (def-thread-api :thread-api/create-or-open-db
  359. [repo opts]
  360. (start-db! repo opts))
  361. (def-thread-api :thread-api/q
  362. [repo inputs]
  363. (when-let [conn (worker-state/get-datascript-conn repo)]
  364. (apply d/q (first inputs) @conn (rest inputs))))
  365. (def-thread-api :thread-api/pull
  366. [repo selector id]
  367. (when-let [conn (worker-state/get-datascript-conn repo)]
  368. (let [eid (if (and (vector? id) (= :block/name (first id)))
  369. (:db/id (ldb/get-page @conn (second id)))
  370. id)]
  371. (some->> eid
  372. (d/pull @conn selector)
  373. (common-initial-data/with-parent @conn)))))
  374. (def ^:private *get-blocks-cache (volatile! (cache/lru-cache-factory {} :threshold 1000)))
  375. (def ^:private get-blocks-with-cache
  376. (common.cache/cache-fn
  377. *get-blocks-cache
  378. (fn [repo requests]
  379. (let [db (some-> (worker-state/get-datascript-conn repo) deref)]
  380. [[repo (:max-tx db) requests]
  381. [db requests]]))
  382. (fn [db requests]
  383. (when db
  384. (mapv (fn [{:keys [id opts]}]
  385. (let [id' (if (and (string? id) (common-util/uuid-string? id)) (uuid id) id)]
  386. (-> (common-initial-data/get-block-and-children db id' opts)
  387. (assoc :id id)))) requests)))))
  388. (def-thread-api :thread-api/get-blocks
  389. [repo requests]
  390. (get-blocks-with-cache repo requests))
  391. (def-thread-api :thread-api/get-block-refs
  392. [repo id]
  393. (when-let [conn (worker-state/get-datascript-conn repo)]
  394. (ldb/get-block-refs @conn id)))
  395. (def-thread-api :thread-api/get-block-refs-count
  396. [repo id]
  397. (when-let [conn (worker-state/get-datascript-conn repo)]
  398. (ldb/get-block-refs-count @conn id)))
  399. (def-thread-api :thread-api/get-block-source
  400. [repo id]
  401. (when-let [conn (worker-state/get-datascript-conn repo)]
  402. (:db/id (first (:block/_alias (d/entity @conn id))))))
  403. (defn- search-blocks
  404. [repo q option]
  405. (p/let [search-db (get-search-db repo)
  406. conn (worker-state/get-datascript-conn repo)]
  407. (search/search-blocks repo conn search-db q option)))
  408. (def-thread-api :thread-api/block-refs-check
  409. [repo id {:keys [unlinked?]}]
  410. (when-let [conn (worker-state/get-datascript-conn repo)]
  411. (let [db @conn
  412. block (d/entity db id)]
  413. (if unlinked?
  414. (p/let [title (string/lower-case (:block/title block))
  415. result (search-blocks repo title {:limit 100})]
  416. (boolean (some (fn [b]
  417. (let [block (d/entity db (:db/id b))]
  418. (and (not= id (:db/id block))
  419. (not ((set (map :db/id (:block/refs block))) id))
  420. (string/includes? (string/lower-case (:block/title block)) title)))) result)))
  421. (some? (first (common-initial-data/get-block-refs db (:db/id block))))))))
  422. (def-thread-api :thread-api/get-block-parents
  423. [repo id depth]
  424. (when-let [conn (worker-state/get-datascript-conn repo)]
  425. (let [block-id (:block/uuid (d/entity @conn id))]
  426. (->> (ldb/get-block-parents @conn block-id {:depth (or depth 3)})
  427. (map (fn [b] (d/pull @conn '[*] (:db/id b))))))))
  428. (def-thread-api :thread-api/set-context
  429. [context]
  430. (when context (worker-state/update-context! context))
  431. nil)
  432. (def-thread-api :thread-api/transact
  433. [repo tx-data tx-meta context]
  434. (assert (some? repo))
  435. (worker-state/set-db-latest-tx-time! repo)
  436. (let [conn (worker-state/get-datascript-conn repo)]
  437. (assert (some? conn) {:repo repo})
  438. (try
  439. (let [tx-data' (if (contains? #{:insert-blocks} (:outliner-op tx-meta))
  440. (map (fn [m]
  441. (if (and (map? m) (nil? (:block/order m)))
  442. (assoc m :block/order (db-order/gen-key nil))
  443. m)) tx-data)
  444. tx-data)
  445. _ (when context (worker-state/set-context! context))
  446. tx-meta' (cond-> tx-meta
  447. (and (not (:whiteboard/transact? tx-meta))
  448. (not (:rtc-download-graph? tx-meta))) ; delay writes to the disk
  449. (assoc :skip-store? true)
  450. true
  451. (dissoc :insert-blocks?))]
  452. (when-not (and (:create-today-journal? tx-meta)
  453. (:today-journal-name tx-meta)
  454. (seq tx-data')
  455. (ldb/get-page @conn (:today-journal-name tx-meta))) ; today journal created already
  456. ;; (prn :debug :transact :tx-data tx-data' :tx-meta tx-meta')
  457. (worker-util/profile "Worker db transact"
  458. (ldb/transact! conn tx-data' tx-meta')))
  459. nil)
  460. (catch :default e
  461. (prn :debug :error)
  462. (js/console.error e)
  463. (prn :debug :tx-data @conn tx-data)))))
  464. (def-thread-api :thread-api/get-initial-data
  465. [repo]
  466. (when-let [conn (worker-state/get-datascript-conn repo)]
  467. (common-initial-data/get-initial-data @conn)))
  468. (def-thread-api :thread-api/reset-db
  469. [repo db-transit]
  470. (reset-db! repo db-transit)
  471. nil)
  472. (def-thread-api :thread-api/unsafe-unlink-db
  473. [repo]
  474. (p/let [pool (<get-opfs-pool repo)
  475. _ (close-db! repo)
  476. _result (remove-vfs! pool)]
  477. nil))
  478. (def-thread-api :thread-api/release-access-handles
  479. [repo]
  480. (when-let [^js pool (worker-state/get-opfs-pool repo)]
  481. (.releaseAccessHandles pool)
  482. nil))
  483. (def-thread-api :thread-api/db-exists
  484. [repo]
  485. (<db-exists? repo))
  486. (def-thread-api :thread-api/export-db
  487. [repo]
  488. (when-let [^js db (worker-state/get-sqlite-conn repo :db)]
  489. (.exec db "PRAGMA wal_checkpoint(2)"))
  490. (p/let [data (<export-db-file repo)]
  491. (Comlink/transfer data #js [(.-buffer data)])))
  492. (def-thread-api :thread-api/import-db
  493. [repo data]
  494. (when-not (string/blank? repo)
  495. (p/let [pool (<get-opfs-pool repo)]
  496. (<import-db pool data)
  497. nil)))
  498. (def-thread-api :thread-api/search-blocks
  499. [repo q option]
  500. (search-blocks repo q option))
  501. (def-thread-api :thread-api/search-upsert-blocks
  502. [repo blocks]
  503. (p/let [db (get-search-db repo)]
  504. (search/upsert-blocks! db (bean/->js blocks))
  505. nil))
  506. (def-thread-api :thread-api/search-delete-blocks
  507. [repo ids]
  508. (p/let [db (get-search-db repo)]
  509. (search/delete-blocks! db ids)
  510. nil))
  511. (def-thread-api :thread-api/search-truncate-tables
  512. [repo]
  513. (p/let [db (get-search-db repo)]
  514. (search/truncate-table! db)
  515. nil))
  516. (def-thread-api :thread-api/search-build-blocks-indice
  517. [repo]
  518. (when-let [conn (worker-state/get-datascript-conn repo)]
  519. (search/build-blocks-indice repo @conn)))
  520. (def-thread-api :thread-api/search-build-pages-indice
  521. [_repo]
  522. nil)
  523. (def-thread-api :thread-api/apply-outliner-ops
  524. [repo ops opts]
  525. (when-let [conn (worker-state/get-datascript-conn repo)]
  526. (try
  527. (worker-util/profile
  528. "apply outliner ops"
  529. (outliner-op/apply-ops! repo conn ops (worker-state/get-date-formatter repo) opts))
  530. (catch :default e
  531. (let [data (ex-data e)
  532. {:keys [type payload]} (when (map? data) data)]
  533. (case type
  534. :notification
  535. (shared-service/broadcast-to-clients! :notification [(:message payload) (:type payload)])
  536. (throw e)))))))
  537. (def-thread-api :thread-api/file-writes-finished?
  538. [repo]
  539. (let [conn (worker-state/get-datascript-conn repo)
  540. writes @file/*writes]
  541. ;; Clean pages that have been deleted
  542. (when conn
  543. (swap! file/*writes (fn [writes]
  544. (->> writes
  545. (remove (fn [[_ pid]] (d/entity @conn pid)))
  546. (into {})))))
  547. (if (empty? writes)
  548. true
  549. (do
  550. (prn "Unfinished file writes:" @file/*writes)
  551. false))))
  552. (def-thread-api :thread-api/page-file-saved
  553. [request-id _page-id]
  554. (file/dissoc-request! request-id)
  555. nil)
  556. (def-thread-api :thread-api/sync-app-state
  557. [new-state]
  558. (worker-state/set-new-state! new-state)
  559. nil)
  560. (def-thread-api :thread-api/export-get-debug-datoms
  561. [repo]
  562. (when-let [conn (worker-state/get-datascript-conn repo)]
  563. (worker-export/get-debug-datoms conn)))
  564. (def-thread-api :thread-api/export-get-all-pages
  565. [repo]
  566. (when-let [conn (worker-state/get-datascript-conn repo)]
  567. (worker-export/get-all-pages repo @conn)))
  568. (def-thread-api :thread-api/export-get-all-page->content
  569. [repo options]
  570. (when-let [conn (worker-state/get-datascript-conn repo)]
  571. (worker-export/get-all-page->content repo @conn options)))
  572. (def-thread-api :thread-api/validate-db
  573. [repo]
  574. (when-let [conn (worker-state/get-datascript-conn repo)]
  575. (worker-db-validate/validate-db conn)))
  576. (def-thread-api :thread-api/export-edn
  577. [repo options]
  578. (let [conn (worker-state/get-datascript-conn repo)]
  579. (try
  580. (sqlite-export/build-export @conn options)
  581. (catch :default e
  582. (js/console.error "export-edn error: " e)
  583. (js/console.error "Stack:\n" (.-stack e))
  584. (worker-util/post-message :notification
  585. ["An unexpected error occurred during export. See the javascript console for details."
  586. :error])
  587. :export-edn-error))))
  588. (def-thread-api :thread-api/get-view-data
  589. [repo view-id option]
  590. (let [db @(worker-state/get-datascript-conn repo)]
  591. (db-view/get-view-data db view-id option)))
  592. (def-thread-api :thread-api/get-property-values
  593. [repo {:keys [property-ident] :as option}]
  594. (let [conn (worker-state/get-datascript-conn repo)]
  595. (db-view/get-property-values @conn property-ident option)))
  596. (def-thread-api :thread-api/build-graph
  597. [repo option]
  598. (let [conn (worker-state/get-datascript-conn repo)]
  599. (graph-view/build-graph @conn option)))
  600. (def ^:private *get-all-page-titles-cache (volatile! (cache/lru-cache-factory {})))
  601. (defn- get-all-page-titles
  602. [db]
  603. (let [pages (ldb/get-all-pages db)]
  604. (sort (map :block/title pages))))
  605. (def ^:private get-all-page-titles-with-cache
  606. (common.cache/cache-fn
  607. *get-all-page-titles-cache
  608. (fn [repo]
  609. (let [db @(worker-state/get-datascript-conn repo)]
  610. [[repo (:max-tx db)] ;cache-key
  611. [db] ;f-args
  612. ]))
  613. get-all-page-titles))
  614. (def-thread-api :thread-api/get-all-page-titles
  615. [repo]
  616. (get-all-page-titles-with-cache repo))
  617. (def-thread-api :thread-api/fix-broken-graph
  618. [graph]
  619. (fix-broken-graph graph))
  620. (def-thread-api :thread-api/reset-file
  621. [repo file-path content opts]
  622. ;; (prn :debug :reset-file :file-path file-path :opts opts)
  623. (when-let [conn (worker-state/get-datascript-conn repo)]
  624. (file-reset/reset-file! repo conn file-path content opts)))
  625. (def-thread-api :thread-api/gc-graph
  626. [repo]
  627. (let [{:keys [db client-ops]} (get @*sqlite-conns repo)
  628. conn (get @*datascript-conns repo)]
  629. (when (and db conn)
  630. (gc-sqlite-dbs! db client-ops conn {:full-gc? true})
  631. nil)))
  632. (def-thread-api :thread-api/set-infer-worker-proxy
  633. [infer-worker-proxy]
  634. (reset! worker-state/*infer-worker infer-worker-proxy))
  635. (def-thread-api :thread-api/vec-search-embedding-model-info
  636. [repo]
  637. (embedding/task--embedding-model-info repo))
  638. (def-thread-api :thread-api/vec-search-init-embedding-model
  639. [repo]
  640. (js/Promise. (embedding/task--init-embedding-model repo)))
  641. (def-thread-api :thread-api/vec-search-load-model
  642. [repo model-name]
  643. (js/Promise. (embedding/task--load-model repo model-name)))
  644. (def-thread-api :thread-api/vec-search-embedding-stale-blocks
  645. [repo]
  646. (embedding/embedding-stale-blocks! repo))
  647. (def-thread-api :thread-api/vec-search-re-embedding-graph-data
  648. [repo]
  649. (embedding/re-embedding-graph-data! repo))
  650. (def-thread-api :thread-api/vec-search-search
  651. [repo query-string nums-neighbors]
  652. (embedding/task--search repo query-string nums-neighbors))
  653. (def-thread-api :thread-api/vec-search-cancel-indexing
  654. [repo]
  655. (embedding/cancel-indexing repo))
  656. (def-thread-api :thread-api/vec-search-update-index-info
  657. [repo]
  658. (js/Promise. (embedding/task--update-index-info! repo)))
  659. (comment
  660. (def-thread-api :general/dangerousRemoveAllDbs
  661. []
  662. (p/let [r (<list-all-dbs)
  663. dbs (ldb/read-transit-str r)]
  664. (p/all (map #(.unsafeUnlinkDB this (:name %)) dbs)))))
  665. (defn- rename-page!
  666. [repo conn page-uuid new-name]
  667. (let [config (worker-state/get-config repo)
  668. f (if (sqlite-util/db-based-graph? repo)
  669. (throw (ex-info "Rename page is a file graph only operation" {}))
  670. file-worker-page-rename/rename!)]
  671. (f repo conn config page-uuid new-name)))
  672. (defn- delete-page!
  673. [repo conn page-uuid]
  674. (let [error-handler (fn [{:keys [msg]}]
  675. (worker-util/post-message :notification
  676. [[:div [:p msg]] :error]))]
  677. (worker-page/delete! repo conn page-uuid {:error-handler error-handler})))
  678. (defn- create-page!
  679. [repo conn title options]
  680. (let [config (worker-state/get-config repo)]
  681. (worker-page/create! repo conn config title options)))
  682. (defn- outliner-register-op-handlers!
  683. []
  684. (outliner-op/register-op-handlers!
  685. {:create-page (fn [repo conn [title options]]
  686. (create-page! repo conn title options))
  687. :rename-page (fn [repo conn [page-uuid new-name]]
  688. (rename-page! repo conn page-uuid new-name))
  689. :delete-page (fn [repo conn [page-uuid]]
  690. (delete-page! repo conn page-uuid))}))
  691. (defn- <ratelimit-file-writes!
  692. []
  693. (file/<ratelimit-file-writes!
  694. (fn [col]
  695. (when (seq col)
  696. (let [repo (ffirst col)
  697. conn (worker-state/get-datascript-conn repo)]
  698. (if conn
  699. (when-not (ldb/db-based-graph? @conn)
  700. (file/write-files! conn col (worker-state/get-context)))
  701. (js/console.error (str "DB is not found for " repo))))))))
  702. (defn- on-become-master
  703. [repo start-opts]
  704. (js/Promise.
  705. (m/sp
  706. (c.m/<? (init-sqlite-module!))
  707. (when-not (:import-type start-opts)
  708. (c.m/<? (start-db! repo start-opts))
  709. (assert (some? (worker-state/get-datascript-conn repo))))
  710. (m/? (rtc.core/new-task--rtc-start true)))))
  711. (def broadcast-data-types
  712. (set (map
  713. common-util/keyword->string
  714. [:sync-db-changes
  715. :notification
  716. :log
  717. :add-repo
  718. :rtc-log
  719. :rtc-sync-state])))
  720. (defn- <init-service!
  721. [graph start-opts]
  722. (let [[prev-graph service] @*service]
  723. (some-> prev-graph close-db!)
  724. (when graph
  725. (if (= graph prev-graph)
  726. service
  727. (p/let [service (shared-service/<create-service graph
  728. (bean/->js fns)
  729. #(on-become-master graph start-opts)
  730. broadcast-data-types
  731. {:import? (:import-type? start-opts)})]
  732. (assert (p/promise? (get-in service [:status :ready])))
  733. (reset! *service [graph service])
  734. service)))))
  735. (defn init
  736. "web worker entry"
  737. []
  738. (let [proxy-object (->>
  739. fns
  740. (map
  741. (fn [[k f]]
  742. [k
  743. (fn [& args]
  744. (let [[_graph service] @*service
  745. method-k (keyword (first args))]
  746. (cond
  747. (= :thread-api/create-or-open-db method-k)
  748. ;; because shared-service operates at the graph level,
  749. ;; creating a new database or switching to another one requires re-initializing the service.
  750. (let [[graph opts] (ldb/read-transit-str (last args))]
  751. (p/let [service (<init-service! graph opts)]
  752. (get-in service [:status :ready])
  753. ;; wait for service ready
  754. (js-invoke (:proxy service) k args)))
  755. (or (contains? #{:thread-api/sync-app-state} method-k)
  756. (nil? service))
  757. ;; only proceed down this branch before shared-service is initialized
  758. (apply f args)
  759. :else
  760. ;; ensure service is ready
  761. (p/let [_ready-value (get-in service [:status :ready])]
  762. (js-invoke (:proxy service) k args)))))]))
  763. (into {})
  764. bean/->js)]
  765. (glogi-console/install!)
  766. (check-worker-scope!)
  767. (outliner-register-op-handlers!)
  768. (<ratelimit-file-writes!)
  769. (js/setInterval #(.postMessage js/self "keepAliveResponse") (* 1000 25))
  770. (Comlink/expose proxy-object)
  771. (let [^js wrapped-main-thread* (Comlink/wrap js/self)
  772. wrapped-main-thread (fn [qkw direct-pass? & args]
  773. (p/let [result (.remoteInvoke wrapped-main-thread*
  774. (str (namespace qkw) "/" (name qkw))
  775. direct-pass?
  776. (if direct-pass?
  777. (into-array args)
  778. (ldb/write-transit-str args)))]
  779. (if direct-pass?
  780. result
  781. (ldb/read-transit-str result))))]
  782. (reset! worker-state/*main-thread wrapped-main-thread))))
  783. (comment
  784. (defn <remove-all-files!
  785. "!! Dangerous: use it only for development."
  786. []
  787. (p/let [all-files (<list-all-files)
  788. files (filter #(= (.-kind %) "file") all-files)
  789. dirs (filter #(= (.-kind %) "directory") all-files)
  790. _ (p/all (map (fn [file] (.remove file)) files))]
  791. (p/all (map (fn [dir] (.remove dir)) dirs)))))