Explorar el Código

Merge branch 'feat/db' into feat/blocks-action-bar

Tienson Qin hace 11 meses
padre
commit
d095ceedd6

+ 6 - 6
.github/workflows/build-desktop-release.yml

@@ -53,7 +53,7 @@ env:
 
 jobs:
   compile-cljs:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       - name: Check build options
         if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.build-target == 'nightly' || github.event.inputs.build-target == 'beta') && github.event.inputs.git-ref != 'master' }}
@@ -173,7 +173,7 @@ jobs:
 
   e2e-test:
     name: E2E Test Shard ${{ matrix.shard }}
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         shard: [1, 2, 3]
@@ -223,7 +223,7 @@ jobs:
           RELEASE: true # skip dev only test
 
   build-linux-x64:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     needs: [ compile-cljs ]
     steps:
       - name: Download The Static Asset
@@ -269,7 +269,7 @@ jobs:
           path: builds
 
   build-linux-arm64:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     needs: [ compile-cljs ]
     steps:
       - name: Download The Static Asset
@@ -581,7 +581,7 @@ jobs:
   nightly-release:
     if: ${{ github.event_name == 'schedule' || github.event.inputs.build-target == 'nightly' }}
     needs: [ build-macos-x64, build-macos-arm64, build-linux-x64, build-linux-arm64, codesign-windows, build-android, e2e-test ]
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       - name: Download MacOS x64 Artifacts
         uses: actions/download-artifact@v3
@@ -661,7 +661,7 @@ jobs:
     # NOTE: For now, we only have beta channel to be released on Github
     if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.build-target == 'beta' }}
     needs: [ build-macos-x64, build-macos-arm64, build-linux-x64, build-linux-arm64, codesign-windows, build-android, e2e-test ]
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       - name: Download MacOS x64 Artifacts
         uses: actions/download-artifact@v3

+ 1 - 1
.github/workflows/build-docker.yml

@@ -12,7 +12,7 @@ env:
 jobs:
 
   build-docker:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
 
     steps:
       - name: Checkout

+ 4 - 4
.github/workflows/build.yml

@@ -20,7 +20,7 @@ env:
 jobs:
   typos:
     name: Spell Check with Typos
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     steps:
       - name: Checkout Actions Repository
         uses: actions/checkout@v4
@@ -32,7 +32,7 @@ jobs:
   test:
     strategy:
       matrix:
-        operating-system: [ubuntu-latest]
+        operating-system: [ubuntu-22.04]
 
     runs-on: ${{ matrix.operating-system }}
 
@@ -90,7 +90,7 @@ jobs:
         run: node static/tests.js -e fix-me
 
   lint:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
 
     steps:
       - name: Checkout
@@ -180,7 +180,7 @@ jobs:
   e2e-test:
     # TODO: Re-enable when ready to enable tests for file graphs
     if: false
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
 
     steps:
       - name: Checkout

+ 2 - 2
.github/workflows/e2e.yml

@@ -25,7 +25,7 @@ env:
 jobs:
   e2e-test-build:
     name: Build Test Artifact
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     steps:
       - name: Checkout
         uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c  # v3.3.0
@@ -98,7 +98,7 @@ jobs:
   e2e-test-run:
     needs: [ e2e-test-build ]
     name: Test Shard ${{ matrix.shard }} Repeat ${{ matrix.repeat }}
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         repeat: [1, 2]

+ 4 - 4
package.json

@@ -6,7 +6,7 @@
     "devDependencies": {
         "@axe-core/playwright": "=4.4.4",
         "@capacitor/cli": "^5.0.0",
-        "@playwright/test": "=1.44.0",
+        "@playwright/test": "=1.51.0",
         "@tailwindcss/aspect-ratio": "0.4.2",
         "@tailwindcss/forms": "0.5.3",
         "@tailwindcss/typography": "0.5.7",
@@ -25,7 +25,7 @@
         "karma-chrome-launcher": "^3.2.0",
         "karma-cljs-test": "^0.1.0",
         "npm-run-all": "^4.1.5",
-        "playwright": "=1.44.0",
+        "playwright": "=1.51.0",
         "postcss": "^8.4.47",
         "postcss-cli": "10.0.0",
         "postcss-functions": "^4.0.2",
@@ -131,8 +131,8 @@
         "d3-force": "3.0.0",
         "diff": "5.0.0",
         "dompurify": "2.4.0",
-        "electron": "31.7.5",
-        "electron-dl": "3.3.0",
+        "electron": "35.0.1",
+        "electron-dl": "^4.0.0",
         "emoji-mart": "^5.5.2",
         "fs": "0.0.1-security",
         "fs-extra": "9.1.0",

+ 6 - 6
resources/package.json

@@ -14,7 +14,7 @@
     "electron:make-linux-arm64": "electron-forge make --platform=linux --arch=arm64",
     "electron:make-macos-arm64": "electron-forge make --platform=darwin --arch=arm64",
     "electron:publish:github": "electron-forge publish",
-    "rebuild:all": "electron-rebuild -v 28.3.1 -f",
+    "rebuild:all": "electron-rebuild -v 35.0.1 -f",
     "postinstall": "install-app-deps"
   },
   "config": {
@@ -54,16 +54,16 @@
     "@electron-forge/maker-squirrel": "^7.3.1",
     "@electron-forge/maker-zip": "^7.3.1",
     "@electron/rebuild": "3.2.10",
-    "electron": "31.7.5",
+    "electron": "35.0.1",
     "electron-builder": "25.1.8",
     "electron-forge-maker-appimage": "https://github.com/logseq/electron-forge-maker-appimage.git",
     "electron-devtools-installer": "^3.2.0"
   },
   "resolutions": {
-    "**/electron": "31.7.5",
-    "**/node-abi": "3.68.0",
-    "**/node-gyp": "10.0.1",
-    "string-width": "^4.2.0",
+    "**/electron": "35.0.1",
+    "**/node-abi": "3.74.0",
+    "**/node-gyp": "11.1.0",
+    "string-width": "4.2.0",
     "wrap-ansi": "^7.0.0",
     "strip-ansi": "^6.0.1"
   }

+ 1 - 1
src/electron/electron/fs_watcher.cljs

@@ -52,7 +52,7 @@
                   (utils/read-file path))
         stat (when (and (not= event "unlink")
                         (not dir-path?))
-               (fs/statSync path))]
+               (utils/fs-stat->clj path))]
     (send-file-watcher! dir event (merge {:dir (utils/fix-win-path! dir)
                                           :path (utils/fix-win-path! path)
                                           :content content

+ 2 - 2
src/electron/electron/handler.cljs

@@ -123,7 +123,7 @@
       (when (and (chmod-enabled?) (fs/existsSync path) (not (writable? path)))
         (fs/chmodSync path "644"))
       (fs/writeFileSync path content)
-      (fs/statSync path)
+      (utils/fs-stat->clj path)
       (catch :default e
         (logger/warn ::write-file path e)
         (let [backup-path (try
@@ -144,7 +144,7 @@
   (fs/renameSync old-path new-path))
 
 (defmethod handle :stat [_window [_ path]]
-  (fs/statSync path))
+  (utils/fs-stat->clj path))
 
 (defn- get-files
   "Returns vec of file-objs"

+ 7 - 1
src/electron/electron/utils.cljs

@@ -151,7 +151,6 @@
         (logger/warn "Unknown PAC rule:" line)
         nil))))
 
-
 (defn <get-system-proxy
   "Get system proxy for url, requires proxy to be set to system"
   ([] (<get-system-proxy "https://www.google.com"))
@@ -295,3 +294,10 @@
     (catch :default _
       (println "decodeURIComponent failed: " uri)
       uri)))
+
+(defn fs-stat->clj
+  [path]
+  (let [stat (fs/statSync path)]
+    {:size (.-size stat)
+     :mtime (.-mtime stat)
+     :ctime (.-ctime stat)}))

+ 3 - 2
src/main/frontend/components/views.cljs

@@ -1579,8 +1579,9 @@
       (search input {:on-change set-input!
                      :set-input! set-input!})
 
-      [:div.text-muted-foreground.text-sm
-       (pv/property-value view-entity (db/entity :logseq.property.view/type) {})]
+      (when db-based?
+        [:div.text-muted-foreground.text-sm
+         (pv/property-value view-entity (db/entity :logseq.property.view/type) {})])
 
       (when db-based? (more-actions view-entity columns table))
 

+ 2 - 0
src/main/frontend/worker/db_worker.cljs

@@ -344,6 +344,8 @@
                                         (= "db" (:kv/value (d/entity @conn :logseq.kv/db-type)))))]
         (swap! *datascript-conns assoc repo conn)
         (swap! *client-ops-conns assoc repo client-ops-conn)
+        (when (not= client-op/schema-in-db (d/schema @client-ops-conn))
+          (d/reset-schema! client-ops-conn client-op/schema-in-db))
         (when (and db-based? (not initial-data-exists?) (not datoms))
           (let [config (or config "")
                 initial-data (sqlite-create-graph/build-db-initial-data config

+ 98 - 60
src/main/frontend/worker/rtc/client.cljs

@@ -12,6 +12,7 @@
             [frontend.worker.rtc.skeleton :as r.skeleton]
             [frontend.worker.rtc.ws :as ws]
             [frontend.worker.rtc.ws-util :as ws-util]
+            [logseq.db :as ldb]
             [logseq.db.frontend.schema :as db-schema]
             [missionary.core :as m]))
 
@@ -36,7 +37,8 @@
   "Return a task: get or create a mws(missionary wrapped websocket).
   see also `ws/get-mws-create`.
   But ensure `register-graph-updates` and `calibrate-graph-skeleton` has been sent"
-  [get-ws-create-task graph-uuid major-schema-version repo conn *last-calibrate-t *online-users add-log-fn]
+  [get-ws-create-task graph-uuid major-schema-version repo conn
+   *last-calibrate-t *online-users *server-schema-version add-log-fn]
   (assert (some? graph-uuid))
   (let [*sent (atom {}) ;; ws->bool
         ]
@@ -75,8 +77,10 @@
           (let [t (client-op/get-local-tx repo)]
             (when (or (nil? @*last-calibrate-t)
                       (< 500 (- t @*last-calibrate-t)))
-              ;; (m/? (r.skeleton/new-task--calibrate-graph-skeleton get-ws-create-task graph-uuid conn t))
-              (m/? (r.skeleton/new-task--calibrate-graph-skeleton get-ws-create-task graph-uuid major-schema-version @conn))
+              (let [{:keys [server-schema-version _server-builtin-db-idents]}
+                    (m/? (r.skeleton/new-task--calibrate-graph-skeleton
+                          get-ws-create-task graph-uuid major-schema-version @conn))]
+                (reset! *server-schema-version server-schema-version))
               (reset! *last-calibrate-t t)))
           (swap! *sent assoc ws true))
         ws))))
