enhance(rtc): add :add op to replace :move + :update combo

This commit is contained in:
rcmerci
2026-01-05 20:34:58 +08:00
parent ae8710f764
commit 4dc4e1be78
6 changed files with 243 additions and 59 deletions

View File

@@ -233,6 +233,30 @@
(string/ends-with? ns ".property"))
(= :db.cardinality/one (:db/cardinality (db-schema a)))))) a-coll)))
(defmethod local-block-ops->remote-ops-aux :add-op
[_ & {:keys [db block add-op parent-uuid block-order *remote-ops *depend-on-block-uuid-set]}]
(let [block-uuid (:block/uuid block)
pos (->pos parent-uuid block-order)
av-coll (->> (:av-coll (last add-op))
(remove-redundant-av db)
(remove-non-exist-ref-av db))
[schema-av-coll other-av-coll] (group-by-schema-attrs av-coll)
update-schema-op (schema-av-coll->update-schema-op db block-uuid (:db/ident block) schema-av-coll)
depend-on-block-uuids (keep (fn [[_a v]] (when (uuid? v) v)) other-av-coll)
card-one-attrs (seq (av-coll->card-one-attrs (d/schema db) other-av-coll))]
(when (seq other-av-coll)
(swap! *remote-ops conj
[:add (cond-> {:block-uuid block-uuid
:pos pos
:av-coll other-av-coll}
(:db/ident block) (assoc :db/ident (:db/ident block))
card-one-attrs (assoc :card-one-attrs card-one-attrs))]))
(when update-schema-op
(swap! *remote-ops conj update-schema-op))
(swap! *depend-on-block-uuid-set (partial apply conj) depend-on-block-uuids)
(when parent-uuid
(swap! *depend-on-block-uuid-set conj parent-uuid))))
(defmethod local-block-ops->remote-ops-aux :update-op
[_ & {:keys [db block update-op block-order parent-uuid *remote-ops *depend-on-block-uuid-set]}]
(let [block-uuid (:block/uuid block)
@@ -281,9 +305,9 @@
[db block-ops]
(let [*depend-on-block-uuid-set (atom #{})
*remote-ops (atom [])
{move-op :move remove-op :remove update-op :update update-page-op :update-page remove-page-op :remove-page}
{move-op :move remove-op :remove update-op :update add-op :add update-page-op :update-page remove-page-op :remove-page}
block-ops]
(when-let [block-uuid (some (comp :block-uuid last) [move-op update-op update-page-op])]
(when-let [block-uuid (some (comp :block-uuid last) [move-op update-op add-op update-page-op])]
(when-let [block (d/entity db [:block/uuid block-uuid])]
(let [parent-uuid (some-> block :block/parent :block/uuid)]
;; remote-move-op
@@ -294,6 +318,16 @@
:block-uuid block-uuid
:*remote-ops *remote-ops
:*depend-on-block-uuid-set *depend-on-block-uuid-set))
;; remote-add-op
(when add-op
(local-block-ops->remote-ops-aux :add-op
:db db
:block block
:add-op add-op
:parent-uuid parent-uuid
:block-order (:block/order block)
:*remote-ops *remote-ops
:*depend-on-block-uuid-set *depend-on-block-uuid-set))
;; remote-update-op
(when update-op
(local-block-ops->remote-ops-aux :update-op
@@ -394,6 +428,10 @@
(some->> (get-in block-uuid->remote-ops [block-uuid :move])
(vector :move)))
sorted-uuids)
add-ops (keep
(fn [[_ remote-ops]]
(some->> (:add remote-ops) (vector :add)))
block-uuid->remote-ops)
update-schema-ops (keep
(fn [[_ remote-ops]]
(some->> (:update-schema remote-ops) (vector :update-schema)))
@@ -415,7 +453,7 @@
(fn [[_ remote-ops]]
(some->> (:remove-page remote-ops) (vector :remove-page)))
block-uuid->remote-ops)]
(concat update-schema-ops update-page-ops remove-ops sorted-move-ops update-ops remove-page-ops)))
(concat add-ops update-schema-ops update-page-ops remove-ops sorted-move-ops update-ops remove-page-ops)))
(defn- rollback
[repo block-ops-map-coll update-kv-value-ops-map-coll rename-db-ident-ops-map-coll]
@@ -470,6 +508,13 @@
(recur rest-remote-ops
(conj result [op-type (assoc op-value :av-coll av-coll*)])))
:add
(let [av-coll* (c.m/<?
(crypt/<encrypt-av-coll
aes-key rtc-const/encrypt-attr-set (:av-coll op-value)))]
(recur rest-remote-ops
(conj result [op-type (assoc op-value :av-coll av-coll*)])))
;; else
(recur rest-remote-ops (conj result remote-op)))))))))

