Procházet zdrojové kódy

Merge branch 'feat/db' into feat/capacitor-new

charlie před 8 měsíci
rodič
revize
5cbd2a4ae5

+ 3 - 2
deps/common/nbb.edn

@@ -1,4 +1,5 @@
 {:paths ["src" "resources"]
 {:paths ["src" "resources"]
  :deps
  :deps
- {io.github.nextjournal/nbb-test-runner
-  {:git/sha "60ed57aa04bca8d604f5ba6b28848bd887109347"}}}
+ ;; TODO: Remove fork when https://github.com/nextjournal/nbb-test-runner/pull/2 is merged
+ {io.github.colin-p-hill/nbb-test-runner
+  {:git/sha "def2cbdb5b3a0e1612b28bf64f5d869c27c733d3"}}}

+ 99 - 96
deps/common/resources/templates/config.edn

@@ -1,5 +1,7 @@
 {:meta/version 1
 {:meta/version 1
 
 
+ ;; == FILE GRAPH CONFIG ==
+ ;;
  ;; Set the preferred format.
  ;; Set the preferred format.
  ;; This is _only_ for file graphs.
  ;; This is _only_ for file graphs.
  ;; Available options:
  ;; Available options:
@@ -42,6 +44,102 @@
  ;; Default value: "yyyy_MM_dd"
  ;; Default value: "yyyy_MM_dd"
  ;; :journal/file-name-format "yyyy_MM_dd"
  ;; :journal/file-name-format "yyyy_MM_dd"
 
 
+ ;; Set the default location for storing notes.
+ ;; This is _only_ for file graphs.
+ ;; Default value: "pages"
+ ;; :pages-directory "pages"
+
+ ;; Set the default location for storing journals.
+ ;; This is _only_ for file graphs.
+ ;; Default value: "journals"
+ ;; :journals-directory "journals"
+
+ ;; Set the default location for storing whiteboards.
+ ;; This is _only_ for file graphs.
+ ;; Default value: "whiteboards"
+ ;; :whiteboards-directory "whiteboards"
+
+ ;; Enabling this option converts
+ ;; [[Grant Ideas]] to [[file:./grant_ideas.org][Grant Ideas]] for org-mode.
+ ;; For more information, visit https://github.com/logseq/logseq/issues/672
+ ;; This is _only_ for file graphs.
+ ;; :org-mode/insert-file-link? false
+
+;; Favorites to list on the left sidebar
+ ;; This is _only_ for file graphs.
+ :favorites []
+
+ ;; Set flashcards interval.
+ ;; This is _only_ for file graphs.
+ ;; Expected value:
+ ;; - Float between 0 and 1
+ ;; higher values result in faster changes to the next review interval.
+ ;; Default value: 0.5
+ ;; :srs/learning-fraction 0.5
+
+ ;; Set the initial interval after the first successful review of a card.
+ ;; This is _only_ for file graphs.
+ ;; Default value: 4
+ ;; :srs/initial-interval 4
+
+ ;; Hide specific block properties.
+ ;; This is _only_ for file graphs.
+ ;; Example usage:
+ ;; :block-hidden-properties #{:public :icon}
+
+ ;; Create a page for all properties.
+ ;; This is _only_ for file graphs.
+ ;; Default value: true
+ :property-pages/enabled? true
+
+ ;; Properties to exclude from having property pages
+ ;; This is _only_ for file graphs.
+ ;; Example usage:
+ ;; :property-pages/excludelist #{:duration :author}
+
+ ;; By default, property value separated by commas will not be treated as
+ ;; page references. You can add properties to enable it.
+ ;; This is _only_ for file graphs.
+ ;; Example usage:
+ ;; :property/separated-by-commas #{:alias :tags}
+
+ ;; Properties that are ignored when parsing property values for references
+ ;; This is _only_ for file graphs.
+ ;; Example usage:
+ ;; :ignored-page-references-keywords #{:author :website}
+
+ ;; logbook configuration.
+ ;; This is _only_ for file graphs.
+ ;; :logbook/settings
+ ;; {:with-second-support? false ;limit logbook to minutes, seconds will be eliminated
+ ;;  :enabled-in-all-blocks true ;display logbook in all blocks after timetracking
+ ;;  :enabled-in-timestamped-blocks false ;don't display logbook at all
+ ;; }
+
+ ;; File sync options
+ ;; Ignore these files when syncing, regexp is supported.
+ ;; This is _only_ for file graphs.
+ ;; :file-sync/ignore-files []
+
+ ;; Configure the escaping method for special characters in page titles.
+ ;; This is _only_ for file graphs.
+ ;; Warning:
+ ;;   This is a dangerous operation. To modify the setting,
+ ;;   you'll need to manually rename all affected files and
+ ;;   re-index them on all clients after synchronization.
+ ;;   Incorrect handling may result in messy page titles.
+ ;; Available options:
+ ;;   - :triple-lowbar (default)
+ ;;      ;use triple underscore `___` for slash `/` in page title
+ ;;      ;use Percent-encoding for other invalid characters
+ :file/name-format :triple-lowbar
+ ;; == END OF FILE GRAPH CONFIG ==
+
+ ;; Hide empty block properties
+ ;; This is _only_ for DB graphs.
+ ;; Default value: false
+ ;; :ui/hide-empty-properties? false
+
  ;; Enable tooltip preview on hover.
  ;; Enable tooltip preview on hover.
  ;; Default value: true
  ;; Default value: true
  :ui/enable-tooltip? true
  :ui/enable-tooltip? true
@@ -58,11 +156,6 @@
  ;; Default value: true
  ;; Default value: true
  :ui/auto-expand-block-refs? true
  :ui/auto-expand-block-refs? true
 
 
- ;; Hide empty block properties
- ;; This is _only_ for DB graphs.
- ;; Default value: false
- ;; :ui/hide-empty-properties? false
-
  ;; Disable accent marks when searching.
  ;; Disable accent marks when searching.
  ;; After changing this setting, rebuild the search index by pressing (^C ^S).
  ;; After changing this setting, rebuild the search index by pressing (^C ^S).
  ;; Default value: true
  ;; Default value: true
@@ -137,27 +230,6 @@
  ;; 3. Set "home" as the home page and display multiple pages in the right sidebar:
  ;; 3. Set "home" as the home page and display multiple pages in the right sidebar:
  ;; :default-home {:page "home", :sidebar ["Page A" "Page B"]}
  ;; :default-home {:page "home", :sidebar ["Page A" "Page B"]}
 
 
- ;; Set the default location for storing notes.
- ;; This is _only_ for file graphs.
- ;; Default value: "pages"
- ;; :pages-directory "pages"
-
- ;; Set the default location for storing journals.
- ;; This is _only_ for file graphs.
- ;; Default value: "journals"
- ;; :journals-directory "journals"
-
- ;; Set the default location for storing whiteboards.
- ;; This is _only_ for file graphs.
- ;; Default value: "whiteboards"
- ;; :whiteboards-directory "whiteboards"
-
- ;; Enabling this option converts
- ;; [[Grant Ideas]] to [[file:./grant_ideas.org][Grant Ideas]] for org-mode.
- ;; For more information, visit https://github.com/logseq/logseq/issues/672
- ;; This is _only_ for file graphs.
- ;; :org-mode/insert-file-link? false
-
  ;; Configure custom shortcuts.
  ;; Configure custom shortcuts.
  ;; Syntax:
  ;; Syntax:
  ;; 1. + indicates simultaneous key presses, e.g., `Ctrl+Shift+a`.
  ;; 1. + indicates simultaneous key presses, e.g., `Ctrl+Shift+a`.
@@ -274,58 +346,6 @@
  ;;  :charge-strength -600   ; Default value: -600
  ;;  :charge-strength -600   ; Default value: -600
  ;;  :charge-range    600}   ; Default value: 600
  ;;  :charge-range    600}   ; Default value: 600
 
 
-
- ;; Favorites to list on the left sidebar
- ;; This is _only_ for file graphs.
- :favorites []
-
- ;; Set flashcards interval.
- ;; This is _only_ for file graphs.
- ;; Expected value:
- ;; - Float between 0 and 1
- ;; higher values result in faster changes to the next review interval.
- ;; Default value: 0.5
- ;; :srs/learning-fraction 0.5
-
- ;; Set the initial interval after the first successful review of a card.
- ;; This is _only_ for file graphs.
- ;; Default value: 4
- ;; :srs/initial-interval 4
-
- ;; Hide specific block properties.
- ;; This is _only_ for file graphs.
- ;; Example usage:
- ;; :block-hidden-properties #{:public :icon}
-
- ;; Create a page for all properties.
- ;; This is _only_ for file graphs.
- ;; Default value: true
- :property-pages/enabled? true
-
- ;; Properties to exclude from having property pages
- ;; This is _only_ for file graphs.
- ;; Example usage:
- ;; :property-pages/excludelist #{:duration :author}
-
- ;; By default, property value separated by commas will not be treated as
- ;; page references. You can add properties to enable it.
- ;; This is _only_ for file graphs.
- ;; Example usage:
- ;; :property/separated-by-commas #{:alias :tags}
-
- ;; Properties that are ignored when parsing property values for references
- ;; This is _only_ for file graphs.
- ;; Example usage:
- ;; :ignored-page-references-keywords #{:author :website}
-
- ;; logbook configuration.
- ;; This is _only_ for file graphs.
- ;; :logbook/settings
- ;; {:with-second-support? false ;limit logbook to minutes, seconds will be eliminated
- ;;  :enabled-in-all-blocks true ;display logbook in all blocks after timetracking
- ;;  :enabled-in-timestamped-blocks false ;don't display logbook at all
- ;; }
-
  ;; Mobile photo upload configuration.
  ;; Mobile photo upload configuration.
  ;; :mobile/photo
  ;; :mobile/photo
  ;; {:allow-editing? true
  ;; {:allow-editing? true
@@ -375,11 +395,6 @@
  ;;  :redirect-page? false          ;; Default value: false
  ;;  :redirect-page? false          ;; Default value: false
  ;;  :default-page "quick capture"} ;; Default page: "quick capture"
  ;;  :default-page "quick capture"} ;; Default page: "quick capture"
 
 