@@ -142,16 +146,14 @@
 (defn- schema-av-coll->update-schema-op
   [db block-uuid db-ident schema-av-coll]
   (when (and (seq schema-av-coll) db-ident)
-    (let [db-ident-ns (namespace db-ident)]
-      (when (and (string/ends-with? db-ident-ns ".property")
-                 (not= db-ident-ns "logseq.property"))
-        (when-let [ent (d/entity db db-ident)]
-          [:update-schema
-           (cond-> {:block-uuid block-uuid
-                    :db/ident db-ident
-                    :db/valueType (or (:db/valueType ent) :db.type/string)}
-             (:db/cardinality ent) (assoc :db/cardinality (:db/cardinality ent))
-             (:db/index ent) (assoc :db/index (:db/index ent)))])))))
+    (when-let [ent (d/entity db db-ident)]
+      (when (ldb/property? ent)
+        [:update-schema
+         (cond-> {:block-uuid block-uuid
+                  :db/ident db-ident
+                  :db/valueType (or (:db/valueType ent) :db.type/string)}
+           (:db/cardinality ent) (assoc :db/cardinality (:db/cardinality ent))
+           (:db/index ent) (assoc :db/index (:db/index ent)))]))))
 
 (defn- av-coll->card-one-attrs
   [db-schema av-coll]
@@ -268,6 +270,30 @@
             (:remote-ops (local-block-ops->remote-ops db block-ops-map))]))
         block-ops-map-coll))
 