View File

@@ -58,6 +58,13 @@
[:value [:map
[:block-uuid :uuid]
[:av-coll [:sequential rtc-schema/av-schema]]]]]]
[:add
[:catn
[:op :keyword]
[:t :int]
[:value [:map
[:block-uuid :uuid]
[:av-coll [:sequential rtc-schema/av-schema]]]]]]
[:update-asset
[:catn
@@ -77,7 +84,7 @@
#(do (log/error ::bad-ops (:value %))
(ma/-fail! ::ops-schema (select-keys % [:value])))))
(def ^:private block-op-types #{:move :remove :update-page :remove-page :update})
(def ^:private block-op-types #{:move :remove :update-page :remove-page :update :add})
(def ^:private asset-op-types #{:update-asset :remove-asset})
(def ^:private update-kv-value-op-types #{:update-kv-value})
(def ^:private db-ident-rename-op-types #{:rename-db-ident})
@@ -128,18 +135,21 @@
r)))
(defn- merge-update-ops
[update-op1 update-op2]
{:pre [(= :update (first update-op1))
(= :update (first update-op2))
(= (:block-uuid (last update-op1))
(:block-uuid (last update-op2)))]}
(let [t1 (second update-op1)
t2 (second update-op2)]
[op1 op2]
{:pre [(contains? #{:add :update} (first op1))
(= :update (first op2))
(= (:block-uuid (last op1))
(:block-uuid (last op2)))]}
(let [t1 (second op1)
t2 (second op2)
op-type1 (first op1)
op-type2 (first op2)]
(if (> t1 t2)
(merge-update-ops update-op2 update-op1)
(let [{av-coll1 :av-coll block-uuid :block-uuid} (last update-op1)
{av-coll2 :av-coll} (last update-op2)]
[:update t2
(merge-update-ops op2 op1)
(let [{av-coll1 :av-coll block-uuid :block-uuid} (last op1)
{av-coll2 :av-coll} (last op2)
result-op-type (if (or (= :add op-type1) (= :add op-type2)) :add :update)]
[result-op-type t2
{:block-uuid block-uuid
:av-coll (concat av-coll1 av-coll2)}]))))
@@ -152,22 +162,34 @@
[_ move-op-t _move-op-value :as move-op] :move
[_ update-op-t _update-op-value :as update-op] :update
[_ update-page-op-t _update-page-op-value :as update-page-op] :update-page
[_ remove-page-op-t _remove-page-op-value :as remove-page-op] :remove-page}
[_ remove-page-op-t _remove-page-op-value :as remove-page-op] :remove-page
[_ add-op-t _add-op-value :as add-op] :add}
(into {} (filter (fn [[_op-type op]] (some-> op (not= :retract))) current-block-op-map))]
(case op-type
:move
:add
(if (>= remove-op-t op-t) current-block-op-map
(cond-> (assoc current-block-op-map :remove :retract)
(or (nil? move-op) (> op-t move-op-t)) (assoc :move op-to-add)))
(or (nil? add-op) (> op-t add-op-t)) (assoc :add op-to-add)))
:move
(if (>= remove-op-t op-t) current-block-op-map
(if add-op
(let [[_ add-t add-val] add-op
new-t (max add-t op-t)]
(assoc current-block-op-map :add [:add new-t add-val]))
(cond-> (assoc current-block-op-map :remove :retract)
(or (nil? move-op) (> op-t move-op-t)) (assoc :move op-to-add))))
:update
(if (>= remove-op-t op-t) current-block-op-map
(assoc current-block-op-map
:remove :retract
:update (if update-op (merge-update-ops update-op op-to-add) op-to-add)))
(if add-op
(assoc current-block-op-map :add (merge-update-ops add-op op-to-add))
(assoc current-block-op-map
:remove :retract
:update (if update-op (merge-update-ops update-op op-to-add) op-to-add))))
:remove
(if (or (>= move-op-t op-t) (>= update-op-t op-t)) current-block-op-map
(cond-> (assoc current-block-op-map :move :retract :update :retract)
(or (nil? remove-op) (> op-t remove-op-t)) (assoc :remove op-to-add)))
(if (or (>= move-op-t op-t) (>= update-op-t op-t) (and add-op (>= add-op-t op-t)))
current-block-op-map
(cond-> (assoc current-block-op-map :move :retract :update :retract :add :retract)
(or (nil? remove-op) (> op-t remove-op-t)) (assoc :remove op-to-add)))
:update-page
(if (>= remove-page-op-t op-t) current-block-op-map
(cond-> (assoc current-block-op-map :remove-page :retract)
@@ -182,7 +204,7 @@
(let [sorted-ops (sort-by second ops)
block-uuids (map (fn [[_op-type _t value]] (:block-uuid value)) sorted-ops)
ents (d/pull-many client-ops-db '[*] (map (fn [block-uuid] [:block/uuid block-uuid]) block-uuids))
op-types [:move :update :remove :update-page :remove-page]
op-types [:add :move :update :remove :update-page :remove-page]
init-block-uuid->op-type->op
(into {}
(map (fn [ent]

View File

@@ -134,6 +134,7 @@
add?->block-order->t :block/order}
a->add?->v->t
[retract-block-uuid t1] (some-> add?->block-uuid->t (get false) first)
[add-block-uuid t-uuid] (some-> add?->block-uuid->t (get true) 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))
@@ -152,6 +153,14 @@
retract-block-uuid
[[:remove t1 {:block-uuid retract-block-uuid}]]
add-block-uuid
(let [av-coll (update-op-av-coll db-before db-after a->add?->v->t*)
add-op [:add (or t-uuid t4 t5) {:block-uuid block-uuid :av-coll av-coll}]]
(if (or add-block-name
(and (ldb/page? entity) add-block-title))
[[:update-page (or t2 t3) {:block-uuid block-uuid}] add-op]
[add-op]))
:else
(let [ops (cond-> []
(or add-block-parent add-block-order)

View File

@@ -186,3 +186,64 @@
(is (some? (:move result-op)))
(is (= 3 (second (:move result-op))))
(is (nil? (:remove result-op))))))))))
(deftest add-op-merge-test
(testing "Merge :add and :move"
(let [conn (d/create-conn client-op/schema-in-db)
block-uuid (random-uuid)
add-op [:add 1 {:block-uuid block-uuid :av-coll []}]
move-op [:move 2 {:block-uuid block-uuid}]]
(with-redefs [worker-state/get-client-ops-conn (constantly conn)]
(client-op/add-ops! "repo" [add-op])
(client-op/add-ops! "repo" [move-op])
(let [ops (client-op/get&remove-all-block-ops "repo")
result-op (first ops)]
(is (= 1 (count ops)))
(is (some? (:add result-op)))
(is (= 2 (second (:add result-op))))
(is (nil? (:move result-op)))))))
(testing "Merge :add and :update"
(let [conn (d/create-conn client-op/schema-in-db)
block-uuid (random-uuid)
add-op [:add 1 {:block-uuid block-uuid :av-coll [[:block/title "A" 1 true]]}]
update-op [:update 2 {:block-uuid block-uuid :av-coll [[:block/title "B" 2 true]]}]]
(with-redefs [worker-state/get-client-ops-conn (constantly conn)]
(client-op/add-ops! "repo" [add-op])
(client-op/add-ops! "repo" [update-op])
(let [ops (client-op/get&remove-all-block-ops "repo")
result-op (first ops)]
(is (= 1 (count ops)))
(is (some? (:add result-op)))
(is (= 2 (second (:add result-op))))
(let [av-coll (:av-coll (last (:add result-op)))]
(is (= [[:block/title "A" 1 true] [:block/title "B" 2 true]] av-coll)))
(is (nil? (:update result-op)))))))
(testing "Merge :add and :remove (Newer Remove)"
(let [conn (d/create-conn client-op/schema-in-db)
block-uuid (random-uuid)
add-op [:add 1 {:block-uuid block-uuid :av-coll []}]
remove-op [:remove 2 {:block-uuid block-uuid}]]
(with-redefs [worker-state/get-client-ops-conn (constantly conn)]
(client-op/add-ops! "repo" [add-op])
(client-op/add-ops! "repo" [remove-op])
(let [ops (client-op/get&remove-all-block-ops "repo")
result-op (first ops)]
(is (= 1 (count ops)))
(is (some? (:remove result-op)))
(is (nil? (:add result-op)))))))
(testing "Merge :remove and :add (Newer Add)"
(let [conn (d/create-conn client-op/schema-in-db)
block-uuid (random-uuid)
remove-op [:remove 1 {:block-uuid block-uuid}]
add-op [:add 2 {:block-uuid block-uuid :av-coll []}]]
(with-redefs [worker-state/get-client-ops-conn (constantly conn)]
(client-op/add-ops! "repo" [remove-op])
(client-op/add-ops! "repo" [add-op])
(let [ops (client-op/get&remove-all-block-ops "repo")
result-op (first ops)]
(is (= 1 (count ops)))
(is (some? (:add result-op)))
(is (nil? (:remove result-op))))))))

View File

@@ -104,4 +104,30 @@
[:block/title (ldb/write-transit-str "xxx") 1 true]
[:block/type (ldb/write-transit-str "property") 1 true]
[:db/cardinality (ldb/write-transit-str :db.cardinality/one) 1 true]
[:db/index (ldb/write-transit-str true) 1 true]]}]})))))))
[:db/index (ldb/write-transit-str true) 1 true]]}]}))))))
(testing "user.class/zzz creation (add op)"
(let [block-uuid (random-uuid)
db (d/db-with empty-db [{:block/uuid block-uuid,
:block/updated-at 1720017595873,
:block/created-at 1720017595872,
:db/ident :user.class/zzz,
:block/type "class",
:block/name "zzz",
:block/title "zzz"}])]
(is (= {:add
{:block-uuid block-uuid
:db/ident :user.class/zzz
:pos [nil nil]
:av-coll
[[:block/name "[\"~#'\",\"zzz\"]" 1 true]
[:block/title "[\"~#'\",\"zzz\"]" 1 true]
[:block/type "[\"~#'\",\"class\"]" 1 true]]}}
(:remote-ops
(#'subject/local-block-ops->remote-ops
db
{:add [:add 1 {:block-uuid block-uuid
:av-coll
[[:block/name (ldb/write-transit-str "zzz") 1 true]
[:block/title (ldb/write-transit-str "zzz") 1 true]
[:block/type (ldb/write-transit-str "class") 1 true]]}]})))))))