- ;; File sync options
- ;; Ignore these files when syncing, regexp is supported.
- ;; This is _only_ for file graphs.
- ;; :file-sync/ignore-files []
-
  ;; Configure the Enter key behavior for
  ;; Configure the Enter key behavior for
  ;; context-aware editing with DWIM (Do What I Mean).
  ;; context-aware editing with DWIM (Do What I Mean).
  ;; context-aware Enter key behavior implies that pressing Enter will
  ;; context-aware Enter key behavior implies that pressing Enter will
@@ -393,16 +408,4 @@
  ;;  :page-ref?        true        ;; Default value: true
  ;;  :page-ref?        true        ;; Default value: true
  ;;  :properties?      true        ;; Default value: true
  ;;  :properties?      true        ;; Default value: true
  ;;  :list?            false}      ;; Default value: false
  ;;  :list?            false}      ;; Default value: false
-
- ;; Configure the escaping method for special characters in page titles.
- ;; This is _only_ for file graphs.
- ;; Warning:
- ;;   This is a dangerous operation. To modify the setting,
- ;;   you'll need to manually rename all affected files and
- ;;   re-index them on all clients after synchronization.
- ;;   Incorrect handling may result in messy page titles.
- ;; Available options:
- ;;   - :triple-lowbar (default)
- ;;      ;use triple underscore `___` for slash `/` in page title
- ;;      ;use Percent-encoding for other invalid characters
- :file/name-format :triple-lowbar}
+ }

+ 41 - 1
deps/common/src/logseq/common/config.cljs

@@ -1,5 +1,5 @@
 (ns logseq.common.config
 (ns logseq.common.config
-  "Common config and constants that are shared between deps and app"
+  "Common config constants and fns that are shared between deps and app"
   (:require [clojure.string :as string]
   (:require [clojure.string :as string]
             [goog.object :as gobj]))
             [goog.object :as gobj]))
 
 
@@ -115,3 +115,43 @@
       "*"
       "*"
 
 
       "-")))
       "-")))
+
+(defn create-config-for-db-graph
+  "Given a new config.edn file string, creates a config.edn for use with only DB graphs"
+  [config]
+  (string/replace config #"(?m)[\s]*;; == FILE GRAPH CONFIG ==(?:.|\n)*?;; == END OF FILE GRAPH CONFIG ==\n?" ""))
+
+(def file-only-config
+  "File only config keys that are deprecated in DB graphs along with
+  descriptions for their deprecation."
+  (merge
+   (zipmap
+    [:file/name-format
+     :file-sync/ignore-files
+     :hidden
+     :ignored-page-references-keywords
+     :journal/file-name-format
+     :journal/page-title-format
+     :journals-directory
+     :logbook/settings
+     :org-mode/insert-file-link?
+     :pages-directory
+     :preferred-workflow
+     :property/separated-by-commas
+     :property-pages/excludelist
+     :srs/learning-fraction
+     :srs/initial-interval
+     :whiteboards-directory]
+    (repeat "is not used in DB graphs"))
+   {:preferred-format
+    "is not used in DB graphs as there is only markdown mode."
+    :property-pages/enabled?
+    "is not used in DB graphs as all properties have pages"
+    :block-hidden-properties
+    "is not used in DB graphs as hiding a property is done in its configuration"
+    :feature/enable-block-timestamps?
+    "is not used in DB graphs as it is always enabled"
+    :favorites
+    "is not stored in config for DB graphs"
+    :default-templates
+    "is replaced by #Template and the `Apply template to tags` property"}))

+ 45 - 0
deps/common/test/logseq/common/config_test.cljc

@@ -0,0 +1,45 @@
+(ns logseq.common.config-test
+  (:require [cljs.test :refer [deftest is]]
+            [clojure.string :as string]
+            [logseq.common.config :as common-config]
+            #?(:org.babashka/nbb [nbb.classpath :as cp])
+            ["fs" :as fs]
+            ["path" :as node-path]))
+
+(deftest remove-hidden-files
+  (let [files ["pages/foo.md" "pages/bar.md"
+               "script/README.md" "script/config.edn"
+               "dev/README.md" "dev/config.edn"]]
+    (is (= ["pages/foo.md" "pages/bar.md"]
+           #_:clj-kondo/ignore ;; buggy unresolved var
+           (common-config/remove-hidden-files
+            files
+            {:hidden ["script" "/dev"]}
+            identity))
+        "Removes hidden relative files")
+
+    (is (= ["/pages/foo.md" "/pages/bar.md"]
+           (common-config/remove-hidden-files
+            (map #(str "/" %) files)
+            {:hidden ["script" "/dev"]}
+            identity))
+        "Removes hidden files if they start with '/'")))
+
+(defn find-on-classpath [classpath rel-path]
+  (some (fn [dir]
+          (let [f (node-path/join dir rel-path)]
+            (when (fs/existsSync f) f)))
+        (string/split classpath #":")))
+
+#?(:org.babashka/nbb
+   (deftest create-config-for-db-graph
+     (let [original-config (some-> (find-on-classpath (cp/get-classpath) "templates/config.edn") fs/readFileSync str)
+           _ (assert original-config "config.edn must not be blank")
+           migrated-config (common-config/create-config-for-db-graph original-config)
+           forbidden-kws-regex (re-pattern (str (string/join "|" (keys common-config/file-only-config))))]
+      ;;  (println migrated-config)
+       (is (not (string/includes? migrated-config "== FILE ONLY CONFIG"))
+           "No longer includes file config header")
+       (assert (re-find forbidden-kws-regex original-config) "File config keys present in original config")
+       (is (not (re-find forbidden-kws-regex migrated-config))
+           "File config keys no longer present in migrated config"))))

+ 0 - 21
deps/common/test/logseq/common/config_test.cljs

@@ -1,21 +0,0 @@
-(ns logseq.common.config-test
-  (:require [logseq.common.config :as common-config]
-            [cljs.test :refer [deftest is]]))
-
-(deftest remove-hidden-files
-  (let [files ["pages/foo.md" "pages/bar.md"
-               "script/README.md" "script/config.edn"
-               "dev/README.md" "dev/config.edn"]]
-    (is (= ["pages/foo.md" "pages/bar.md"]
-           (common-config/remove-hidden-files
-            files
-            {:hidden ["script" "/dev"]}
-            identity))
-        "Removes hidden relative files")
-
-    (is (= ["/pages/foo.md" "/pages/bar.md"]
-           (common-config/remove-hidden-files
-            (map #(str "/" %) files)
-            {:hidden ["script" "/dev"]}
-            identity))
-        "Removes hidden files if they start with '/'")))

+ 1 - 0
deps/db/deps.edn

@@ -12,6 +12,7 @@
   logseq/common                {:local/root "../common"}
   logseq/common                {:local/root "../common"}
   logseq/clj-fractional-indexing        {:git/url "https://github.com/logseq/clj-fractional-indexing"
   logseq/clj-fractional-indexing        {:git/url "https://github.com/logseq/clj-fractional-indexing"
                                          :sha     "7182b7878410f78536dc2b6df35ed32ef9cd6b61"}
                                          :sha     "7182b7878410f78536dc2b6df35ed32ef9cd6b61"}
+  borkdude/rewrite-edn {:mvn/version "0.4.9"}
   metosin/malli {:mvn/version "0.16.1"}
   metosin/malli {:mvn/version "0.16.1"}
   medley/medley {:mvn/version "1.4.0"}}
   medley/medley {:mvn/version "1.4.0"}}
 
 

+ 2 - 0
deps/db/nbb.edn

@@ -4,6 +4,8 @@
   {:local/root "../common"}
   {:local/root "../common"}
   medley/medley {:mvn/version "1.4.0"}
   medley/medley {:mvn/version "1.4.0"}
   metosin/malli {:mvn/version "0.16.1"}
   metosin/malli {:mvn/version "0.16.1"}
+  ;; Used by db scripts with outliner.cli
+  borkdude/rewrite-edn {:mvn/version "0.4.9"}
   logseq/clj-fractional-indexing        {:git/url "https://github.com/logseq/clj-fractional-indexing"
   logseq/clj-fractional-indexing        {:git/url "https://github.com/logseq/clj-fractional-indexing"
                                          :sha     "7182b7878410f78536dc2b6df35ed32ef9cd6b61"}
                                          :sha     "7182b7878410f78536dc2b6df35ed32ef9cd6b61"}
   io.github.nextjournal/nbb-test-runner
   io.github.nextjournal/nbb-test-runner

+ 2 - 6
deps/db/src/logseq/db/frontend/malli_schema.cljs

@@ -3,9 +3,9 @@
   (:require [clojure.set :as set]
   (:require [clojure.set :as set]
             [clojure.string :as string]
             [clojure.string :as string]
             [datascript.core :as d]
             [datascript.core :as d]
+            [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.common.order :as db-order]
             [logseq.db.common.order :as db-order]
             [logseq.db.frontend.class :as db-class]
             [logseq.db.frontend.class :as db-class]
-            [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.frontend.entity-util :as entity-util]
             [logseq.db.frontend.entity-util :as entity-util]
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.property.type :as db-property-type]
             [logseq.db.frontend.property.type :as db-property-type]
@@ -482,14 +482,10 @@
                        :file-block
                        :file-block
                        (:logseq.property.history/block d)
                        (:logseq.property.history/block d)
                        :property-history-block
                        :property-history-block
-
                        (:block/closed-value-property d)
                        (:block/closed-value-property d)
                        :closed-value-block
                        :closed-value-block
-
-                       (and (:logseq.property/created-from-property d)
-                            (:logseq.property/value d))
+                       (and (:logseq.property/created-from-property d) (:logseq.property/value d))
                        :property-value-block
                        :property-value-block
-
                        (:block/uuid d)
                        (:block/uuid d)
                        :block
                        :block
                        (= (:db/ident d) :logseq.property/empty-placeholder)
                        (= (:db/ident d) :logseq.property/empty-placeholder)

+ 44 - 31
deps/db/src/logseq/db/sqlite/export.cljs

@@ -803,21 +803,29 @@
              set)]
              set)]
     (set/difference ref-uuids known-uuids)))
     (set/difference ref-uuids known-uuids)))
 
 