+(defmulti ^:private local-db-ident-kv-ops->remote-ops-aux (fn [op-type & _] op-type))
+(defmethod local-db-ident-kv-ops->remote-ops-aux :update-kv-value
+  [_ op]
+  (let [op-value (last op)
+        db-ident (:db-ident op-value)
+        value (:value op-value)]
+    [:update-kv-value {:db-ident db-ident :value (ldb/write-transit-str value)}]))
+
+(defmethod local-db-ident-kv-ops->remote-ops-aux :db-ident
+  [_ _op]
+  ;; ignore
+  )
+
+(defn- local-db-ident-kv-ops->remote-ops
+  [db-ident-kv-ops-map]
+  (keep
+   (fn [[op-type op]]
+     (local-db-ident-kv-ops->remote-ops-aux op-type op))
+   db-ident-kv-ops-map))
+
+(defn- gen-db-ident-kv-remote-ops
+  [db-ident-kv-ops-map-coll]
+  (mapcat local-db-ident-kv-ops->remote-ops db-ident-kv-ops-map-coll))
+
 (defn- merge-remove-remove-ops
   [remote-remove-ops]
   (when-let [block-uuids (->> remote-remove-ops
@@ -326,61 +352,73 @@
     (concat update-schema-ops update-page-ops remove-ops sorted-move-ops update-ops remove-page-ops)))
 
 (defn- rollback
-  [repo block-ops-map-coll]
-  (let [ops (mapcat
-             (fn [m]
-               (keep (fn [[k op]]
-                       (when (not= :block/uuid k)
-                         op))
-                     m))
-             block-ops-map-coll)]
-    (client-op/add-ops repo ops)
+  [repo block-ops-map-coll db-ident-kv-ops-map-coll]
+  (let [block-ops
+        (mapcat
+         (fn [m]
+           (keep (fn [[k op]]
+                   (when-not (keyword-identical? :block/uuid k)
+                     op))
+                 m))
+         block-ops-map-coll)
+        db-ident-kv-ops
+        (mapcat
+         (fn [m]
+           (keep (fn [[k op]]
+                   (when-not (keyword-identical? :db-ident k)
+                     op))
+                 m))
+         db-ident-kv-ops-map-coll)]
+    (client-op/add-ops! repo block-ops)
+    (client-op/add-ops! repo db-ident-kv-ops)
     nil))
 
 (defn new-task--push-local-ops
   "Return a task: push local updates"
   [repo conn graph-uuid major-schema-version date-formatter get-ws-create-task *remote-profile? add-log-fn]
   (m/sp
-    (let [block-ops-map-coll (client-op/get&remove-all-block-ops repo)]
-      (when-let [block-uuid->remote-ops (not-empty (gen-block-uuid->remote-ops @conn block-ops-map-coll))]
-        (when-let [ops-for-remote (rtc-schema/to-ws-ops-decoder
-                                   (sort-remote-ops
-                                    block-uuid->remote-ops))]
-          (let [local-tx (client-op/get-local-tx repo)
-                r (try
-                    (m/? (ws-util/send&recv get-ws-create-task
-                                            (cond-> {:action "apply-ops"
-                                                     :graph-uuid graph-uuid :schema-version (str major-schema-version)
-                                                     :ops ops-for-remote :t-before (or local-tx 1)}
-                                              (true? @*remote-profile?) (assoc :profile true))))
-                    (catch :default e
-                      (rollback repo block-ops-map-coll)
-                      (throw e)))]
-            (if-let [remote-ex (:ex-data r)]
-              (do (add-log-fn :rtc.log/push-local-update remote-ex)
-                  (case (:type remote-ex)
-                    ;; - :graph-lock-failed
-                    ;;   conflict-update remote-graph, keep these local-pending-ops
-                    ;;   and try to send ops later
-                    :graph-lock-failed
-                    (rollback repo block-ops-map-coll)
-                    ;; - :graph-lock-missing
-                    ;;   this case means something wrong in remote-graph data,
-                    ;;   nothing to do at client-side
-                    :graph-lock-missing
-                    (do (rollback repo block-ops-map-coll)
-                        (throw r.ex/ex-remote-graph-lock-missing))
+    (let [block-ops-map-coll (client-op/get&remove-all-block-ops repo)
+          db-ident-kv-ops-map-coll (client-op/get&remove-all-db-ident-kv-ops repo)
+          block-uuid->remote-ops (not-empty (gen-block-uuid->remote-ops @conn block-ops-map-coll))
+          other-remote-ops (gen-db-ident-kv-remote-ops db-ident-kv-ops-map-coll)
+          remote-ops (concat (when block-uuid->remote-ops (sort-remote-ops block-uuid->remote-ops))
+                             other-remote-ops)]
+      (when-let [ops-for-remote (rtc-schema/to-ws-ops-decoder remote-ops)]
+        (let [local-tx (client-op/get-local-tx repo)
+              r (try
+                  (m/? (ws-util/send&recv get-ws-create-task
+                                          (cond-> {:action "apply-ops"
+                                                   :graph-uuid graph-uuid :schema-version (str major-schema-version)
+                                                   :ops ops-for-remote :t-before (or local-tx 1)}
+                                            (true? @*remote-profile?) (assoc :profile true))))
+                  (catch :default e
+                    (rollback repo block-ops-map-coll db-ident-kv-ops-map-coll)
+                    (throw e)))]
+          (if-let [remote-ex (:ex-data r)]
+            (do (add-log-fn :rtc.log/push-local-update remote-ex)
+                (case (:type remote-ex)
+                  ;; - :graph-lock-failed
+                  ;;   conflict-update remote-graph, keep these local-pending-ops
+                  ;;   and try to send ops later
+                  :graph-lock-failed
+                  (rollback repo block-ops-map-coll db-ident-kv-ops-map-coll)
+                  ;; - :graph-lock-missing
+                  ;;   this case means something wrong in remote-graph data,
+                  ;;   nothing to do at client-side
+                  :graph-lock-missing
+                  (do (rollback repo block-ops-map-coll db-ident-kv-ops-map-coll)
+                      (throw r.ex/ex-remote-graph-lock-missing))
 
-                    :rtc.exception/get-s3-object-failed
-                    (rollback repo block-ops-map-coll)
-                    ;; else
-                    (do (rollback repo block-ops-map-coll)
-                        (throw (ex-info "Unavailable1" {:remote-ex remote-ex})))))
+                  :rtc.exception/get-s3-object-failed
+                  (rollback repo block-ops-map-coll db-ident-kv-ops-map-coll)
+                  ;; else
+                  (do (rollback repo block-ops-map-coll db-ident-kv-ops-map-coll)
+                      (throw (ex-info "Unavailable1" {:remote-ex remote-ex})))))
 
-              (do (assert (pos? (:t r)) r)
-                  (r.remote-update/apply-remote-update
-                   graph-uuid repo conn date-formatter {:type :remote-update :value r} add-log-fn)
-                  (add-log-fn :rtc.log/push-local-update {:remote-t (:t r)})))))))))
+            (do (assert (pos? (:t r)) r)
+                (r.remote-update/apply-remote-update
+                 graph-uuid repo conn date-formatter {:type :remote-update :value r} add-log-fn)
+                (add-log-fn :rtc.log/push-local-update {:remote-t (:t r)}))))))))
 
 (defn new-task--pull-remote-data
   [repo conn graph-uuid major-schema-version date-formatter get-ws-create-task add-log-fn]

+ 112 - 10
src/main/frontend/worker/rtc/client_op.cljs

@@ -12,6 +12,14 @@
 
 (def op-schema
   [:multi {:dispatch first}
+   [:update-kv-value
+    ;; update :logseq.kv/xxx entities
+    [:catn
+     [:op :keyword]
+     [:t :int]
+     [:value [:map
+              [:db-ident :keyword]
+              [:value :any]]]]]
    [:move
     [:catn
      [:op :keyword]
@@ -60,15 +68,17 @@
 (def ops-schema [:sequential op-schema])
 (def ops-coercer (ma/coercer ops-schema mt/json-transformer nil
                              #(do (log/error ::bad-ops (:value %))
-                                  (ma/-fail! ::ops-schema %))))
+                                  (ma/-fail! ::ops-schema (select-keys % [:value])))))
 
 (def ^:private block-op-types #{:move :remove :update-page :remove-page :update})
 (def ^:private asset-op-types #{:update-asset :remove-asset})
+(def ^:private db-ident-kv-op-types #{:update-kv-value})
 
 (def schema-in-db
   "TODO: rename this db-name from client-op to client-metadata+op.
   and move it to its own namespace."
   {:block/uuid {:db/unique :db.unique/identity}
+   :db-ident {:db/unique :db.unique/identity}
    :local-tx {:db/index true}
    :graph-uuid {:db/index true}
    :aes-key-jwk {:db/index true}
@@ -190,13 +200,66 @@
            (cons [:db/add tmpid :block/uuid block-uuid] tx-data))))
      block-uuid->op-type->op)))
 
-(defn add-ops
+(defn- generate-ident-kv-ops-tx-data
+  [client-ops-db ops]
+  (let [sorted-ops (sort-by second ops)
+        db-idents (map (fn [[_op-type _t value]] (:db-ident value)) sorted-ops)
+        ents (d/pull-many client-ops-db '[*] (map (fn [db-ident] [:db-ident db-ident]) db-idents))
+        op-types [:update-kv-value]
+        init-db-ident->op-type->op
+        (into {}
+              (map (fn [ent]
+                     [(:db-ident ent)
+                      (into {}
+                            (keep
+                             (fn [op-type]
+                               (when-let [op (get ent op-type)]
+                                 [op-type op])))
+                            op-types)]))
+              ents)
+        db-ident->op-type->op
+        (reduce
+         (fn [r op]
+           (let [[op-type _t value] op
+                 db-ident (:db-ident value)]
+             (case op-type
+               :update-kv-value
+               (assoc-in r [db-ident :update-kv-value] op))))
+         init-db-ident->op-type->op sorted-ops)]
+    (mapcat
+     (fn [[db-ident op-type->op]]
+       (let [tmpid (str db-ident)]
+         (when-let [tx-data (not-empty
+                             (keep
+                              (fn [[op-type op]]
+                                (when op [:db/add tmpid op-type op]))
+                              op-type->op))]
+           (cons [:db/add tmpid :db-ident db-ident] tx-data))))
+     db-ident->op-type->op)))
+
+(defn- partition-ops
+  "Return [db-ident-kv-ops block-ops]"
+  [ops]
+  ((juxt :db-ident :block-uuid)
+   (group-by
+    (fn [[_op-type _t value :as op]]
+      (cond
+        (:block-uuid value) :block-uuid
+        (:db-ident value) :db-ident
+        :else (throw (ex-info "invalid op" {:op op}))))
+    ops)))
+
+(defn add-ops!
   [repo ops]
-  (let [conn (worker-state/get-client-ops-conn repo)
-        ops (ops-coercer ops)]
-    (assert (some? conn) repo)
-    (when-let [tx-data (not-empty (generate-block-ops-tx-data @conn ops))]
-      (d/transact! conn tx-data))))
+  (when (seq ops)
+    (let [conn (worker-state/get-client-ops-conn repo)
+          ops (ops-coercer ops)
+          _ (assert (some? conn) repo)
+          [db-ident-kv-ops block-ops] (partition-ops ops)
+          tx-data1 (when (seq block-ops) (generate-block-ops-tx-data @conn block-ops))
+          tx-data2 (when (seq db-ident-kv-ops) (generate-ident-kv-ops-tx-data @conn db-ident-kv-ops))]
+      (when-let [tx-data (not-empty (concat tx-data1 tx-data2))]
+        (d/transact! conn tx-data)))))
 
 (defn- get-all-block-ops*
   "Return e->op-map"
@@ -207,7 +270,8 @@
                (let [op-map (into {}
                                   (keep (fn [datom]
                                           (let [a (:a datom)]
-                                            (when (or (= :block/uuid a) (contains? block-op-types a))
+                                            (when (or (keyword-identical? :block/uuid a)
+                                                      (contains? block-op-types a))
                                               [a (:v datom)]))))
                                   datoms)]
                  (when (and (:block/uuid op-map)
@@ -216,7 +280,26 @@
                    [e op-map]))))
        (into {})))
 
-(defn get&remove-all-block-ops*
+(defn- get-all-db-ident-kv-ops*
+  "Return e->op-map"
+  [db]
+  (let [db-ident-datoms (d/datoms db :avet :db-ident)
+        es (map :e db-ident-datoms)]
+    (->> (map (fn [e] [e (d/datoms db :eavt e)]) es)
+         (keep (fn [[e datoms]]
+                 (let [op-map (into {}
+                                    (keep (fn [datom]
+                                            (let [a (:a datom)]
+                                              (when (or (keyword-identical? :db-ident a)
+                                                        (contains? db-ident-kv-op-types a))
+                                                [a (:v datom)]))))
+                                    datoms)]
+                   (when (and (:db-ident op-map)
+                              (> (count op-map) 1))
+                     [e op-map]))))
+         (into {}))))
+
+(defn- get&remove-all-block-ops*
   [conn]
   (let [e->op-map (get-all-block-ops* @conn)
         retract-all-tx-data (mapcat (fn [e] (map (fn [a] [:db.fn/retractAttribute e a]) block-op-types))
@@ -224,6 +307,14 @@
     (d/transact! conn retract-all-tx-data)
     (vals e->op-map)))
 
+(defn- get&remove-all-db-ident-kv-ops*
+  [conn]
+  (let [e->op-map (get-all-db-ident-kv-ops* @conn)
+        retract-all-tx-data (mapcat (fn [e] (map (fn [a] [:db.fn/retractAttribute e a]) db-ident-kv-op-types))
+                                    (keys e->op-map))]
+    (d/transact! conn retract-all-tx-data)
+    (vals e->op-map)))
+
 (defn get-all-block-ops
   [repo]
   (when-let [conn (worker-state/get-client-ops-conn repo)]
@@ -234,6 +325,12 @@
              m))
      (vals (get-all-block-ops* @conn)))))
 
+(comment
+  (defn get-all-db-ident-kv-ops
+    [repo]
+    (when-let [conn (worker-state/get-client-ops-conn repo)]
+      (get-all-db-ident-kv-ops* @conn))))
+
 (defn get&remove-all-block-ops
   "Return coll of
   {:block/uuid ...
@@ -244,6 +341,11 @@
   (when-let [conn (worker-state/get-client-ops-conn repo)]
     (get&remove-all-block-ops* conn)))
 
+(defn get&remove-all-db-ident-kv-ops
+  [repo]
+  (when-let [conn (worker-state/get-client-ops-conn repo)]
+    (get&remove-all-db-ident-kv-ops* conn)))
+
 (defn get-unpushed-block-ops-count
   [repo]
   (when-let [conn (worker-state/get-client-ops-conn repo)]
@@ -327,7 +429,7 @@
                (let [op-map (into {}
                                   (keep (fn [datom]
                                           (let [a (:a datom)]
-                                            (when (or (= :block/uuid a) (contains? asset-op-types a))
+                                            (when (or (keyword-identical? :block/uuid a) (contains? asset-op-types a))
                                               [a (:v datom)]))))
                                   datoms)]
                  (when (and (:block/uuid op-map)

+ 25 - 3
src/main/frontend/worker/rtc/core.cljs

@@ -11,6 +11,7 @@
             [frontend.worker.rtc.exception :as r.ex]
             [frontend.worker.rtc.full-upload-download-graph :as r.upload-download]
             [frontend.worker.rtc.log-and-state :as rtc-log-and-state]
+            [frontend.worker.rtc.migrate :as r.migrate]
             [frontend.worker.rtc.remote-update :as r.remote-update]
             [frontend.worker.rtc.skeleton]
             [frontend.worker.rtc.ws :as ws]
@@ -157,6 +158,24 @@
        ws-state (assoc :ws-state ws-state)))
    (m/reductions {} nil ws-state-flow)))
 
+(defn- add-migration-client-ops!
+  [repo db server-schema-version]
+  (when server-schema-version
+    (let [client-schema-version (ldb/get-graph-schema-version db)
+          added-ops (r.migrate/add-migration-client-ops! repo db server-schema-version client-schema-version)]
+      (when (seq added-ops)
+        (log/info :add-migration-client-ops
+                  {:repo repo
+                   :server-schema-version server-schema-version
+                   :client-schema-version client-schema-version})))))
+
+(defn- update-remote-schema-version!
+  [conn server-schema-version]
+  (when server-schema-version
+    (d/transact! conn [(ldb/kv :logseq.kv/remote-schema-version server-schema-version)]
+                 {:gen-undo-ops? false
+                  :persist-op? false})))
+
 (defonce ^:private *rtc-lock (atom nil))
 (defn- holding-rtc-lock
   "Use this fn to prevent multiple rtc-loops at same time.
@@ -188,15 +207,16 @@
         *last-calibrate-t          (atom nil)
         *online-users              (atom nil)
         *assets-sync-loop-canceler (atom nil)
+        *server-schema-version     (atom nil)
         started-dfv                (m/dfv)
         add-log-fn                 (fn [type message]
                                      (assert (map? message) message)
                                      (rtc-log-and-state/rtc-log type (assoc message :graph-uuid graph-uuid)))
         {:keys [*current-ws get-ws-create-task]}
         (gen-get-ws-create-map--memoized ws-url)
-        get-ws-create-task         (r.client/ensure-register-graph-updates
-                                    get-ws-create-task graph-uuid major-schema-version
-                                    repo conn *last-calibrate-t *online-users add-log-fn)
+        get-ws-create-task (r.client/ensure-register-graph-updates
+                            get-ws-create-task graph-uuid major-schema-version
+                            repo conn *last-calibrate-t *online-users *server-schema-version add-log-fn)
         {:keys [assets-sync-loop-task]}
         (r.asset/create-assets-sync-loop repo get-ws-create-task graph-uuid major-schema-version conn *auto-push?)
         mixed-flow                 (create-mixed-flow repo get-ws-create-task *auto-push? *online-users)]
@@ -214,6 +234,8 @@
           ;; init run to open a ws
           (m/? get-ws-create-task)
           (started-dfv true)
+          (update-remote-schema-version! conn @*server-schema-version)
+          (add-migration-client-ops! repo @conn @*server-schema-version)
           (reset! *assets-sync-loop-canceler
                   (c.m/run-task assets-sync-loop-task :assets-sync-loop-task))
           (->>

+ 6 - 138
src/main/frontend/worker/rtc/db_listener.cljs

@@ -1,148 +1,14 @@
 (ns frontend.worker.rtc.db-listener
   "listen datascript changes, infer operations from the db tx-report"
-  (:require [clojure.string :as string]
-            [datascript.core :as d]
-            [frontend.worker.db-listener :as db-listener]
+  (:require [frontend.worker.db-listener :as db-listener]
             [frontend.worker.rtc.client-op :as client-op]
-            [frontend.worker.rtc.const :as rtc-const]
-            [logseq.db :as ldb]
-            [logseq.db.frontend.property :as db-property]))
-
-(defn- latest-add?->v->t
-  [add?->v->t]
-  (let [latest-add     (first (sort-by second > (seq (add?->v->t true))))
-        latest-retract (first (sort-by second > (seq (add?->v->t false))))]
-    (cond
-      (nil? latest-add)                               {false (conj {} latest-retract)}
-      (nil? latest-retract)                           {true (conj {} latest-add)}
-      (= (second latest-add) (second latest-retract)) {true (conj {} latest-add)
-                                                       false (conj {} latest-retract)}
-      (> (second latest-add) (second latest-retract)) {true (conj {} latest-add)}
-      :else                                           {false (conj {} latest-retract)})))
-
-(def ^:private watched-attrs
-  #{:block/title :block/created-at :block/updated-at :block/alias
-    :block/tags :block/link :block/journal-day
-    :logseq.property/classes :logseq.property/value
-    :db/index :db/valueType :db/cardinality})
-
-(def ^:private watched-attr-ns
-  (conj db-property/logseq-property-namespaces "logseq.class"))
-
-(defn- watched-attr?
-  [attr]
-  (or (contains? watched-attrs attr)
-      (let [ns (namespace attr)]
-        (or (contains? watched-attr-ns ns)
-            (string/ends-with? ns ".property")
-            (string/ends-with? ns ".class")))))
-
-(defn- ref-attr?
-  [db attr]
-  (= :db.type/ref (get-in (d/schema db) [attr :db/valueType])))
-
-(defn- update-op-av-coll
-  [db-before db-after a->add?->v->t]
-  (mapcat
-   (fn [[a add?->v->t]]
-     (mapcat
-      (fn [[add? v->t]]
-        (keep
-         (fn [[v t]]
-           (let [ref? (ref-attr? db-after a)]
-             (case [add? ref?]
-               [true true]
-               (when-let [v-uuid (:block/uuid (d/entity db-after v))]
-                 [a v-uuid t add?])
-               [false true]
-               (when-let [v-uuid (:block/uuid
-                                  (or (d/entity db-after v)
-                                      (d/entity db-before v)))]
-                 [a v-uuid t add?])
-               ([true false] [false false]) [a (ldb/write-transit-str v) t add?])))
-         v->t))
-      add?->v->t))
-   a->add?->v->t))
-
-(defn- redundant-update-op-av-coll?
-  [av-coll]
-  (every? (fn [av] (keyword-identical? :block/updated-at (first av))) av-coll))
-
-(defn- max-t
-  [a->add?->v->t]
-  (apply max (mapcat vals (mapcat vals (vals a->add?->v->t)))))
-
-(defn- get-first-vt
-  [add?->v->t k]
-  (some-> add?->v->t (get k) first))
-
-(defn- entity-datoms=>ops
-  [db-before db-after e->a->add?->v->t ignore-attr-set entity-datoms]
-  (let [e                        (ffirst entity-datoms)
-        entity                   (d/entity db-after e)
-        {block-uuid :block/uuid} entity
-        a->add?->v->t            (e->a->add?->v->t e)
-        {add?->block-name->t   :block/name
-         add?->block-title->t  :block/title
-         add?->block-uuid->t   :block/uuid
-         add?->block-parent->t :block/parent
-         add?->block-order->t  :block/order}
-        a->add?->v->t
-        [retract-block-uuid t1]  (some-> add?->block-uuid->t (get false) first)
-        [retract-block-name _]   (some-> add?->block-name->t (get false) first)
-        [add-block-name t2]      (some-> add?->block-name->t latest-add?->v->t (get-first-vt true))
-        [add-block-title t3]     (some-> add?->block-title->t latest-add?->v->t (get-first-vt true))
-        [add-block-parent t4]    (some-> add?->block-parent->t latest-add?->v->t (get-first-vt true))
-        [add-block-order t5]     (some-> add?->block-order->t latest-add?->v->t (get-first-vt true))
-        a->add?->v->t*           (into {}
-                                       (filter
-                                        (fn [[a _]]
-                                          (and (watched-attr? a)
-                                               (not (contains? ignore-attr-set a)))))
-                                       a->add?->v->t)]
-    (cond
-      (and retract-block-uuid retract-block-name)
-      [[:remove-page t1 {:block-uuid retract-block-uuid}]]
-
-      retract-block-uuid
-      [[:remove t1 {:block-uuid retract-block-uuid}]]
-
-      :else
-      (let [ops (cond-> []
-                  (or add-block-parent add-block-order)
-                  (conj [:move (or t4 t5) {:block-uuid block-uuid}])
-
-                  (or add-block-name
-                      (and (ldb/page? entity) add-block-title))
-                  (conj [:update-page (or t2 t3) {:block-uuid block-uuid}]))
-            update-op (when-let [av-coll (not-empty (update-op-av-coll db-before db-after a->add?->v->t*))]
-                        (when-not (redundant-update-op-av-coll? av-coll)
-                          (let [t (max-t a->add?->v->t*)]
-                            [:update t {:block-uuid block-uuid :av-coll av-coll}])))]
-        (cond-> ops update-op (conj update-op))))))
-
-(defn- generate-rtc-ops
-  [repo db-before db-after same-entity-datoms-coll e->a->v->add?->t]
-  (let [ops (mapcat
-             (partial entity-datoms=>ops
-                      db-before db-after e->a->v->add?->t rtc-const/ignore-attrs-when-syncing)
-             same-entity-datoms-coll)]
-    (when (seq ops)
-      (client-op/add-ops repo ops))))
+            [frontend.worker.rtc.gen-client-op :as gen-client-op]))
 
 (comment
   ;; TODO: make it a qualified-keyword
   (defkeywords
     :persist-op? {:doc "tx-meta option, generate rtc ops when not nil (default true)"}))
 
-(defn- entity-datoms=>a->add?->v->t
-  [entity-datoms]
-  (reduce
-   (fn [m datom]
-     (let [[_e a v t add?] datom]
-       (assoc-in m [a add? v] t)))
-   {} entity-datoms))
-
 (defmethod db-listener/listen-db-changes :gen-rtc-ops
   [_
    {:keys [repo same-entity-datoms-coll id->same-entity-datoms]}
@@ -151,5 +17,7 @@
              (:persist-op? tx-meta true))
     (let [e->a->add?->v->t (update-vals
                             id->same-entity-datoms
-                            entity-datoms=>a->add?->v->t)]
-      (generate-rtc-ops repo db-before db-after same-entity-datoms-coll e->a->add?->v->t))))
+                            gen-client-op/entity-datoms=>a->add?->v->t)
+          ops (gen-client-op/generate-rtc-ops db-before db-after same-entity-datoms-coll e->a->add?->v->t)]
+      (when (seq ops)
+        (client-op/add-ops! repo ops)))))