View File

@@ -24,7 +24,7 @@
id->same-entity-datoms (group-by first datom-vec-coll)]
(update-vals id->same-entity-datoms #'subject/entity-datoms=>a->add?->v->t)))
(deftest entity-datoms=>ops-test
(deftest ^:large-vars/cleanup-todo entity-datoms=>ops-test
(testing "remove whiteboard page-block"
(let [conn (db-test/create-conn)
block-uuid (random-uuid)
@@ -43,6 +43,31 @@
(is (= [[:remove-page {:block-uuid block-uuid}]]
(map (fn [[op-type _t op-value]] [op-type op-value]) r)))))
(testing "create page block"
(let [conn (db-test/create-conn)
block-uuid (random-uuid)
tx-data [[:db/add 1000000 :block/uuid block-uuid]
[:db/add 1000000 :block/name "page-name"]
[:db/add 1000000 :block/title "Page Title"]
[:db/add 1000000 :block/created-at 1716882111476]
[:db/add 1000000 :block/updated-at 1716882111476]]
{:keys [db-before db-after tx-data]} (d/transact! conn tx-data)
ops (#'subject/entity-datoms=>ops db-before db-after
(tx-data=>e->a->add?->v->t tx-data)
nil
(map vec tx-data))]
(is (= [[:update-page {:block-uuid block-uuid}]
[:add {:block-uuid block-uuid
:av-coll
(set [[:block/updated-at "[\"~#'\",1716882111476]"]
[:block/created-at "[\"~#'\",1716882111476]"]
[:block/title "[\"~#'\",\"Page Title\"]"]])}]]
(map (fn [[op-type _t op-value]]
[op-type (cond-> op-value
(:av-coll op-value)
(assoc :av-coll (set (map #(take 2 %) (:av-coll op-value)))))])
ops)))))
(testing "update-schema op"
(let [conn (db-test/create-conn)
tx-data [[:db/add 1000000 :db/index true]
@@ -64,20 +89,19 @@
#{:logseq.property/ignored-attr-x}
(map vec tx-data))]
(is (=
[[:move {:block-uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305"}]
[:update-page {:block-uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305"}]
[:update {:block-uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305"
:av-coll
[[:db/index "[\"~#'\",true]"]
[:logseq.property/type "[\"~#'\",\"~:number\"]"]
[:db/valueType "[\"~#'\",\"~:db.type/ref\"]"]
[:block/updated-at "[\"~#'\",1716882111476]"]
[:block/created-at "[\"~#'\",1716882111476]"]
[:block/tags #uuid "00000002-1038-7670-4800-000000000000"]
[:block/title "[\"~#'\",\"qqq\"]"]
[:db/cardinality "[\"~#'\",\"~:db.cardinality/one\"]"]
[[:update-page {:block-uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305"}]
[:add {:block-uuid #uuid "66558abf-6512-469d-9e83-8f1ba0be9305"
:av-coll
[[:db/index "[\"~#'\",true]"]
[:logseq.property/type "[\"~#'\",\"~:number\"]"]
[:db/valueType "[\"~#'\",\"~:db.type/ref\"]"]
[:block/updated-at "[\"~#'\",1716882111476]"]
[:block/created-at "[\"~#'\",1716882111476]"]
[:block/tags #uuid "00000002-1038-7670-4800-000000000000"]
[:block/title "[\"~#'\",\"qqq\"]"]
[:db/cardinality "[\"~#'\",\"~:db.cardinality/one\"]"]
;; [:db/ident "[\"~#'\",\"~:user.property/qqq\"]"]
]}]]
]}]]
(map (fn [[op-type _t op-value]]
[op-type (cond-> op-value
(:av-coll op-value)
@@ -101,16 +125,16 @@
(map vec tx-data))]
(is (=
[[:update-page {:block-uuid #uuid "66856a29-6eb3-4122-af97-8580a853c6a6"}]
[:update {:block-uuid #uuid "66856a29-6eb3-4122-af97-8580a853c6a6",
:av-coll
(set
[[:block/updated-at "[\"~#'\",1720019497643]"]
[:block/created-at "[\"~#'\",1720019497643]"]
[:block/tags #uuid "00000002-5389-0208-3000-000000000000"]
[:block/title "[\"~#'\",\"zzz\"]"]
[:logseq.property.class/extends #uuid "00000002-2737-8382-7000-000000000000"]
[:add {:block-uuid #uuid "66856a29-6eb3-4122-af97-8580a853c6a6",
:av-coll
(set
[[:block/updated-at "[\"~#'\",1720019497643]"]
[:block/created-at "[\"~#'\",1720019497643]"]
[:block/tags #uuid "00000002-5389-0208-3000-000000000000"]
[:block/title "[\"~#'\",\"zzz\"]"]
[:logseq.property.class/extends #uuid "00000002-2737-8382-7000-000000000000"]
;;1. shouldn't have :db/ident, :db/ident is special, will be handled later
])}]]
])}]]
(map (fn [[op-type _t op-value]]
[op-type (cond-> op-value
(:av-coll op-value)
@@ -129,7 +153,7 @@
(testing "add page"
(worker-page/create! conn "TEST-PAGE" {:uuid page-uuid})
(is (some? (d/pull @conn '[*] [:block/uuid page-uuid])))
(is (= {page-uuid #{:update-page :update}}
(is (= {page-uuid #{:add :update-page}}
(ops-coll=>block-uuid->op-types (client-op/get&remove-all-block-ops repo)))))
(testing "add blocks to this page"
(let [target-entity (d/entity @conn [:block/uuid page-uuid])]
@@ -142,8 +166,8 @@
target-entity
{:sibling? false :keep-uuid? true}))
(is (=
{block-uuid1 #{:move :update}
block-uuid2 #{:move :update}}
{block-uuid1 #{:add}
block-uuid2 #{:add}}
(ops-coll=>block-uuid->op-types (client-op/get&remove-all-block-ops repo))))))
(testing "delete a block"
@@ -164,10 +188,8 @@
: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)
([:update-page . _ ...] [:add _ {:block-uuid ?block-uuid :av-coll ([!av-coll-attrs . _ ...] ...)}])
!av-coll-attrs)
set
(set/difference av-coll-attrs)
empty?))))
@@ -180,9 +202,8 @@
: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)
([:update-page . _ ...] [:add _ {:block-uuid ?block-uuid :av-coll ([!av-coll-attrs . _ ...] ...)}])
!av-coll-attrs)
set
(set/difference av-coll-attrs)
empty?))))