+(defn- remove-namespaced-keys
+  "Removes keys from this ns for maps passed sqlite.build fns as they don't need to validate or use them"
+  [m]
+  (->> m
+       (remove (fn [[k _v]] (= "logseq.db.sqlite.export" (namespace k))))
+       (into {})))
+
 (defn- ensure-export-is-valid
 (defn- ensure-export-is-valid
   "Checks that export map is usable by sqlite.build including checking that
   "Checks that export map is usable by sqlite.build including checking that
    all referenced properties and classes are defined. Checks related to properties and
    all referenced properties and classes are defined. Checks related to properties and
    classes are disabled when :exclude-namespaces is set because those checks can't be done"
    classes are disabled when :exclude-namespaces is set because those checks can't be done"
-  [export-map {:keys [graph-options]}]
-  (when-not (seq (:exclude-namespaces graph-options)) (sqlite-build/validate-options export-map))
-  (let [undefined-uuids (find-undefined-uuids export-map)
-        undefined (cond-> {}
-                    (empty? (:exclude-namespaces graph-options))
-                    (merge (find-undefined-classes-and-properties export-map))
-                    (seq undefined-uuids)
-                    (assoc :uuids undefined-uuids))]
-    (when (seq undefined)
-      (throw (ex-info (str "The following classes, uuids and properties are not defined: " (pr-str undefined))
-                      undefined)))))
+  [export-map* {:keys [graph-options]}]
+  (let [export-map (remove-namespaced-keys export-map*)]
+    (when-not (seq (:exclude-namespaces graph-options)) (sqlite-build/validate-options export-map))
+    (let [undefined-uuids (find-undefined-uuids export-map)
+          undefined (cond-> {}
+                      (empty? (:exclude-namespaces graph-options))
+                      (merge (find-undefined-classes-and-properties export-map))
+                      (seq undefined-uuids)
+                      (assoc :uuids undefined-uuids))]
+      (when (seq undefined)
+        (throw (ex-info (str "The following classes, uuids and properties are not defined: " (pr-str undefined))
+                        undefined))))))
 
 
 (defn build-export
 (defn build-export
   "Handles exporting db by given export-type"
   "Handles exporting db by given export-type"
@@ -838,30 +846,33 @@
           (build-graph-export db (:graph-options options)))]
           (build-graph-export db (:graph-options options)))]
     (if (get-in options [:graph-options :catch-validation-errors?])
     (if (get-in options [:graph-options :catch-validation-errors?])
       (try
       (try
-        (ensure-export-is-valid (dissoc export-map ::block ::graph-files ::kv-values ::schema-version) options)
+        (ensure-export-is-valid export-map options)
         (catch ExceptionInfo e
         (catch ExceptionInfo e
           (println "Caught error:" e)))
           (println "Caught error:" e)))
-      (ensure-export-is-valid (dissoc export-map ::block ::graph-files ::kv-values ::schema-version) options))
+      (ensure-export-is-valid export-map options))
     (assoc export-map ::export-type export-type)))
     (assoc export-map ::export-type export-type)))
 
 
 ;; Import fns
 ;; Import fns
 ;; ==========
 ;; ==========
 (defn- add-uuid-to-page-if-exists
 (defn- add-uuid-to-page-if-exists
-  [db import-to-existing-page-uuids m]
-  (if-let [ent (some->> (:build/journal m)
-                        (d/datoms db :avet :block/journal-day)
-                        first
-                        :e
-                        (d/entity db))]
+  [db import-to-existing-page-uuids {:keys [existing-pages-keep-properties?]} m]
+  (if-let [ent (if (:build/journal m)
+                 (some->> (:build/journal m)
+                          (d/datoms db :avet :block/journal-day)
+                          first
+                          :e
+                          (d/entity db))
+                 ;; TODO: For now only check page uniqueness by title. Could handle more uniqueness checks later
+                 (some->> (:block/title m) (ldb/get-case-page db)))]
     (do
     (do
       (swap! import-to-existing-page-uuids assoc (:block/uuid m) (:block/uuid ent))
       (swap! import-to-existing-page-uuids assoc (:block/uuid m) (:block/uuid ent))
-      (assoc m :block/uuid (:block/uuid ent)))
-    ;; TODO: For now only check page uniqueness by title. Could handle more uniqueness checks later
-    (if-let [ent (some->> (:block/title m) (ldb/get-case-page db))]
-      (do
-        (swap! import-to-existing-page-uuids assoc (:block/uuid m) (:block/uuid ent))
-        (assoc m :block/uuid (:block/uuid ent)))
-      m)))
+      (cond-> (assoc m :block/uuid (:block/uuid ent))
+        (and (:build/properties m) existing-pages-keep-properties?)
+        (update :build/properties (fn [props]
+                                    (->> props
+                                         (remove (fn [[k _v]] (get ent k)))
+                                         (into {}))))))
+    m))
 
 
 (defn- update-existing-properties
 (defn- update-existing-properties
   "Updates existing properties by ident. Also check imported and existing properties have
   "Updates existing properties by ident. Also check imported and existing properties have
@@ -884,7 +895,7 @@
 (defn- check-for-existing-entities
 (defn- check-for-existing-entities
   "Checks export map for existing entities and adds :block/uuid to them if they exist in graph to import.
   "Checks export map for existing entities and adds :block/uuid to them if they exist in graph to import.
    Also checks for property conflicts between existing properties and properties to be imported"
    Also checks for property conflicts between existing properties and properties to be imported"
-  [db {:keys [pages-and-blocks classes properties] ::keys [export-type] :as export-map} property-conflicts]
+  [db {:keys [pages-and-blocks classes properties] ::keys [export-type import-options] :as export-map} property-conflicts]
   (let [import-to-existing-page-uuids (atom {})
   (let [import-to-existing-page-uuids (atom {})
         export-map
         export-map
         (cond-> {:build-existing-tx? true
         (cond-> {:build-existing-tx? true
@@ -892,7 +903,7 @@
           (seq pages-and-blocks)
           (seq pages-and-blocks)
           (assoc :pages-and-blocks
           (assoc :pages-and-blocks
                  (mapv (fn [m]
                  (mapv (fn [m]
-                         (update m :page (partial add-uuid-to-page-if-exists db import-to-existing-page-uuids)))
+                         (update m :page (partial add-uuid-to-page-if-exists db import-to-existing-page-uuids import-options)))
                        pages-and-blocks))
                        pages-and-blocks))
           (seq classes)
           (seq classes)
           (assoc :classes
           (assoc :classes
@@ -915,7 +926,7 @@
                       (walk/postwalk (fn [f]
                       (walk/postwalk (fn [f]
                                        (if (and (vector? f) (= :build/page (first f)))
                                        (if (and (vector? f) (= :build/page (first f)))
                                          [:build/page
                                          [:build/page
-                                          (add-uuid-to-page-if-exists db import-to-existing-page-uuids (second f))]
+                                          (add-uuid-to-page-if-exists db import-to-existing-page-uuids import-options (second f))]
                                          f))
                                          f))
                                      export-map))
                                      export-map))
         ;; Update uuid references of all pages that had their uuids updated to reference an existing page
         ;; Update uuid references of all pages that had their uuids updated to reference an existing page
@@ -948,6 +959,8 @@
    * ::kv-values - Vec of :kv/value maps for a :graph export
    * ::kv-values - Vec of :kv/value maps for a :graph export
    * ::auto-include-namespaces - A set of parent namespaces to include from properties and classes
    * ::auto-include-namespaces - A set of parent namespaces to include from properties and classes
      for a :graph export. See :exclude-namespaces in build-graph-export for a similar option
      for a :graph export. See :exclude-namespaces in build-graph-export for a similar option
+   * ::import-options - A map of options that alters importing behavior. Has the following keys:
+     * :existing-pages-keep-properties? - Boolean which disables upsert of :build/properties on
 
 
    This fn then returns a map of txs to transact with the following keys:
    This fn then returns a map of txs to transact with the following keys:
    * :init-tx - Txs that must be transacted first, usually because they define new properties
    * :init-tx - Txs that must be transacted first, usually because they define new properties
@@ -969,7 +982,7 @@
         {:error (str "The following imported properties conflict with the current graph: "
         {:error (str "The following imported properties conflict with the current graph: "
                      (pr-str (mapv :property-id @property-conflicts)))})
                      (pr-str (mapv :property-id @property-conflicts)))})
       (if (= :graph (::export-type export-map''))
       (if (= :graph (::export-type export-map''))
-        (-> (sqlite-build/build-blocks-tx (dissoc export-map'' ::graph-files ::kv-values ::export-type ::schema-version))
+        (-> (sqlite-build/build-blocks-tx (remove-namespaced-keys export-map''))
             (assoc :misc-tx (vec (concat (::graph-files export-map'')
             (assoc :misc-tx (vec (concat (::graph-files export-map'')
                                          (::kv-values export-map'')))))
                                          (::kv-values export-map'')))))
-        (sqlite-build/build-blocks-tx export-map'')))))
+        (sqlite-build/build-blocks-tx (remove-namespaced-keys export-map''))))))

+ 39 - 28
deps/db/test/logseq/db/sqlite/export_test.cljs

@@ -826,54 +826,65 @@
            (-> (:classes imported-graph)
            (-> (:classes imported-graph)
                (medley/dissoc-in [:user.property/p1 :build/properties]))))))
                (medley/dissoc-in [:user.property/p1 :build/properties]))))))
 
 