+ 160 - 0
src/main/frontend/worker/rtc/gen_client_op.cljs

@@ -0,0 +1,160 @@
+(ns frontend.worker.rtc.gen-client-op
+  "Generate client-ops from entities/datoms"
+  (:require [clojure.string :as string]
+            [datascript.core :as d]
+            [frontend.worker.rtc.const :as rtc-const]
+            [logseq.db :as ldb]
+            [logseq.db.frontend.property :as db-property]))
+
+(defn- latest-add?->v->t
+  [add?->v->t]
+  (let [latest-add     (first (sort-by second > (seq (add?->v->t true))))
+        latest-retract (first (sort-by second > (seq (add?->v->t false))))]
+    (cond
+      (nil? latest-add)                               {false (conj {} latest-retract)}
+      (nil? latest-retract)                           {true (conj {} latest-add)}
+      (= (second latest-add) (second latest-retract)) {true (conj {} latest-add)
+                                                       false (conj {} latest-retract)}
+      (> (second latest-add) (second latest-retract)) {true (conj {} latest-add)}
+      :else                                           {false (conj {} latest-retract)})))
+
+(def ^:private watched-attrs
+  #{:block/title :block/created-at :block/updated-at :block/alias
+    :block/tags :block/link :block/journal-day
+    :logseq.property/classes :logseq.property/value
+    :db/index :db/valueType :db/cardinality})
+
+(def ^:private watched-attr-ns
+  (conj db-property/logseq-property-namespaces "logseq.class"))
+
+(defn- watched-attr?
+  [attr]
+  (or (contains? watched-attrs attr)
+      (let [ns (namespace attr)]
+        (or (contains? watched-attr-ns ns)
+            (string/ends-with? ns ".property")
+            (string/ends-with? ns ".class")))))
+
+(defn- ref-attr?
+  [db attr]
+  (= :db.type/ref (get-in (d/schema db) [attr :db/valueType])))
+
+(defn- update-op-av-coll
+  [db-before db-after a->add?->v->t]
+  (mapcat
+   (fn [[a add?->v->t]]
+     (mapcat
+      (fn [[add? v->t]]
+        (keep
+         (fn [[v t]]
+           (let [ref? (ref-attr? db-after a)]
+             (case [add? ref?]
+               [true true]
+               (when-let [v-uuid (:block/uuid (d/entity db-after v))]
+                 [a v-uuid t add?])
+               [false true]
+               (when-let [v-uuid (:block/uuid
+                                  (or (d/entity db-after v)
+                                      (d/entity db-before v)))]
+                 [a v-uuid t add?])
+               ([true false] [false false]) [a (ldb/write-transit-str v) t add?])))
+         v->t))
+      add?->v->t))
+   a->add?->v->t))
+
+(defn- redundant-update-op-av-coll?
+  [av-coll]
+  (every? (fn [av] (keyword-identical? :block/updated-at (first av))) av-coll))
+
+(defn- max-t
+  [a->add?->v->t]
+  (apply max (mapcat vals (mapcat vals (vals a->add?->v->t)))))
+
+(defn- get-first-vt
+  [add?->v->t k]
+  (some-> add?->v->t (get k) first))
+
+(defn- entity-datoms=>ops
+  [db-before db-after e->a->add?->v->t ignore-attr-set entity-datoms]
+  (let [e                        (ffirst entity-datoms)
+        entity                   (d/entity db-after e)
+        {block-uuid :block/uuid} entity
+        a->add?->v->t            (e->a->add?->v->t e)
+        {add?->block-name->t   :block/name
+         add?->block-title->t  :block/title
+         add?->block-uuid->t   :block/uuid
+         add?->block-parent->t :block/parent
+         add?->block-order->t  :block/order}
+        a->add?->v->t
+        [retract-block-uuid t1]  (some-> add?->block-uuid->t (get false) first)
+        [retract-block-name _]   (some-> add?->block-name->t (get false) first)
+        [add-block-name t2]      (some-> add?->block-name->t latest-add?->v->t (get-first-vt true))
+        [add-block-title t3]     (some-> add?->block-title->t latest-add?->v->t (get-first-vt true))
+        [add-block-parent t4]    (some-> add?->block-parent->t latest-add?->v->t (get-first-vt true))
+        [add-block-order t5]     (some-> add?->block-order->t latest-add?->v->t (get-first-vt true))
+        a->add?->v->t*           (into {}
+                                       (filter
+                                        (fn [[a _]]
+                                          (and (watched-attr? a)
+                                               (not (contains? ignore-attr-set a)))))
+                                       a->add?->v->t)]
+    (cond
+      (and retract-block-uuid retract-block-name)
+      [[:remove-page t1 {:block-uuid retract-block-uuid}]]
+
+      retract-block-uuid
+      [[:remove t1 {:block-uuid retract-block-uuid}]]
+
+      :else
+      (let [ops (cond-> []
+                  (or add-block-parent add-block-order)
+                  (conj [:move (or t4 t5) {:block-uuid block-uuid}])
+
+                  (or add-block-name
+                      (and (ldb/page? entity) add-block-title))
+                  (conj [:update-page (or t2 t3) {:block-uuid block-uuid}]))
+            update-op (when-let [av-coll (not-empty (update-op-av-coll db-before db-after a->add?->v->t*))]
+                        (when-not (redundant-update-op-av-coll? av-coll)
+                          (let [t (max-t a->add?->v->t*)]
+                            [:update t {:block-uuid block-uuid :av-coll av-coll}])))]
+        (cond-> ops update-op (conj update-op))))))
+
+(defn entity-datoms=>a->add?->v->t
+  [entity-datoms]
+  (reduce
+   (fn [m datom]
+     (let [[_e a v t add?] datom]
+       (assoc-in m [a add? v] t)))
+   {} entity-datoms))
+
+(defn generate-rtc-ops
+  [db-before db-after same-entity-datoms-coll e->a->v->add?->t]
+  (mapcat
+   (partial entity-datoms=>ops
+            db-before db-after e->a->v->add?->t rtc-const/ignore-attrs-when-syncing)
+   same-entity-datoms-coll))
+
+(defn- generate-rtc-ops-from-entities
+  [ents]
+  (let [db (d/entity-db (first ents))
+        id->same-entity-datoms
+        (into {}
+              (map (fn [ent]
+                     (let [e (:db/id ent)
+                           datoms (d/datoms db :eavt e)]
+                       [e datoms])))
+              ents)
+        e->a->v->add?->t (update-vals id->same-entity-datoms entity-datoms=>a->add?->v->t)]
+    (generate-rtc-ops db db (vals id->same-entity-datoms) e->a->v->add?->t)))
+
+(defn generate-rtc-ops-from-property-entities
+  [property-ents]
+  (when (seq property-ents)
+    (assert (every? ldb/property? property-ents))
+    (generate-rtc-ops-from-entities property-ents)))
+
+(defn generate-rtc-ops-from-class-entities
+  [class-ents]
+  (when (seq class-ents)
+    (assert (every? ldb/class? class-ents))
+    (generate-rtc-ops-from-entities class-ents)))

+ 6 - 1
src/main/frontend/worker/rtc/malli_schema.cljs

@@ -25,6 +25,11 @@
 
 (def to-ws-op-schema
   [:multi {:dispatch first :decode/string #(update % 0 keyword)}
+   [:update-kv-value
+    [:cat :keyword
+     [:map
+      [:db-ident :keyword]
+      [:value :string]]]]
    [:move
     [:cat :keyword
      [:map
@@ -356,4 +361,4 @@
 (def data-to-ws-coercer (m/coercer data-to-ws-schema mt/string-transformer nil
                                    #(do
                                       (log/error ::data-to-ws-schema %)
-                                      (m/-fail! ::data-to-ws-schema %))))
+                                      (m/-fail! ::data-to-ws-schema (select-keys % [:value])))))

+ 49 - 0
src/main/frontend/worker/rtc/migrate.cljs

@@ -0,0 +1,49 @@
+(ns frontend.worker.rtc.migrate
+  "migrate server data according to schema-version and client's migration-updates"
+  (:require [datascript.core :as d]
+            [frontend.worker.db.migrate :as db-migrate]
+            [frontend.worker.rtc.client-op :as client-op]
+            [frontend.worker.rtc.gen-client-op :as gen-client-op]
+            [logseq.db.frontend.schema :as db-schema]))
+
+(defn- server-client-schema-version->migrations
+  [server-schema-version client-schema-version]
+  (when (neg? (db-schema/compare-schema-version server-schema-version client-schema-version))
+    (let [sorted-schema-version->updates
+          (->> (map (fn [[schema-version updates]]
+                      [((juxt :major :minor) (db-schema/parse-schema-version schema-version))
+                       updates])
+                    db-migrate/schema-version->updates)
+               (sort-by first))]
+      (->> sorted-schema-version->updates
+           (drop-while (fn [[schema-version _updates]]
+                         (not (neg? (db-schema/compare-schema-version server-schema-version schema-version)))))
+           (take-while (fn [[schema-version _updates]]
+                         (not (neg? (db-schema/compare-schema-version client-schema-version schema-version)))))
+           (map second)))))
+
+(defn- migration-updates->client-ops
+  "convert :classes, :properties from frontend.worker.db.migrate/schema-version->updates into client-ops"
+  [db client-schema-version migrate-updates]
+  (let [property-ks (mapcat :properties migrate-updates)
+        class-ks (mapcat :classes migrate-updates)
+        d-entity-fn (partial d/entity db)
+        new-property-entities (keep d-entity-fn property-ks)
+        new-class-entities (keep d-entity-fn class-ks)
+        client-ops (vec (concat (gen-client-op/generate-rtc-ops-from-property-entities new-property-entities)
+                                (gen-client-op/generate-rtc-ops-from-class-entities new-class-entities)))
+        max-t (apply max 0 (map second client-ops))]
+    (conj client-ops
+          [:update-kv-value
+           max-t
+           {:db-ident :logseq.kv/schema-version
+            :value client-schema-version}])))
+
+(defn add-migration-client-ops!
+  [repo db server-schema-version client-schema-version]
+  (assert (and server-schema-version client-schema-version))
+  (when-let [ops (not-empty
+                  (some->> (server-client-schema-version->migrations server-schema-version client-schema-version)
+                           (migration-updates->client-ops db client-schema-version)))]
+    (client-op/add-ops! repo ops)
+    ops))

+ 3 - 3
src/main/frontend/worker/rtc/remote_update.cljs

@@ -317,10 +317,10 @@ so need to pull earlier remote-data from websocket."})
 
 (defn- affected-blocks->diff-type-ops
   [repo affected-blocks]
-  (let [unpushed-ops (client-op/get-all-block-ops repo)
-        affected-blocks-map* (if unpushed-ops
+  (let [unpushed-block-ops (client-op/get-all-block-ops repo)
+        affected-blocks-map* (if unpushed-block-ops
                                (update-remote-data-by-local-unpushed-ops
-                                affected-blocks unpushed-ops)
+                                affected-blocks unpushed-block-ops)
                                affected-blocks)
         {remove-ops-map :remove move-ops-map :move update-ops-map :update-attrs
          move+update-ops-map :move+update-attrs

+ 2 - 1
src/main/frontend/worker/rtc/skeleton.cljs

@@ -51,4 +51,5 @@
                                            (conj [:p (str :client-only-db-idents client-only)])
                                            (seq server-only)
                                            (conj [:p (str :server-only-db-idents server-only)]))
-                                         :error]))))))))
+                                         :error])))
+          r)))))

+ 2 - 2
src/resources/dicts/zh-cn.edn

@@ -534,8 +534,8 @@
  :command.editor/delete                   "向右删除"
  :command.editor/cycle-todo               "切换TODO状态"
  :command.editor/clear-block              "清除块内容"
- :command.editor/kill-line-before         "删除光标侧行"
- :command.editor/kill-line-after          "删除光标侧行"
+ :command.editor/kill-line-before         "删除光标侧行"
+ :command.editor/kill-line-after          "删除光标侧行"
  :command.editor/beginning-of-block       "移动光标到块开始位置"
  :command.editor/end-of-block             "移动光标到块末尾"
  :command.editor/forward-word             "光标向后移动一个单词"