-(deftest build-import-can-import-existing-page-with-different-uuid
+(defn- test-import-existing-page [import-options expected-page-properties]
   (let [original-data
   (let [original-data
         {:properties {:user.property/node {:logseq.property/type :node
         {:properties {:user.property/node {:logseq.property/type :node
                                            :db/cardinality :db.cardinality/many}}
                                            :db/cardinality :db.cardinality/many}}
          :pages-and-blocks
          :pages-and-blocks
          [{:page {:block/title "page1"
          [{:page {:block/title "page1"
-                  :build/properties {:user.property/node #{[:build/page {:block/title "node1"}]}}}}]}
+                  :build/properties {:user.property/node
+                                     #{[:build/page {:block/title "existing page"
+                                                     :build/properties {:logseq.property/description "first description"}}]}}}}]}
         conn (db-test/create-conn-with-blocks original-data)
         conn (db-test/create-conn-with-blocks original-data)
-        page-uuid (:block/uuid (db-test/find-page-by-title @conn "node1"))
+        page-uuid (:block/uuid (db-test/find-page-by-title @conn "existing page"))
         _ (validate-db @conn)
         _ (validate-db @conn)
         ;; This is just a temp uuid used to link to the page during import
         ;; This is just a temp uuid used to link to the page during import
         temp-uuid (random-uuid)
         temp-uuid (random-uuid)
-        existing-data
+        import-data
         {:properties {:user.property/node {:logseq.property/type :node
         {:properties {:user.property/node {:logseq.property/type :node
                                            :db/cardinality :db.cardinality/many}}
                                            :db/cardinality :db.cardinality/many}}
          :pages-and-blocks
          :pages-and-blocks
-         [{:page {:block/title "node1"
+         [{:page {:block/title "existing page"
                   :block/uuid temp-uuid
                   :block/uuid temp-uuid
-                  :build/keep-uuid? true}}
+                  :build/keep-uuid? true
+                  :build/properties {:logseq.property/description "second description"
+                                     :logseq.property/exclude-from-graph-view true}}}
           {:page {:block/title "page2"
           {:page {:block/title "page2"
-                  :build/properties {:user.property/node #{[:block/uuid temp-uuid]}}}}]}
+                  :build/properties {:user.property/node #{[:block/uuid temp-uuid]}}}}]
+         ::sqlite-export/import-options import-options}
         {:keys [init-tx block-props-tx] :as _txs}
         {:keys [init-tx block-props-tx] :as _txs}
-        (sqlite-export/build-import existing-data @conn {})
+        (sqlite-export/build-import import-data @conn {})
         ;; _ (cljs.pprint/pprint _txs)
         ;; _ (cljs.pprint/pprint _txs)
         _ (d/transact! conn init-tx)
         _ (d/transact! conn init-tx)
         _ (d/transact! conn block-props-tx)
         _ (d/transact! conn block-props-tx)
         _ (validate-db @conn)
         _ (validate-db @conn)
         expected-pages-and-blocks
         expected-pages-and-blocks
-        [{:page
-          {:block/uuid page-uuid
-           :build/keep-uuid? true,
-           :block/title "node1"},
-          :blocks []}
-         {:page
-          {:build/properties
-           {:user.property/node
-            #{[:block/uuid page-uuid]}},
-           :block/title "page1"},
-          :blocks []}
-         {:page
-          {:build/properties
-           {:user.property/node
-            #{[:block/uuid page-uuid]}},
-           :block/title "page2"},
-          :blocks []}],
+        [{:block/uuid page-uuid
+          :build/keep-uuid? true,
+          :block/title "existing page"
+          :build/properties
+          expected-page-properties}
+         {:build/properties
+          {:user.property/node
+           #{[:block/uuid page-uuid]}},
+          :block/title "page1"}
+         {:build/properties
+          {:user.property/node
+           #{[:block/uuid page-uuid]}},
+          :block/title "page2"}]
         exported-graph (sqlite-export/build-export @conn {:export-type :graph
         exported-graph (sqlite-export/build-export @conn {:export-type :graph
                                                           :graph-options {:exclude-built-in-pages? true}})]
                                                           :graph-options {:exclude-built-in-pages? true}})]
     (is (= expected-pages-and-blocks
     (is (= expected-pages-and-blocks
-           (:pages-and-blocks exported-graph))
-        "page uuid ('node1') is preserved across imports even when its assigned a temporary
-         uuid to relate it to other nodes")))
+           (map :page (:pages-and-blocks exported-graph)))
+        "page uuid of 'existing page' is preserved across imports even when its assigned a temporary
+         uuid to relate it to other nodes")))
+
+(deftest build-import-can-import-existing-page-with-different-uuid
+  (testing "By default any properties passed to an existing page are upserted"
+    (test-import-existing-page {}
+                              {:logseq.property/description "second description"
+                               :logseq.property/exclude-from-graph-view true}))
+  (testing "With ::existing-pages-keep-properties?, existing properties on existing pages are not overwritten by imported data"
+    (test-import-existing-page {:existing-pages-keep-properties? true}
+                               {:logseq.property/description "first description"
+                                :logseq.property/exclude-from-graph-view true})))

+ 6 - 1
deps/graph-parser/src/logseq/graph_parser/exporter.cljs

@@ -764,6 +764,7 @@
     {:block block'' :properties-tx properties-tx}))
     {:block block'' :properties-tx properties-tx}))
 
 
 (defn- pretty-print-dissoc
 (defn- pretty-print-dissoc
+  "Remove list of keys from a given map string while preserving whitespace"
   [s dissoc-keys]
   [s dissoc-keys]
   (-> (reduce rewrite/dissoc
   (-> (reduce rewrite/dissoc
               (rewrite/parse-string s)
               (rewrite/parse-string s)
@@ -1496,7 +1497,11 @@
                                              <save-file default-save-file}}]
                                              <save-file default-save-file}}]
   (-> (<read-file config-file)
   (-> (<read-file config-file)
       (p/then #(p/do!
       (p/then #(p/do!
-                (<save-file repo-or-conn "logseq/config.edn" %)
+                (<save-file repo-or-conn
+                            "logseq/config.edn"
+                            ;; Converts a file graph config.edn for use with DB graphs. Unlike common-config/create-config-for-db-graph,
+                            ;; manually dissoc deprecated keys for config to be valid
+                            (pretty-print-dissoc % (keys common-config/file-only-config)))
                 (let [config (edn/read-string %)]
                 (let [config (edn/read-string %)]
                   (when-let [title-format (or (:journal/page-title-format config) (:date-formatter config))]
                   (when-let [title-format (or (:journal/page-title-format config) (:date-formatter config))]
                     (ldb/transact! repo-or-conn [{:db/ident :logseq.class/Journal
                     (ldb/transact! repo-or-conn [{:db/ident :logseq.class/Journal

+ 1 - 1
deps/graph-parser/test/logseq/graph_parser/exporter_test.cljs

@@ -693,7 +693,7 @@
 
 
 (deftest-async export-config-file-sets-title-format
 (deftest-async export-config-file-sets-title-format
   (p/let [conn (db-test/create-conn)
   (p/let [conn (db-test/create-conn)
-          read-file #(p/do! (pr-str {:journal/page-title-format "yyyy-MM-dd"}))
+          read-file #(p/do! "{:journal/page-title-format \"yyyy-MM-dd\"}")
           _ (gp-exporter/export-config-file conn "logseq/config.edn" read-file {})]
           _ (gp-exporter/export-config-file conn "logseq/config.edn" read-file {})]
     (is (= "yyyy-MM-dd"
     (is (= "yyyy-MM-dd"
            (:logseq.property.journal/title-format (d/entity @conn :logseq.class/Journal)))
            (:logseq.property.journal/title-format (d/entity @conn :logseq.class/Journal)))

+ 16 - 6
deps/outliner/src/logseq/outliner/cli.cljs

@@ -1,13 +1,15 @@
 (ns ^:node-only logseq.outliner.cli
 (ns ^:node-only logseq.outliner.cli
   "Primary ns for outliner CLI fns"
   "Primary ns for outliner CLI fns"
-  (:require [clojure.string :as string]
+  (:require [borkdude.rewrite-edn :as rewrite]
+            [clojure.string :as string]
             [datascript.core :as d]
             [datascript.core :as d]
             [logseq.db.sqlite.create-graph :as sqlite-create-graph]
             [logseq.db.sqlite.create-graph :as sqlite-create-graph]
             [logseq.db.sqlite.build :as sqlite-build]
             [logseq.db.sqlite.build :as sqlite-build]
             [logseq.db.common.sqlite-cli :as sqlite-cli]
             [logseq.db.common.sqlite-cli :as sqlite-cli]
             [logseq.outliner.db-pipeline :as db-pipeline]
             [logseq.outliner.db-pipeline :as db-pipeline]
             ["fs" :as fs]
             ["fs" :as fs]
-            ["path" :as node-path]))
+            ["path" :as node-path]
+            [logseq.common.config :as common-config]))
 
 
 (defn- find-on-classpath [classpath rel-path]
 (defn- find-on-classpath [classpath rel-path]
   (some (fn [dir]
   (some (fn [dir]
@@ -15,6 +17,15 @@
             (when (fs/existsSync f) f)))
             (when (fs/existsSync f) f)))
         (string/split classpath #":")))
         (string/split classpath #":")))
 
 
+(defn- pretty-print-merge
+  "Merge map into string while preversing whitespace"
+  [s m]
+  (-> (reduce (fn [acc [k v]]
+                (rewrite/assoc acc k v))
+              (rewrite/parse-string s)
+              m)
+      str))
+
 (defn- setup-init-data
 (defn- setup-init-data
   "Setup initial data same as frontend.handler.repo/create-db"
   "Setup initial data same as frontend.handler.repo/create-db"
   [conn {:keys [additional-config classpath import-type]
   [conn {:keys [additional-config classpath import-type]
@@ -23,11 +34,10 @@
         (cond-> (or (some-> (find-on-classpath classpath "templates/config.edn") fs/readFileSync str)
         (cond-> (or (some-> (find-on-classpath classpath "templates/config.edn") fs/readFileSync str)
                     (do (println "Setting graph's config to empty since no templates/config.edn was found.")
                     (do (println "Setting graph's config to empty since no templates/config.edn was found.")
                         "{}"))
                         "{}"))
+          true
+          (common-config/create-config-for-db-graph)
           additional-config
           additional-config
-          ;; TODO: Replace with rewrite-clj when it's available
-          (string/replace-first #"(:file/name-format :triple-lowbar)"
-                                (str "$1 "
-                                     (string/replace-first (str additional-config) #"^\{(.*)\}$" "$1"))))]
+          (pretty-print-merge additional-config))]
     (d/transact! conn (sqlite-create-graph/build-db-initial-data config-content {:import-type import-type}))))
     (d/transact! conn (sqlite-create-graph/build-db-initial-data config-content {:import-type import-type}))))
 
 
 (defn init-conn
 (defn init-conn

+ 4 - 7
src/main/frontend/components/block.cljs

@@ -62,7 +62,6 @@
             [frontend.mobile.util :as mobile-util]
             [frontend.mobile.util :as mobile-util]
             [frontend.modules.outliner.tree :as tree]
             [frontend.modules.outliner.tree :as tree]
             [frontend.modules.shortcut.utils :as shortcut-utils]
             [frontend.modules.shortcut.utils :as shortcut-utils]
-            [frontend.util.ref :as ref]
             [frontend.security :as security]
             [frontend.security :as security]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.template :as template]
             [frontend.template :as template]
@@ -70,6 +69,7 @@
             [frontend.util :as util]
             [frontend.util :as util]
             [frontend.util.file-based.clock :as clock]
             [frontend.util.file-based.clock :as clock]
             [frontend.util.file-based.drawer :as drawer]
             [frontend.util.file-based.drawer :as drawer]
+            [frontend.util.ref :as ref]
             [frontend.util.text :as text-util]
             [frontend.util.text :as text-util]
             [goog.dom :as gdom]
             [goog.dom :as gdom]
             [goog.functions :refer [debounce]]
             [goog.functions :refer [debounce]]
@@ -939,13 +939,13 @@
   "Component for a page. `page` argument contains :block/name which can be (un)sanitized page name.
   "Component for a page. `page` argument contains :block/name which can be (un)sanitized page name.
    Keys for `config`:
    Keys for `config`:
    - `:preview?`: Is this component under preview mode? (If true, `page-preview-trigger` won't be registered to this `page-cp`)"
    - `:preview?`: Is this component under preview mode? (If true, `page-preview-trigger` won't be registered to this `page-cp`)"
-  [state {:keys [label children preview? disable-preview? show-non-exists-page? table-view? tag? _skip-async-load?] :as config} page]
+  [state {:keys [label children preview? disable-preview? show-non-exists-page? tag? _skip-async-load?] :as config} page]
   (when-let [entity' (rum/react (:*entity state))]
   (when-let [entity' (rum/react (:*entity state))]
     (let [entity (or (db/sub-block (:db/id entity')) entity')
     (let [entity (or (db/sub-block (:db/id entity')) entity')
           config (assoc config :block entity)]
           config (assoc config :block entity)]
       (cond
       (cond
         entity
         entity
-        (if (ldb/page? entity)
+        (if (or (ldb/page? entity) (not (:block/page entity)))
           (let [page-name (some-> (:block/title entity) util/page-name-sanity-lc)
           (let [page-name (some-> (:block/title entity) util/page-name-sanity-lc)
                 whiteboard-page? (model/whiteboard-page? entity)
                 whiteboard-page? (model/whiteboard-page? entity)
                 inner (page-inner (assoc config :whiteboard-page? whiteboard-page?) entity children label)
                 inner (page-inner (assoc config :whiteboard-page? whiteboard-page?) entity children label)
@@ -962,10 +962,7 @@
                              (gp-mldoc/inline->edn label (mldoc/get-default-config :markdown))
                              (gp-mldoc/inline->edn label (mldoc/get-default-config :markdown))
                              label)))
                              label)))
 
 
-        (and (:block/name page) (util/uuid-string? (:block/name page)))
-        (invalid-node-ref (:block/name page))
-
-        (and (:block/name page) (or show-non-exists-page? table-view?))
+        (and (:block/name page) show-non-exists-page?)
         (page-inner config (merge
         (page-inner config (merge
                             {:block/title (:block/name page)
                             {:block/title (:block/name page)
                              :block/name (:block/name page)}
                              :block/name (:block/name page)}

+ 23 - 1
src/main/frontend/components/cmdk/core.cljs

@@ -81,7 +81,9 @@
   (string/replace input #"^#+" ""))
   (string/replace input #"^#+" ""))
 
 
 (defn create-items [q]
 (defn create-items [q]
-  (when (and (not (string/blank? q)) (not config/publishing?))
+  (when (and (not (string/blank? q))
+             (not (#{"config.edn" "custom.js" "custom.css"} q))
+             (not config/publishing?))
     (let [class? (string/starts-with? q "#")]
     (let [class? (string/starts-with? q "#")]
       (->> [{:text (if class? "Create tag" "Create page")       :icon "new-page"
       (->> [{:text (if class? "Create tag" "Create page")       :icon "new-page"
              :icon-theme :gray
              :icon-theme :gray
@@ -236,6 +238,25 @@
            (hash-map :status :success :items)
            (hash-map :status :success :items)
            (swap! !results update group merge)))))
            (swap! !results update group merge)))))
 
 
+(defmethod load-results :recently-updated-pages [group state]
+  (let [!input (::input state)
+        !results (::results state)]
+    (swap! !results assoc-in [group :status] :loading)
+    (let [recent-pages (ldb/get-recent-updated-pages (db/get-db))
+          search-results (if (string/blank? @!input)
+                           recent-pages
+                           (search/fuzzy-search recent-pages @!input {:extract-fn :block/title}))]
+      (->> search-results
+           (map (fn [block]
+                  (let [text (block-handler/block-unique-title block)
+                        icon (get-page-icon block)]
+                    {:icon icon
+                     :icon-theme :gray
+                     :text text
+                     :source-block block})))
+           (hash-map :status :success :items)
+           (swap! !results update group merge)))))
+
 (defn highlight-content-query
 (defn highlight-content-query
   "Return hiccup of highlighted content FTS result"
   "Return hiccup of highlighted content FTS result"
   [content q]
   [content q]
@@ -396,6 +417,7 @@
           (load-results :nodes state)
           (load-results :nodes state)
           (load-results :filters state)
           (load-results :filters state)
           (load-results :files state)
           (load-results :files state)
+          (load-results :recently-updated-pages state)
           ;; (load-results :recents state)
           ;; (load-results :recents state)
           )))))
           )))))
 
 

+ 1 - 2
src/main/frontend/components/imports.cljs

@@ -370,8 +370,7 @@
                    ;; config file options
                    ;; config file options
                    :default-config config/config-default-content
                    :default-config config/config-default-content
                    :<save-config-file (fn save-config-file [_ path content]
                    :<save-config-file (fn save-config-file [_ path content]
-                                        (let [migrated-content (repo-handler/migrate-db-config content)]
-                                          (db-editor-handler/save-file! path migrated-content)))
+                                        (db-editor-handler/save-file! path content))
                    ;; logseq file options
                    ;; logseq file options
                    :<save-logseq-file (fn save-logseq-file [_ path content]
                    :<save-logseq-file (fn save-logseq-file [_ path content]
                                         (db-editor-handler/save-file! path content))
                                         (db-editor-handler/save-file! path content))

+ 1 - 0
src/main/frontend/components/reference.cljs

@@ -43,6 +43,7 @@
     (views/view
     (views/view
      {:view-parent page-entity
      {:view-parent page-entity
       :view-feature-type :linked-references
       :view-feature-type :linked-references
+      :show-items-count? true
       :additional-actions [reference-filter]
       :additional-actions [reference-filter]
       :columns (views/build-columns config [] {})
       :columns (views/build-columns config [] {})
       :config config})))
       :config config})))

+ 3 - 36
src/main/frontend/handler/common/config_edn.cljs

@@ -7,7 +7,8 @@
             [lambdaisland.glogi :as log]
             [lambdaisland.glogi :as log]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
             [goog.string :as gstring]
             [goog.string :as gstring]
-            [reitit.frontend.easy :as rfe]))
+            [reitit.frontend.easy :as rfe]
+            [logseq.common.config :as common-config]))
 
 
 (defn- humanize-more
 (defn- humanize-more
   "Make error maps from me/humanize more readable for users. Doesn't try to handle
   "Make error maps from me/humanize more readable for users. Doesn't try to handle
@@ -86,40 +87,6 @@ nested keys or positional errors e.g. tuples"
       :else
       :else
       (validate-config-map parsed-body schema path))))
       (validate-config-map parsed-body schema path))))
 
 
-(def file-only-config
-  "File only config that is deprecated in DB graphs"
-  (merge
-   (zipmap
-    [:file/name-format
-     :file-sync/ignore-files
-     :hidden
-     :ignored-page-references-keywords
-     :journal/file-name-format
-     :journal/page-title-format
-     :journals-directory
-     :logbook/settings
-     :org-mode/insert-file-link?
-     :pages-directory
-     :preferred-workflow
-     :property/separated-by-commas
-     :property-pages/excludelist
-     :srs/learning-fraction
-     :srs/initial-interval
-     :whiteboards-directory]
-    (repeat "is not used in DB graphs"))
-   {:preferred-format
-    "is not used in DB graphs as there is only markdown mode."
-    :property-pages/enabled?
-    "is not used in DB graphs as all properties have pages"
-    :block-hidden-properties
-    "is not used in DB graphs as hiding a property is done in its configuration"
-    :feature/enable-block-timestamps?
-    "is not used in DB graphs as it is always enabled"
-    :favorites
-    "is not stored in config for DB graphs"
-    :default-templates
-    "is replaced by #Template and the `Apply template to tags` property"}))
-
 (defn detect-deprecations
 (defn detect-deprecations
   "Detects config keys that will or have been deprecated"
   "Detects config keys that will or have been deprecated"
   [path content {:keys [db-graph?]}]
   [path content {:keys [db-graph?]}]
@@ -132,7 +99,7 @@ nested keys or positional errors e.g. tuples"
                    "is no longer supported."}
                    "is no longer supported."}
                    db-graph?
                    db-graph?
                    (merge
                    (merge
-                    file-only-config))]
+                    common-config/file-only-config))]
     (cond
     (cond
       (= body ::failed-to-detect)
       (= body ::failed-to-detect)
       (log/info :msg "Skip deprecation check since config is not valid edn")
       (log/info :msg "Skip deprecation check since config is not valid edn")

+ 5 - 1
src/main/frontend/handler/events.cljs

@@ -50,6 +50,7 @@
             [frontend.util.persist-var :as persist-var]
             [frontend.util.persist-var :as persist-var]
             [goog.dom :as gdom]
             [goog.dom :as gdom]
             [lambdaisland.glogi :as log]
             [lambdaisland.glogi :as log]
+            [logseq.db.frontend.schema :as db-schema]
             [promesa.core :as p]))
             [promesa.core :as p]))
 
 
 ;; TODO: should we move all events here?
 ;; TODO: should we move all events here?
@@ -199,7 +200,10 @@
                        :user-id user-uuid
                        :user-id user-uuid
                        :graph-id graph-uuid
                        :graph-id graph-uuid
                        :tx-id tx-id
                        :tx-id tx-id
-                       :db-based (config/db-based-graph? (state/get-current-repo)))]
+                       :db-based (config/db-based-graph? (state/get-current-repo))
+                       :schema-version db-schema/version
+                       :db-schema-version (when-let [db (frontend.db/get-db)]
+                                            (:kv/value (frontend.db/entity db :logseq.kv/schema-version))))]
     (Sentry/captureException error
     (Sentry/captureException error
                              (bean/->js {:tags payload}))))
                              (bean/->js {:tags payload}))))
 
 

+ 4 - 12
src/main/frontend/handler/repo.cljs

@@ -1,8 +1,7 @@
 (ns frontend.handler.repo
 (ns frontend.handler.repo
   "System-component-like ns that manages user's repos/graphs"
   "System-component-like ns that manages user's repos/graphs"
   (:refer-clojure :exclude [clone])
   (:refer-clojure :exclude [clone])
-  (:require [borkdude.rewrite-edn :as rewrite]
-            [cljs-bean.core :as bean]
+  (:require [cljs-bean.core :as bean]
             [clojure.string :as string]
             [clojure.string :as string]
             [electron.ipc :as ipc]
             [electron.ipc :as ipc]
             [frontend.config :as config]
             [frontend.config :as config]
@@ -11,7 +10,6 @@
             [frontend.db.persist :as db-persist]
             [frontend.db.persist :as db-persist]
             [frontend.db.react :as react]
             [frontend.db.react :as react]
             [frontend.db.restore :as db-restore]
             [frontend.db.restore :as db-restore]
-            [frontend.handler.common.config-edn :as config-edn-common-handler]
             [frontend.handler.global-config :as global-config-handler]
             [frontend.handler.global-config :as global-config-handler]
             [frontend.handler.graph :as graph-handler]
             [frontend.handler.graph :as graph-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
@@ -27,7 +25,8 @@
             [frontend.util :as util]
             [frontend.util :as util]
             [frontend.util.fs :as util-fs]
             [frontend.util.fs :as util-fs]
             [frontend.util.text :as text-util]
             [frontend.util.text :as text-util]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [logseq.common.config :as common-config]))
 
 
 ;; Project settings should be checked in two situations:
 ;; Project settings should be checked in two situations:
 ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
 ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
@@ -177,16 +176,9 @@
   (let [full-graph-name (string/lower-case (str config/db-version-prefix graph-name))]
   (let [full-graph-name (string/lower-case (str config/db-version-prefix graph-name))]
     (some #(= (some-> (:url %) string/lower-case) full-graph-name) (state/get-repos))))
     (some #(= (some-> (:url %) string/lower-case) full-graph-name) (state/get-repos))))
 
 
-(defn migrate-db-config
-  [content]
-  (-> (reduce rewrite/dissoc
-              (rewrite/parse-string (str content))
-              (keys config-edn-common-handler/file-only-config))
-      str))
-
 (defn- create-db [full-graph-name {:keys [file-graph-import?]}]
 (defn- create-db [full-graph-name {:keys [file-graph-import?]}]
   (->
   (->
-   (p/let [config (migrate-db-config config/config-default-content)
+   (p/let [config (common-config/create-config-for-db-graph config/config-default-content)
            _ (persist-db/<new full-graph-name
            _ (persist-db/<new full-graph-name
                               (cond-> {:config config}
                               (cond-> {:config config}
                                 file-graph-import? (assoc :import-type :file-graph)))
                                 file-graph-import? (assoc :import-type :file-graph)))

+ 4 - 4
src/main/frontend/modules/instrumentation/sentry.cljs

@@ -16,14 +16,14 @@
                          version)
                          version)
    :environment (if config/dev? "development" "production")
    :environment (if config/dev? "development" "production")
    :initialScope {:tags
    :initialScope {:tags
-                  (merge
-                   (when (not-empty config/revision)
-                     {:revision config/revision})
+                  (cond->
                    {:platform (cond
                    {:platform (cond
                                 (util/electron?) "electron"
                                 (util/electron?) "electron"
                                 (mobile-util/native-platform?) "mobile"
                                 (mobile-util/native-platform?) "mobile"
                                 :else "web")
                                 :else "web")
-                    :publishing config/publishing?})}
+                    :publishing config/publishing?}
+                    (not-empty config/revision)
+                    (assoc :revision config/revision))}
    ;; :integrations [(new posthog/SentryIntegration posthog "logseq" 5311485)
    ;; :integrations [(new posthog/SentryIntegration posthog "logseq" 5311485)
    ;;                (new BrowserTracing)]
    ;;                (new BrowserTracing)]
    :debug config/dev?
    :debug config/dev?

+ 0 - 1
src/main/frontend/state.cljs

@@ -2376,7 +2376,6 @@ Similar to re-frame subscriptions"
 
 
 (defn set-highlight-recent-days!
 (defn set-highlight-recent-days!
   [days]
   [days]
-  (prn :debug :set :days days)
   (reset! (:ui/highlight-recent-days @state) days)
   (reset! (:ui/highlight-recent-days @state) days)
   (storage/set :ui/highlight-recent-days days))
   (storage/set :ui/highlight-recent-days days))
 
 

+ 8 - 3
src/main/frontend/worker/db/migrate.cljs

@@ -16,6 +16,7 @@
             [logseq.db.common.order :as db-order]
             [logseq.db.common.order :as db-order]
             [logseq.db.frontend.class :as db-class]
             [logseq.db.frontend.class :as db-class]
             [logseq.db.frontend.content :as db-content]
             [logseq.db.frontend.content :as db-content]
+            [logseq.db.frontend.malli-schema :as db-malli-schema]
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.property.build :as db-property-build]
             [logseq.db.frontend.property.build :as db-property-build]
             [logseq.db.frontend.schema :as db-schema]
             [logseq.db.frontend.schema :as db-schema]
@@ -1172,8 +1173,11 @@
             (js/console.error e)
             (js/console.error e)
             (throw e)))))))
             (throw e)))))))
 
 
-(defn- build-invalid-tx [entity eid]
+(defn- build-invalid-tx [db entity eid]
   (cond
   (cond
+    (nil? (db-malli-schema/entity-dispatch-key db entity))
+    [[:db/retractEntity eid]]
+
     (:block/schema entity)
     (:block/schema entity)
     [[:db/retract eid :block/schema]]
     [[:db/retract eid :block/schema]]
 
 
@@ -1200,7 +1204,8 @@
     (= #{:block/tx-id} (set (keys entity)))
     (= #{:block/tx-id} (set (keys entity)))
     [[:db/retractEntity (:db/id entity)]]
     [[:db/retractEntity (:db/id entity)]]
 
 
-    (and (seq (:block/refs entity))
+    (and (or (seq (:block/refs entity))
+             (:logseq.property.table/filters entity))
          (not (or (:block/title entity) (:block/content entity) (:property.value/content entity))))
          (not (or (:block/title entity) (:block/content entity) (:property.value/content entity))))
     [[:db/retractEntity (:db/id entity)]]
     [[:db/retractEntity (:db/id entity)]]
 
 
@@ -1285,7 +1290,7 @@
                                                     [:db/retract (:db/id entity) k]))))))
                                                     [:db/retract (:db/id entity) k]))))))
                                         (into {} entity))
                                         (into {} entity))
                           eid (:db/id entity)
                           eid (:db/id entity)
-                          fix (build-invalid-tx entity eid)]
+                          fix (build-invalid-tx db entity eid)]
                       (into fix wrong-choice)))
                       (into fix wrong-choice)))
                   invalid-entity-ids)
                   invalid-entity-ids)
                  distinct)]
                  distinct)]

+ 96 - 135
src/main/frontend/worker/db_worker.cljs

@@ -32,7 +32,6 @@
             [frontend.worker.util :as worker-util]
             [frontend.worker.util :as worker-util]
             [goog.object :as gobj]
             [goog.object :as gobj]
             [lambdaisland.glogi.console :as glogi-console]
             [lambdaisland.glogi.console :as glogi-console]
-            [logseq.common.log :as log]
             [logseq.common.util :as common-util]
             [logseq.common.util :as common-util]
             [logseq.db :as ldb]
             [logseq.db :as ldb]
             [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.common.entity-plus :as entity-plus]
@@ -101,38 +100,39 @@
   [^js pool data]
   [^js pool data]
   (.importDb ^js pool repo-path data))
   (.importDb ^js pool repo-path data))
 
 
-(defn- get-all-datoms-from-sqlite-db
-  [db]
-  (some->> (.exec db #js {:sql "select * from kvs"
-                          :rowMode "array"})
-           bean/->clj
-           (mapcat
-            (fn [[_addr content _addresses]]
-              (let [content' (sqlite-util/transit-read content)
-                    datoms (when (map? content')
-                             (:keys content'))]
-                datoms)))
-           distinct
-           (map (fn [[e a v t]]
-                  (d/datom e a v t)))))
-
-(defn- rebuild-db-from-datoms!
-  "Persistent-sorted-set has been broken, used addresses can't be found"
-  [datascript-conn sqlite-db import-type]
-  (let [datoms (get-all-datoms-from-sqlite-db sqlite-db)
-        db (d/init-db [] db-schema/schema
-                      {:storage (storage/storage @datascript-conn)})
-        db (d/db-with db
-                      (map (fn [d]
-                             [:db/add (:e d) (:a d) (:v d) (:t d)]) datoms))]
-    (prn :debug :rebuild-db-from-datoms :datoms-count (count datoms))
+(comment
+  (defn- get-all-datoms-from-sqlite-db
+    [db]
+    (some->> (.exec db #js {:sql "select * from kvs"
+                            :rowMode "array"})
+             bean/->clj
+             (mapcat
+              (fn [[_addr content _addresses]]
+                (let [content' (sqlite-util/transit-read content)
+                      datoms (when (map? content')
+                               (:keys content'))]
+                  datoms)))
+             distinct
+             (map (fn [[e a v t]]
+                    (d/datom e a v t)))))
+
+  (defn- rebuild-db-from-datoms!
+    "Persistent-sorted-set has been broken, used addresses can't be found"
+    [datascript-conn sqlite-db import-type]
+    (let [datoms (get-all-datoms-from-sqlite-db sqlite-db)
+          db (d/init-db [] db-schema/schema
+                        {:storage (storage/storage @datascript-conn)})
+          db (d/db-with db
+                        (map (fn [d]
+                               [:db/add (:e d) (:a d) (:v d) (:t d)]) datoms))]
+      (prn :debug :rebuild-db-from-datoms :datoms-count (count datoms))
     ;; export db first
     ;; export db first
-    (when-not import-type
-      (worker-util/post-message :notification ["The SQLite db will be exported to avoid any data-loss." :warning false])
-      (worker-util/post-message :export-current-db []))
-    (.exec sqlite-db #js {:sql "delete from kvs"})
-    (d/reset-conn! datascript-conn db)
-    (db-migrate/fix-db! datascript-conn)))
+      (when-not import-type
+        (worker-util/post-message :notification ["The SQLite db will be exported to avoid any data-loss." :warning false])
+        (worker-util/post-message :export-current-db []))
+      (.exec sqlite-db #js {:sql "delete from kvs"})
+      (d/reset-conn! datascript-conn db)
+      (db-migrate/fix-db! datascript-conn))))
 
 
 (comment
 (comment
   (defn- gc-kvs-table!
   (defn- gc-kvs-table!
@@ -158,104 +158,74 @@
                                             :bind #js [addr]}))))))))
                                             :bind #js [addr]}))))))))
 
 
 (defn- find-missing-addresses
 (defn- find-missing-addresses
-  [^Object db & {:keys [delete-addrs upsert-addr-content? open-db?]}]
-  (worker-util/profile
-   "find-missing-addresses"
-   (let [schema (some->> (.exec db #js {:sql "select content from kvs where addr = 0"
-                                        :rowMode "array"})
-                         bean/->clj
-                         ffirst
-                         sqlite-util/transit-read)
-         result (->> (.exec db #js {:sql "select addr, addresses from kvs"
-                                    :rowMode "array"})
-                     bean/->clj
-                     (keep (fn [[addr addresses]]
-                             (when-not (and delete-addrs (delete-addrs addr))
-                               [addr (bean/->clj (js/JSON.parse addresses))]))))
-         used-addresses (-> (set (concat (mapcat second result)
-                                         [0 1 (:eavt schema) (:avet schema) (:aevt schema)]))
-                            (clojure.set/difference delete-addrs))
-         missing-addresses (clojure.set/difference used-addresses (set (map first result)))]
-     (when (seq missing-addresses)
-       (prn :error :missing-addresses missing-addresses)
-       (if worker-util/dev?
-         (throw (ex-info "Found missing addresses that shouldn't happen" {:missing-addresses missing-addresses}))
-         (worker-util/post-message :capture-error
-                                   {:error "v2-db-missing-addresses"
-                                    :payload {:missing-addresses missing-addresses
-                                              :upsert-addr-content upsert-addr-content?
-                                              :open-db open-db?}})))
-     missing-addresses)))
+  [conn ^Object db & {:keys [delete-addrs]}]
+  (let [schema (some->> (.exec db #js {:sql "select content from kvs where addr = 0"
+                                       :rowMode "array"})
+                        bean/->clj
+                        ffirst
+                        sqlite-util/transit-read)
+        result (->> (.exec db #js {:sql "select addr, addresses from kvs"
+                                   :rowMode "array"})
+                    bean/->clj
+                    (keep (fn [[addr addresses]]
+                            (when-not (and delete-addrs (delete-addrs addr))
+                              [addr (bean/->clj (js/JSON.parse addresses))]))))
+        used-addresses (-> (set (concat (mapcat second result)
+                                        [0 1 (:eavt schema) (:avet schema) (:aevt schema)]))
+                           (clojure.set/difference delete-addrs))
+        missing-addresses (clojure.set/difference used-addresses (set (map first result)))]
+    (when (seq missing-addresses)
+      (let [version-in-db (when conn (db-schema/parse-schema-version (or (:kv/value (d/entity @conn :logseq.kv/schema-version)) 0)))
+            compare-result (when version-in-db (db-schema/compare-schema-version version-in-db "64.8"))]
+        (when (and compare-result (not (neg? compare-result))) ; >= 64.8
+          (worker-util/post-message :capture-error
+                                    {:error "db-missing-addresses-v2"
+                                     :payload {:missing-addresses missing-addresses}}))))
+    missing-addresses))
 
 
 (defn upsert-addr-content!
 (defn upsert-addr-content!
   "Upsert addr+data-seq. Update sqlite-cli/upsert-addr-content! when making changes"
   "Upsert addr+data-seq. Update sqlite-cli/upsert-addr-content! when making changes"
-  [repo data delete-addrs* & {:keys [client-ops-db?] :or {client-ops-db? false}}]
-  (let [^Object db (worker-state/get-sqlite-conn repo (if client-ops-db? :client-ops :db))
-        delete-addrs (set delete-addrs*)]
+  [db data delete-addrs*]
+  (let [delete-addrs (clojure.set/difference (set delete-addrs*) #{0 1})]
     (assert (some? db) "sqlite db not exists")
     (assert (some? db) "sqlite db not exists")
     (.transaction db (fn [tx]
     (.transaction db (fn [tx]
                        (doseq [item data]
                        (doseq [item data]
                          (.exec tx #js {:sql "INSERT INTO kvs (addr, content, addresses) values ($addr, $content, $addresses) on conflict(addr) do update set content = $content, addresses = $addresses"
                          (.exec tx #js {:sql "INSERT INTO kvs (addr, content, addresses) values ($addr, $content, $addresses) on conflict(addr) do update set content = $content, addresses = $addresses"
                                         :bind item}))))
                                         :bind item}))))
     (when (seq delete-addrs)
     (when (seq delete-addrs)
+      (.transaction db (fn [tx]
+                         (doseq [addr delete-addrs]
+                           (.exec tx #js {:sql "Delete from kvs WHERE addr = ? AND NOT EXISTS (SELECT 1 FROM json_each(addresses) WHERE value = ?);"
+                                          :bind #js [addr]}))))
       (let [missing-addrs (when worker-util/dev?
       (let [missing-addrs (when worker-util/dev?
-                            (seq (find-missing-addresses db {:delete-addrs delete-addrs
-                                                             :upsert-addr-content? true})))
-            delete-addrs' (if missing-addrs
-                            (remove (set missing-addrs) delete-addrs)
-                            delete-addrs)]
-        (when (seq delete-addrs')
-          (.transaction db (fn [tx]
-                             (doseq [addr delete-addrs']
-                               (.exec tx #js {:sql "Delete from kvs WHERE addr = ? AND NOT EXISTS (SELECT 1 FROM json_each(addresses) WHERE value = ?);"
-                                              :bind #js [addr]})))))))))
+                            (seq (find-missing-addresses nil db {:delete-addrs delete-addrs})))]
+        (if (seq missing-addrs)
+          (worker-util/post-message :notification [(str "Bug!! Missing addresses: " missing-addrs) :error false])
+          (when (seq delete-addrs)
+            (.transaction db (fn [tx]
+                               (doseq [addr delete-addrs]
+                                 (.exec tx #js {:sql "Delete from kvs WHERE addr = ? AND NOT EXISTS (SELECT 1 FROM json_each(addresses) WHERE value = ?);"
+                                                :bind #js [addr]}))))))))))
 
 
 (defn restore-data-from-addr
 (defn restore-data-from-addr
   "Update sqlite-cli/restore-data-from-addr when making changes"
   "Update sqlite-cli/restore-data-from-addr when making changes"
-  [repo addr & {:keys [client-ops-db?] :or {client-ops-db? false}}]
-  (let [^Object db (worker-state/get-sqlite-conn repo (if client-ops-db? :client-ops :db))]
-    (assert (some? db) "sqlite db not exists")
-    (when-let [result (-> (.exec db #js {:sql "select content, addresses from kvs where addr = ?"
-                                         :bind #js [addr]
-                                         :rowMode "array"})
-                          first)]
-      (let [[content addresses] (bean/->clj result)
-            addresses (when addresses
-                        (js/JSON.parse addresses))
-            data (sqlite-util/transit-read content)]
-        (if (and addresses (map? data))
-          (assoc data :addresses addresses)
-          data)))))
+  [db addr]
+  (assert (some? db) "sqlite db not exists")
+  (when-let [result (-> (.exec db #js {:sql "select content, addresses from kvs where addr = ?"
+                                       :bind #js [addr]
+                                       :rowMode "array"})
+                        first)]
+    (let [[content addresses] (bean/->clj result)
+          addresses (when addresses
+                      (js/JSON.parse addresses))
+          data (sqlite-util/transit-read content)]
+      (if (and addresses (map? data))
+        (assoc data :addresses addresses)
+        data))))
 
 
 (defn new-sqlite-storage
 (defn new-sqlite-storage
   "Update sqlite-cli/new-sqlite-storage when making changes"
   "Update sqlite-cli/new-sqlite-storage when making changes"
-  [repo _opts]
-  (reify IStorage
-    (-store [_ addr+data-seq delete-addrs]
-      (let [used-addrs (set (mapcat
-                             (fn [[addr data]]
-                               (cons addr
-                                     (when (map? data)
-                                       (:addresses data))))
-                             addr+data-seq))
-            delete-addrs (remove used-addrs delete-addrs)
-            data (map
-                  (fn [[addr data]]
-                    (let [data' (if (map? data) (dissoc data :addresses) data)
-                          addresses (when (map? data)
-                                      (when-let [addresses (:addresses data)]
-                                        (js/JSON.stringify (bean/->js addresses))))]
-                      #js {:$addr addr
-                           :$content (sqlite-util/transit-write data')
-                           :$addresses addresses}))
-                  addr+data-seq)]
-        (upsert-addr-content! repo data delete-addrs)))
-
-    (-restore [_ addr]
-      (restore-data-from-addr repo addr))))
-
-(defn new-sqlite-client-ops-storage
-  [repo]
+  [^Object db]
   (reify IStorage
   (reify IStorage
     (-store [_ addr+data-seq delete-addrs]
     (-store [_ addr+data-seq delete-addrs]
       (let [used-addrs (set (mapcat
       (let [used-addrs (set (mapcat
@@ -275,10 +245,10 @@
                            :$content (sqlite-util/transit-write data')
                            :$content (sqlite-util/transit-write data')
                            :$addresses addresses}))
                            :$addresses addresses}))
                   addr+data-seq)]
                   addr+data-seq)]
-        (upsert-addr-content! repo data delete-addrs :client-ops-db? true)))
+        (upsert-addr-content! db data delete-addrs)))
 
 
     (-restore [_ addr]
     (-restore [_ addr]
-      (restore-data-from-addr repo addr :client-ops-db? true))))
+      (restore-data-from-addr db addr))))
 
 
 (defn- close-db-aux!
 (defn- close-db-aux!
   [repo ^Object db ^Object search ^Object client-ops]
   [repo ^Object db ^Object search ^Object client-ops]
@@ -338,8 +308,9 @@
   [repo {:keys [config import-type datoms]}]
   [repo {:keys [config import-type datoms]}]
   (when-not (worker-state/get-sqlite-conn repo)
   (when-not (worker-state/get-sqlite-conn repo)
     (p/let [[db search-db client-ops-db :as dbs] (get-dbs repo)
     (p/let [[db search-db client-ops-db :as dbs] (get-dbs repo)
-            storage (new-sqlite-storage repo {})
-            client-ops-storage (when-not @*publishing? (new-sqlite-client-ops-storage repo))
+            storage (new-sqlite-storage db)
+            client-ops-storage (when-not @*publishing?
+                                 (new-sqlite-storage client-ops-db))
             db-based? (sqlite-util/db-based-graph? repo)]
             db-based? (sqlite-util/db-based-graph? repo)]
       (swap! *sqlite-conns assoc repo {:db db
       (swap! *sqlite-conns assoc repo {:db db
                                        :search search-db
                                        :search search-db
@@ -374,24 +345,14 @@
                                                                         (when import-type {:import-type import-type}))]
                                                                         (when import-type {:import-type import-type}))]
             (d/transact! conn initial-data {:initial-db? true})))
             (d/transact! conn initial-data {:initial-db? true})))
 
 
-        (try
-
-          ;; TODO: remove this once we can ensure there's no bug for missing addresses
-          ;; because it's slow for large graphs
-          (when-not import-type
-            (when-let [missing-addresses (seq (find-missing-addresses db {:open-db? true}))]
-              (throw (ex-info "DB missing addresses" {:missing-addresses missing-addresses}))))
-
-          (db-migrate/migrate conn search-db)
+        ;; TODO: remove this once we can ensure there's no bug for missing addresses
+        ;; because it's slow for large graphs
+        (when-not import-type
+          (when-let [missing-addresses (seq (find-missing-addresses conn db))]
+            (worker-util/post-message :notification ["It seems that the DB has been broken, please export a backup and contact Logseq team for help." :error false])
+            (throw (ex-info "DB missing addresses" {:missing-addresses missing-addresses}))))
 
 
-          ;; TODO: Remove this once we can ensure there's no bug for missing addresses
-          (catch :default e
-            (log/error (str "DB migrate failed for " repo ", error:") e)
-            (if (= (.-message e) "DB missing addresses")
-              (do
-                (rebuild-db-from-datoms! conn db import-type)
-                (db-migrate/migrate conn search-db))
-              (throw e))))
+        (db-migrate/migrate conn search-db)
 
 
         (db-listener/listen-db-changes! repo (get @*datascript-conns repo))))))
         (db-listener/listen-db-changes! repo (get @*datascript-conns repo))))))
 
 

+ 5 - 3
src/main/frontend/worker/pipeline.cljs

@@ -107,9 +107,11 @@
     (let [valid? (if (get-in tx-report [:tx-meta :reset-conn!])
     (let [valid? (if (get-in tx-report [:tx-meta :reset-conn!])
                    true
                    true
                    (db-validate/validate-tx-report! tx-report (:validate-db-options context)))]
                    (db-validate/validate-tx-report! tx-report (:validate-db-options context)))]
-      (when (and (get-in context [:validate-db-options :fail-invalid?]) (not valid?))
-        (shared-service/broadcast-to-clients! :notification
-                                              [["Invalid DB!"] :error]))))
+      (when-not valid?
+        (when (or (get-in context [:validate-db-options :fail-invalid?]) worker-util/dev?)
+          (shared-service/broadcast-to-clients! :notification
+                                                [["Invalid DB!"] :error]))
+        (throw (ex-info "Invalid data" {:graph repo})))))
 
 
   ;; Ensure :block/order is unique for any block that has :block/parent
   ;; Ensure :block/order is unique for any block that has :block/parent
   (when (or (:dev? context) (exists? js/process))
   (when (or (:dev? context) (exists? js/process))

+ 7 - 4
src/main/frontend/worker/shared_service.cljs

@@ -294,10 +294,13 @@
   (.postMessage common-channel #js {:type "master-changed"
   (.postMessage common-channel #js {:type "master-changed"
                                     :master-client-id master-client-id
                                     :master-client-id master-client-id
                                     :serviceName service-name})
                                     :serviceName service-name})
-  (p/do!
-   (on-become-master-handler service-name)
-   (<re-requests-in-flight-on-master! target)
-   (p/resolve! status-ready-deferred-p)))
+  (->
+   (p/do!
+    (on-become-master-handler service-name)
+    (<re-requests-in-flight-on-master! target))
+   (p/finally
+     (fn []
+       (p/resolve! status-ready-deferred-p)))))
 
 
 (defn <create-service
 (defn <create-service
   "broadcast-data-types - For data matching these types,
   "broadcast-data-types - For data matching these types,