+ 20 - 1
src/test/frontend/worker/rtc/client_test.cljs

@@ -40,7 +40,25 @@
   (testing "user.property/xxx creation"
     (let [block-uuid (random-uuid)
           block-order "b0P"
-          db (d/db-with empty-db [{:db/index true
+          db (d/db-with empty-db [{:block/uuid #uuid "00000002-5389-0208-3000-000000000000",
+                                   :block/updated-at 1741424828774,
+                                   :block/created-at 1741424828774,
+                                   :logseq.property/built-in? true,
+                                   :block/tags [2],
+                                   :block/title "Tag",
+                                   :db/id 2,
+                                   :db/ident :logseq.class/Tag,
+                                   :block/name "tag"}
+                                  {:block/uuid #uuid "00000002-1038-7670-4800-000000000000",
+                                   :block/updated-at 1741424828774,
+                                   :block/created-at 1741424828774,
+                                   :logseq.property/built-in? true,
+                                   :block/tags [2]
+                                   :block/title "Property",
+                                   :db/id 3,
+                                   :db/ident :logseq.class/Property,
+                                   :block/name "property"}
+                                  {:db/index true
                                    :block/uuid block-uuid
                                    :db/valueType :db.type/ref
                                    :block/updated-at 1716880036491
@@ -48,6 +66,7 @@
                                    :logseq.property/type :number
                                    :db/cardinality :db.cardinality/one
                                    :db/ident :user.property/xxx,
+                                   :block/tags [3]
                                    :block/type "property",
                                    :block/order block-order,
                                    :block/name "xxx",

+ 1 - 0
src/test/frontend/worker/rtc/fixture.cljs

@@ -4,6 +4,7 @@
             [frontend.test.helper :as test-helper]
             [frontend.worker.db-listener :as worker-db-listener]
             [frontend.worker.rtc.client-op :as client-op]
+            [frontend.worker.rtc.db-listener]
             [frontend.worker.state :as worker-state]))
 
 (def listen-test-db-to-gen-rtc-ops-fixture

+ 37 - 3
src/test/frontend/worker/rtc/db_listener_test.cljs → src/test/frontend/worker/rtc/gen_client_op_test.cljs

@@ -1,17 +1,19 @@
-(ns frontend.worker.rtc.db-listener-test
+(ns frontend.worker.rtc.gen-client-op-test
   (:require [cljs.test :as t :refer [deftest is testing]]
+            [clojure.set :as set]
             [datascript.core :as d]
             [frontend.db.conn :as conn]
             [frontend.state :as state]
             [frontend.test.helper :as test-helper]
             [frontend.worker.handler.page :as worker-page]
             [frontend.worker.rtc.client-op :as client-op]
-            [frontend.worker.rtc.db-listener :as subject]
             [frontend.worker.rtc.fixture :as r.fixture]
+            [frontend.worker.rtc.gen-client-op :as subject]
             [frontend.worker.state :as worker-state]
             [logseq.db.test.helper :as db-test]
             [logseq.outliner.batch-tx :as batch-tx]
-            [logseq.outliner.core :as outliner-core]))
+            [logseq.outliner.core :as outliner-core]
+            [meander.epsilon :as me]))
 
 (t/use-fixtures :each
   test-helper/db-based-start-and-destroy-db-map-fixture
@@ -156,3 +158,35 @@
         (is (=
              {block-uuid1 #{:remove}}
              (ops-coll=>block-uuid->op-types (client-op/get&remove-all-block-ops repo))))))))
+
+(deftest generate-rtc-ops-from-property-entity-test
+  (let [repo (state/get-current-repo)
+        db (conn/get-db repo true)
+        ent (d/entity db :logseq.property.view/feature-type)
+        av-coll-attrs #{:logseq.property/type :logseq.property/built-in?
+                        :logseq.property/public? :logseq.property/hide?
+                        :block/tags :block/title :db/cardinality}]
+    #_{:clj-kondo/ignore [:unresolved-symbol :invalid-arity]}
+    (is (->> (me/find (subject/generate-rtc-ops-from-property-entities [ent])
+               ([:move _ {:block-uuid ?block-uuid}]
+                [:update-page _ {:block-uuid ?block-uuid}]
+                [:update _ {:block-uuid ?block-uuid :av-coll ([!av-coll-attrs . _ ...] ...)}])
+               !av-coll-attrs)
+             set
+             (set/difference av-coll-attrs)
+             empty?))))
+
+(deftest generate-rtc-ops-from-class-entity-test
+  (let [repo (state/get-current-repo)
+        db (conn/get-db repo true)
+        ent (d/entity db :logseq.class/Template)
+        av-coll-attrs #{:logseq.property.class/properties :logseq.property/built-in? :logseq.property/parent
+                        :block/tags :block/title}]
+    #_{:clj-kondo/ignore [:unresolved-symbol :invalid-arity]}
+    (is (->> (me/find (subject/generate-rtc-ops-from-class-entities [ent])
+               ([:update-page _ {:block-uuid ?block-uuid}]
+                [:update _ {:block-uuid ?block-uuid :av-coll ([!av-coll-attrs . _ ...] ...)}])
+               !av-coll-attrs)
+             set
+             (set/difference av-coll-attrs)
+             empty?))))

+ 152 - 109
static/yarn.lock

@@ -561,6 +561,13 @@
     wrap-ansi "^8.1.0"
     wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
 
+"@isaacs/fs-minipass@^4.0.0":
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32"
+  integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==
+  dependencies:
+    minipass "^7.0.4"
+
 "@logseq/[email protected]":
   version "0.0.91"
   resolved "https://registry.yarnpkg.com/@logseq/rsapi-darwin-arm64/-/rsapi-darwin-arm64-0.0.91.tgz#e29cd37a372609b04d7dcfc7ae4c1405c7a54353"
@@ -648,10 +655,10 @@
     "@nodelib/fs.scandir" "2.1.5"
     fastq "^1.6.0"
 
-"@npmcli/agent@^2.0.0":
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-2.2.2.tgz#967604918e62f620a648c7975461c9c9e74fc5d5"
-  integrity sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==
+"@npmcli/agent@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-3.0.0.tgz#1685b1fbd4a1b7bb4f930cbb68ce801edfe7aa44"
+  integrity sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==
   dependencies:
     agent-base "^7.1.0"
     http-proxy-agent "^7.0.0"
@@ -659,10 +666,10 @@
     lru-cache "^10.0.1"
     socks-proxy-agent "^8.0.3"
 
-"@npmcli/fs@^3.1.0":
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.1.tgz#59cdaa5adca95d135fc00f2bb53f5771575ce726"
-  integrity sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==
+"@npmcli/fs@^4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-4.0.0.tgz#a1eb1aeddefd2a4a347eca0fab30bc62c0e1c0f2"
+  integrity sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==
   dependencies:
     semver "^7.3.5"
 
@@ -845,12 +852,12 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.23.tgz#75c580983846181ebe5f4abc40fe9dfb2d65665f"
   integrity sha512-DWNcCHolDq0ZKGizjx2DZjR/PqsYwAcYUJmfMWqtVU2MBMG5Mo+xFZrhGId5r/O5HOuMPyQEcM6KUBp5lBZZBg==
 
-"@types/node@^20.9.0":
-  version "20.17.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.8.tgz#42748cdb169adf5be7c9760604c72820c7b7d560"
-  integrity sha512-ahz2g6/oqbKalW9sPv6L2iRbhLnojxjYWspAqhjvqSWBgGebEJT5GvRmk0QXPj3sbC6rU0GTQjPLQkmR8CObvA==
+"@types/node@^22.7.7":
+  version "22.13.10"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4"
+  integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==
   dependencies:
-    undici-types "~6.19.2"
+    undici-types "~6.20.0"
 
 "@types/plist@^3.0.1":
   version "3.0.2"
@@ -884,10 +891,10 @@
   resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99"
   integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
 
-abbrev@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf"
-  integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==
+abbrev@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-3.0.0.tgz#c29a6337e167ac61a84b41b80461b29c5c271a27"
+  integrity sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==
 
 [email protected]:
   version "3.0.0"
@@ -1351,12 +1358,12 @@ [email protected]:
     stat-mode "^1.0.0"
     temp-file "^3.4.0"
 
-cacache@^18.0.0:
-  version "18.0.4"
-  resolved "https://registry.yarnpkg.com/cacache/-/cacache-18.0.4.tgz#4601d7578dadb59c66044e157d02a3314682d6a5"
-  integrity sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==
+cacache@^19.0.1:
+  version "19.0.1"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-19.0.1.tgz#3370cc28a758434c85c2585008bd5bdcff17d6cd"
+  integrity sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==
   dependencies:
-    "@npmcli/fs" "^3.1.0"
+    "@npmcli/fs" "^4.0.0"
     fs-minipass "^3.0.0"
     glob "^10.2.2"
     lru-cache "^10.0.1"
@@ -1364,10 +1371,10 @@ cacache@^18.0.0:
     minipass-collect "^2.0.1"
     minipass-flush "^1.0.5"
     minipass-pipeline "^1.2.4"
-    p-map "^4.0.0"
-    ssri "^10.0.0"
-    tar "^6.1.11"
-    unique-filename "^3.0.0"
+    p-map "^7.0.2"
+    ssri "^12.0.0"
+    tar "^7.4.3"
+    unique-filename "^4.0.0"
 
 cacheable-lookup@^5.0.3:
   version "5.0.4"
@@ -1415,6 +1422,11 @@ chownr@^2.0.0:
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
   integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
 
+chownr@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4"
+  integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==
+
 chrome-trace-event@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
@@ -1997,13 +2009,13 @@ electron-wix-msi@^5.1.3:
   optionalDependencies:
     "@bitdisaster/exe-icon-extractor" "^1.0.10"
 
-electron@*, electron@31.7.5:
-  version "31.7.5"
-  resolved "https://registry.yarnpkg.com/electron/-/electron-31.7.5.tgz#98396c75041808b26c7b6a8844cbedacee93d8a6"
-  integrity sha512-8zFzVJdhxTRmoPcRiKkEmPW0bJHAUsTQJwEX2YJ8X0BVFIJLwSvHkSlpCjEExVbNCAk+gHnkIYX+2OyCXrRwHQ==
+electron@*, electron@35.0.1:
+  version "35.0.1"
+  resolved "https://registry.yarnpkg.com/electron/-/electron-35.0.1.tgz#8596e3936c4b5787e0cf52739bf6842925cfcdc1"
+  integrity sha512-iQonj6lnPhqfqha2KXx6LzV1dnu6UPTCWK+b7f9Zvg828umGemi22DKbcJ3/q+Opn7iUVTWyqp9z1JQqkIi6OA==
   dependencies:
     "@electron/get" "^2.0.0"
-    "@types/node" "^20.9.0"
+    "@types/node" "^22.7.7"
     extract-zip "^2.0.1"
 
 emoji-regex@^8.0.0:
@@ -2540,7 +2552,7 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
   dependencies:
     is-glob "^4.0.1"
 
-glob@^10.2.2, glob@^10.3.12:
+glob@^10.2.2, glob@^10.3.12, glob@^10.3.7:
   version "10.4.5"
   resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
   integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
@@ -2900,11 +2912,6 @@ is-interactive@^1.0.0:
   resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
   integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
 
-is-lambda@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
-  integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==
-
 is-my-ip-valid@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.1.tgz#f7220d1146257c98672e6fba097a9f3f2d348442"
@@ -3294,23 +3301,22 @@ macos-alias@~0.2.5:
   dependencies:
     nan "^2.4.0"
 
-make-fetch-happen@^13.0.0:
-  version "13.0.1"
-  resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz#273ba2f78f45e1f3a6dca91cede87d9fa4821e36"
-  integrity sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==
+make-fetch-happen@^14.0.3:
+  version "14.0.3"
+  resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz#d74c3ecb0028f08ab604011e0bc6baed483fcdcd"
+  integrity sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==
   dependencies:
-    "@npmcli/agent" "^2.0.0"
-    cacache "^18.0.0"
+    "@npmcli/agent" "^3.0.0"
+    cacache "^19.0.1"
     http-cache-semantics "^4.1.1"
-    is-lambda "^1.0.1"
     minipass "^7.0.2"
-    minipass-fetch "^3.0.0"
+    minipass-fetch "^4.0.0"
     minipass-flush "^1.0.5"
     minipass-pipeline "^1.2.4"
-    negotiator "^0.6.3"
-    proc-log "^4.2.0"
+    negotiator "^1.0.0"
+    proc-log "^5.0.0"
     promise-retry "^2.0.1"
-    ssri "^10.0.0"
+    ssri "^12.0.0"
 
 map-age-cleaner@^0.1.1:
   version "0.1.3"
@@ -3439,14 +3445,14 @@ minipass-collect@^2.0.1:
   dependencies:
     minipass "^7.0.3"
 
-minipass-fetch@^3.0.0:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.5.tgz#f0f97e40580affc4a35cc4a1349f05ae36cb1e4c"
-  integrity sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==
+minipass-fetch@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-4.0.1.tgz#f2d717d5a418ad0b1a7274f9b913515d3e78f9e5"
+  integrity sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==
   dependencies:
     minipass "^7.0.3"
     minipass-sized "^1.0.3"
-    minizlib "^2.1.2"
+    minizlib "^3.0.1"
   optionalDependencies:
     encoding "^0.1.13"
 
@@ -3488,12 +3494,12 @@ minipass@^5.0.0:
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
   integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
 
-minipass@^7.0.2, minipass@^7.0.3, minipass@^7.1.2:
+minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2:
   version "7.1.2"
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
   integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
 
-minizlib@^2.1.1, minizlib@^2.1.2:
+minizlib@^2.1.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
   integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
@@ -3501,6 +3507,14 @@ minizlib@^2.1.1, minizlib@^2.1.2:
     minipass "^3.0.0"
     yallist "^4.0.0"
 
+minizlib@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012"
+  integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==
+  dependencies:
+    minipass "^7.0.4"
+    rimraf "^5.0.5"
+
 mkdirp@^0.5.1:
   version "0.5.6"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
@@ -3513,6 +3527,11 @@ mkdirp@^1.0.3:
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 
+mkdirp@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
+  integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
+
 [email protected]:
   version "0.39.8"
   resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.39.8.tgz#9078cd8386081afd986cca34b52b5d84ea7a4d38"
@@ -3554,20 +3573,20 @@ nan@^2.4.0:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
   integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
 
-negotiator@^0.6.3:
-  version "0.6.3"
-  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
-  integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+negotiator@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a"
+  integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
 
 nice-try@^1.0.4:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
   integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 
-node-abi@3.68.0, node-abi@^3.0.0, node-abi@^3.45.0:
-  version "3.68.0"
-  resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.68.0.tgz#8f37fb02ecf4f43ebe694090dcb52e0c4cc4ba25"
-  integrity sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==
+node-abi@3.74.0, node-abi@^3.0.0, node-abi@^3.45.0:
+  version "3.74.0"
+  resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.74.0.tgz#5bfb4424264eaeb91432d2adb9da23c63a301ed0"
+  integrity sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==
   dependencies:
     semver "^7.3.5"
 
@@ -3612,28 +3631,28 @@ node-gyp-build@^4.2.1:
   resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40"
   integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==
 
-node-gyp@10.0.1, node-gyp@^9.0.0:
-  version "10.0.1"
-  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.0.1.tgz#205514fc19e5830fa991e4a689f9e81af377a966"
-  integrity sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==
+node-gyp@11.1.0, node-gyp@^9.0.0:
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-11.1.0.tgz#212a1d9c167c50d727d42659410780b40e07bbd3"
+  integrity sha512-/+7TuHKnBpnMvUQnsYEb0JOozDZqarQbfNuSGLXIjhStMT0fbw7IdSqWgopOP5xhRZE+lsbIvAHcekddruPZgQ==
   dependencies:
     env-paths "^2.2.0"
     exponential-backoff "^3.1.1"
     glob "^10.3.10"
     graceful-fs "^4.2.6"
-    make-fetch-happen "^13.0.0"
-    nopt "^7.0.0"
-    proc-log "^3.0.0"
+    make-fetch-happen "^14.0.3"
+    nopt "^8.0.0"
+    proc-log "^5.0.0"
     semver "^7.3.5"
-    tar "^6.1.2"
-    which "^4.0.0"
+    tar "^7.4.3"
+    which "^5.0.0"
 
-nopt@^7.0.0:
-  version "7.2.1"
-  resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7"
-  integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==
+nopt@^8.0.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-8.1.0.tgz#b11d38caf0f8643ce885818518064127f602eae3"
+  integrity sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==
   dependencies:
-    abbrev "^2.0.0"
+    abbrev "^3.0.0"
 
 normalize-package-data@^2.3.2:
   version "2.5.0"
@@ -3791,6 +3810,11 @@ p-map@^4.0.0:
   dependencies:
     aggregate-error "^3.0.0"
 
+p-map@^7.0.2:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.3.tgz#7ac210a2d36f81ec28b736134810f7ba4418cdb6"
+  integrity sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==
+
 p-try@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
@@ -3987,15 +4011,10 @@ postject@^1.0.0-alpha.6:
   dependencies:
     commander "^9.4.0"
 
-proc-log@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8"
-  integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==
-
-proc-log@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-4.2.0.tgz#b6f461e4026e75fdfe228b265e9f7a00779d7034"
-  integrity sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==
+proc-log@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8"
+  integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==
 
 process-nextick-args@~2.0.0:
   version "2.0.1"
@@ -4278,6 +4297,13 @@ rimraf@^3.0.0, rimraf@^3.0.2:
   dependencies:
     glob "^7.1.3"
 
+rimraf@^5.0.5:
+  version "5.0.10"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c"
+  integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==
+  dependencies:
+    glob "^10.3.7"
+
 rimraf@~2.6.2:
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
@@ -4599,10 +4625,10 @@ sprintf-js@^1.1.3:
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
   integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
 
-ssri@^10.0.0:
-  version "10.0.6"
-  resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.6.tgz#a8aade2de60ba2bce8688e3fa349bad05c7dc1e5"
-  integrity sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==
+ssri@^12.0.0:
+  version "12.0.0"
+  resolved "https://registry.yarnpkg.com/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832"
+  integrity sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==
   dependencies:
     minipass "^7.0.3"
 
@@ -4625,7 +4651,7 @@ stream-buffers@~2.2.0:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.1"
 
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3, string-width@^5.0.0, string-width@^5.1.2:
+[email protected], string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3, string-width@^5.0.0, string-width@^5.1.2:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
   integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
@@ -4703,7 +4729,7 @@ supports-preserve-symlinks-flag@^1.0.0:
   resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
 
-tar@^6.0.5, tar@^6.1.11, tar@^6.1.2:
+tar@^6.0.5, tar@^6.1.11:
   version "6.1.11"
   resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
   integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
@@ -4727,6 +4753,18 @@ tar@^6.1.12:
     mkdirp "^1.0.3"
     yallist "^4.0.0"
 
+tar@^7.4.3:
+  version "7.4.3"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571"
+  integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==
+  dependencies:
+    "@isaacs/fs-minipass" "^4.0.0"
+    chownr "^3.0.0"
+    minipass "^7.1.2"
+    minizlib "^3.0.1"
+    mkdirp "^3.0.1"
+    yallist "^5.0.0"
+
 temp-file@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.4.0.tgz#766ea28911c683996c248ef1a20eea04d51652c7"
@@ -4857,22 +4895,22 @@ typescript@^5.4.3:
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
   integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
 
-undici-types@~6.19.2:
-  version "6.19.8"
-  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
-  integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
+undici-types@~6.20.0:
+  version "6.20.0"
+  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
+  integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
 
-unique-filename@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea"
-  integrity sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==
+unique-filename@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-4.0.0.tgz#a06534d370e7c977a939cd1d11f7f0ab8f1fed13"
+  integrity sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==
   dependencies:
-    unique-slug "^4.0.0"
+    unique-slug "^5.0.0"
 
-unique-slug@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz#6bae6bb16be91351badd24cdce741f892a6532e3"
-  integrity sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==
+unique-slug@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-5.0.0.tgz#ca72af03ad0dbab4dad8aa683f633878b1accda8"
+  integrity sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==
   dependencies:
     imurmurhash "^0.1.4"
 
@@ -4994,10 +5032,10 @@ which@^2.0.1, which@^2.0.2:
   dependencies:
     isexe "^2.0.0"
 
-which@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a"
-  integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==
+which@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/which/-/which-5.0.0.tgz#d93f2d93f79834d4363c7d0c23e00d07c466c8d6"
+  integrity sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==
   dependencies:
     isexe "^3.1.1"
 
@@ -5054,6 +5092,11 @@ yallist@^4.0.0:
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
   integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
 
+yallist@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533"
+  integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==
+
 yargs-parser@^20.2.2:
   version "20.2.9"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"

+ 59 - 61
yarn.lock

@@ -818,12 +818,12 @@
     eventemitter3 "^3.1.0"
     url "^0.11.0"
 
-"@playwright/test@=1.44.0":
-  version "1.44.0"
-  resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.44.0.tgz#ac7a764b5ee6a80558bdc0fcbc525fcb81f83465"
-  integrity sha512-rNX5lbNidamSUorBhB4XZ9SQTjAqfe5M+p37Z8ic0jPFBMo5iCtQz1kRWkEMg+rYOKSlVycpQmpqjSFq7LXOfg==
+"@playwright/test@=1.51.0":
+  version "1.51.0"
+  resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.51.0.tgz#8d5c8400b465a0bfdbcf993e390ceecb903ea6d2"
+  integrity sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA==
   dependencies:
-    playwright "1.44.0"
+    playwright "1.51.0"
 
 "@popperjs/core@^2.11.5", "@popperjs/core@^2.9.0":
   version "2.11.8"
@@ -1082,19 +1082,12 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.5.tgz#4c6a79adf59a8e8193ac87a0e522605b16587258"
   integrity sha512-2qGq5LAOTh9izcc0+F+dToFigBWiK1phKPt7rNhOqJSr35y8rlIBjDwGtFSgAI6MGIhjwOVNSQZVdJsZJ2uR1w==
 
-"@types/node@>=10.0.0":
-  version "22.5.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.1.tgz#de01dce265f6b99ed32b295962045d10b5b99560"
-  integrity sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==
+"@types/node@>=10.0.0", "@types/node@^22.7.7":
+  version "22.13.10"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4"
+  integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==
   dependencies:
-    undici-types "~6.19.2"
-
-"@types/node@^20.9.0":
-  version "20.17.10"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.10.tgz#3f7166190aece19a0d1d364d75c8b0b5778c1e18"
-  integrity sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==
-  dependencies:
-    undici-types "~6.19.2"
+    undici-types "~6.20.0"
 
 "@types/normalize-package-data@^2.4.0":
   version "2.4.2"
@@ -2890,27 +2883,27 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
-electron-dl@3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/electron-dl/-/electron-dl-3.3.0.tgz#4e422e276c627373ba61fcf3f92ffa088988db1a"
-  integrity sha512-Zwaz/OMGPIfBLV2SQH4sTsdDOs/U4y5AOHfremMBXEpjIxX+SiTx845DZAvJJwgb5hfowyWOBLiJhd/emBNLLQ==
+electron-dl@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/electron-dl/-/electron-dl-4.0.0.tgz#413929460cb6bf91257378d262e904787d10e03d"
+  integrity sha512-USiB9816d2JzKv0LiSbreRfTg5lDk3lWh0vlx/gugCO92ZIJkHVH0UM18EHvKeadErP6Xn4yiTphWzYfbA2Ong==
   dependencies:
     ext-name "^5.0.0"
-    pupa "^2.0.1"
-    unused-filename "^2.1.0"
+    pupa "^3.1.0"
+    unused-filename "^4.0.1"
 
 electron-to-chromium@^1.4.526:
   version "1.4.528"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz#7c900fd73d9d2e8bb0dab0e301f25f0f4776ef2c"
   integrity sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==
 
-electron@31.7.5:
-  version "31.7.5"
-  resolved "https://registry.yarnpkg.com/electron/-/electron-31.7.5.tgz#98396c75041808b26c7b6a8844cbedacee93d8a6"
-  integrity sha512-8zFzVJdhxTRmoPcRiKkEmPW0bJHAUsTQJwEX2YJ8X0BVFIJLwSvHkSlpCjEExVbNCAk+gHnkIYX+2OyCXrRwHQ==
+electron@35.0.1:
+  version "35.0.1"
+  resolved "https://registry.yarnpkg.com/electron/-/electron-35.0.1.tgz#8596e3936c4b5787e0cf52739bf6842925cfcdc1"
+  integrity sha512-iQonj6lnPhqfqha2KXx6LzV1dnu6UPTCWK+b7f9Zvg828umGemi22DKbcJ3/q+Opn7iUVTWyqp9z1JQqkIi6OA==
   dependencies:
     "@electron/get" "^2.0.0"
-    "@types/node" "^20.9.0"
+    "@types/node" "^22.7.7"
     extract-zip "^2.0.1"
 
 element-resize-detector@^1.1.14:
@@ -3122,10 +3115,10 @@ escalade@^3.1.1:
   resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
   integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
 
-escape-goat@^2.0.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
-  integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
+escape-goat@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-4.0.0.tgz#9424820331b510b0666b98f7873fe11ac4aa8081"
+  integrity sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==
 
 escape-html@~1.0.3:
   version "1.0.3"
@@ -3142,6 +3135,11 @@ escape-string-regexp@^4.0.0:
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
   integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
 
+escape-string-regexp@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
+  integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
+
 esm@^3.2.25:
   version "3.2.25"
   resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
@@ -5507,11 +5505,6 @@ mldoc@^1.5.9:
   dependencies:
     yargs "^12.0.2"
 
-modify-filename@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/modify-filename/-/modify-filename-1.1.0.tgz#9a2dec83806fbb2d975f22beec859ca26b393aa1"
-  integrity sha512-EickqnKq3kVVaZisYuCxhtKbZjInCuwgwZWyAmRIp1NTMhri7r3380/uqwrUHfaDiPzLVTuoNy4whX66bxPVog==
-
 [email protected]:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -6072,6 +6065,11 @@ path-exists@^4.0.0:
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
   integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
 
+path-exists@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7"
+  integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==
+
 path-is-absolute@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -6313,17 +6311,17 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.38.1.tgz#75a3c470aa9576b7d7c4e274de3d79977448ba08"
   integrity sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==
 
-playwright-core@1.44.0:
-  version "1.44.0"
-  resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.0.tgz#316c4f0bca0551ffb88b6eb1c97bc0d2d861b0d5"
-  integrity sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ==
+playwright-core@1.51.0:
+  version "1.51.0"
+  resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.51.0.tgz#bb23ea6bb6298242d088ae5e966ffcf8dc9827e8"
+  integrity sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg==
 
-playwright@1.44.0, playwright@=1.44.0:
-  version "1.44.0"
-  resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.0.tgz#22894e9b69087f6beb639249323d80fe2b5087ff"
-  integrity sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ==
+playwright@1.51.0, playwright@=1.51.0:
+  version "1.51.0"
+  resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.51.0.tgz#9ba154497ba62bc6dc199c58ee19295eb35a4707"
+  integrity sha512-442pTfGM0xxfCYxuBa/Pu6B2OqxqqaYq39JS8QDMGThUvIOCd6s0ANDog3uwA0cHavVlnTQzGCN7Id2YekDSXA==
   dependencies:
-    playwright-core "1.44.0"
+    playwright-core "1.51.0"
   optionalDependencies:
     fsevents "2.3.2"
 
@@ -6859,12 +6857,12 @@ punycode@^2.1.0:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
   integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
 
-pupa@^2.0.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
-  integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==
+pupa@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/pupa/-/pupa-3.1.0.tgz#f15610274376bbcc70c9a3aa8b505ea23f41c579"
+  integrity sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==
   dependencies:
-    escape-goat "^2.0.0"
+    escape-goat "^4.0.0"
 
 [email protected]:
   version "4.0.2"
@@ -8616,10 +8614,10 @@ undertaker@^1.2.1:
     object.reduce "^1.0.0"
     undertaker-registry "^1.0.0"
 
-undici-types@~6.19.2:
-  version "6.19.8"
-  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
-  integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
+undici-types@~6.20.0:
+  version "6.20.0"
+  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
+  integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
 
 unified@^9.1.0:
   version "9.2.2"
@@ -8698,13 +8696,13 @@ untildify@^4.0.0:
   resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
   integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
 
-unused-filename@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/unused-filename/-/unused-filename-2.1.0.tgz#33719c4e8d9644f32d2dec1bc8525c6aaeb4ba51"
-  integrity sha512-BMiNwJbuWmqCpAM1FqxCTD7lXF97AvfQC8Kr/DIeA6VtvhJaMDupZ82+inbjl5yVP44PcxOuCSxye1QMS0wZyg==
+unused-filename@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/unused-filename/-/unused-filename-4.0.1.tgz#3e7285db0f3ec94fb2b089dd220a3b269b226914"
+  integrity sha512-ZX6U1J04K1FoSUeoX1OicAhw4d0aro2qo+L8RhJkiGTNtBNkd/Fi1Wxoc9HzcVu6HfOzm0si/N15JjxFmD1z6A==
   dependencies:
-    modify-filename "^1.1.0"
-    path-exists "^4.0.0"
+    escape-string-regexp "^5.0.0"
+    path-exists "^5.0.0"
 
 upath@^1.1.1:
   version "1.2.0"