fix(db-sync): worker server unit-tests

This commit is contained in:
rcmerci
2026-01-11 23:57:33 +08:00
parent 10e368fb39
commit 9356782168
4 changed files with 109 additions and 47 deletions

5
bb.edn
View File

@@ -174,8 +174,9 @@
:task (clojure {:dir "clj-e2e"} "-X:dev-run-rtc-extra-part2-test")}
dev:db-sync-test
{:doc "Run db-sync unit tests"
:task (shell {:dir "deps/db-sync"} "clojure -M:cljs compile db-sync-test && node worker/dist/worker-test.js")}
{:doc "Run db-sync server unit tests"
:task (do (shell {:dir "deps/db-sync"} "clojure -M:cljs compile db-sync-test")
(shell {:dir "deps/db-sync"} "node worker/dist/worker-test.js"))}
dev:db-sync-start
{:doc "Start db-sync local server + client watch processes in foreground"

View File

@@ -45,18 +45,29 @@
(keyword? entity) [:db/ident entity]
:else entity))
(defn- next-parent-eid [db attr eid]
(when-let [entity (d/entity db eid)]
(let [value (get entity attr)]
(:db/id value))))
(defn- next-parent-eid [db attr eid updates-by-eid]
(if (contains? updates-by-eid eid)
(get updates-by-eid eid)
(when-let [entity (d/entity db eid)]
(let [value (get entity attr)]
(cond
(instance? Entity value) (:db/id value)
:else (ref->eid db (normalize-entity-ref value)))))))
(defn- cycle-from-eid? [db attr eid]
(loop [seen #{eid}
current (next-parent-eid db attr eid)]
(defn- cycle-from-eid? [db attr start-eid target-eid updates-by-eid]
(loop [seen #{target-eid}
current start-eid]
(cond
(nil? current) false
(contains? seen current) true
:else (recur (conj seen current) (next-parent-eid db attr current)))))
:else (recur (conj seen current)
(next-parent-eid db attr current updates-by-eid)))))
(defn- normalize-entity-ref-for-result [db entity-ref]
(if (number? entity-ref)
(when-let [ent (d/entity db entity-ref)]
[:block/uuid (:block/uuid ent)])
entity-ref))
(defn detect-cycle
"Returns a map with cycle details when applying tx-data would introduce a cycle.
@@ -65,16 +76,31 @@
(reduce
(fn [_ attr]
(let [updates (attr-updates-from-tx tx-data attr)
updates-by-eid
(reduce
(fn [acc {:keys [entity value]}]
(let [entity-ref (normalize-entity-ref entity)
eid (ref->eid db entity-ref)
value-ref (normalize-entity-ref value)
value-eid (ref->eid db value-ref)]
(if eid
(assoc acc eid value-eid)
acc)))
{}
updates)
result
(reduce
(fn [_ {:keys [entity value]}]
(if (nil? value)
nil
(let [entity-ref (normalize-entity-ref entity)
eid (ref->eid db entity-ref)]
(when (and eid (cycle-from-eid? db attr eid))
eid (ref->eid db entity-ref)
value-ref (normalize-entity-ref value)
value-eid (ref->eid db value-ref)]
(when (and eid value-eid
(cycle-from-eid? db attr value-eid eid updates-by-eid))
{:attr attr
:entity entity-ref}))))
:entity (normalize-entity-ref-for-result db entity-ref)}))))
nil
updates)]
(when result

View File

@@ -2,39 +2,77 @@
(:require [datascript.core :as d]
[logseq.db.common.order :as db-order]))
(defn- attr-updates-from-tx [tx-data attr]
(filter (fn [d] (= attr (:a d))) tx-data))
(defn- normalize-eid [db entity]
(cond
(number? entity) (when (pos? entity) entity)
(vector? entity) (d/entid db entity)
(uuid? entity) (d/entid db [:block/uuid entity])
(keyword? entity) (d/entid db [:db/ident entity])
:else nil))
(defn- attr-updates-from-tx [db tx-data attr]
(keep (fn [tx]
(cond
(and (vector? tx)
(= :db/add (first tx))
(= attr (nth tx 2 nil)))
(let [eid (normalize-eid db (nth tx 1 nil))]
(when eid
{:e eid
:a attr
:v (nth tx 3 nil)}))
(and (map? tx) (contains? tx attr))
(let [entity (or (:db/id tx) (:block/uuid tx) (:db/ident tx))
eid (normalize-eid db entity)]
(when eid
{:e eid
:a attr
:v (get tx attr)}))
:else nil))
tx-data))
(defn fix-duplicate-orders [db tx-data]
(let [updates (attr-updates-from-tx tx-data :block/order)
fixes (reduce
(fn [acc d]
(let [eid (:e d)
value (:v d)
parent (when eid (:block/parent (d/entity db eid)))
parent-eid (:db/id parent)]
(if (and eid parent-eid value)
(let [siblings (d/datoms db :avet :block/parent parent-eid)
same-order-siblings (-> (filter
(fn [datom]
(let [sib-eid (:e datom)]
(when (and (not= sib-eid eid)
(= value (:block/order (d/entity db sib-eid))))
sib-eid)))
siblings)
sort
vec)
orders (map (fn [d] (:block/order (d/entity db (:e d)))) siblings)
start (some (fn [order] (when (< order value) order)) orders)
end (some (fn [order] (when (> order value) order)) orders)]
(if same-order-siblings
(let [orders (db-order/gen-n-keys (inc (count same-order-siblings)) start end)]
(into acc
(map-indexed (fn [idx id] [:db/add id :block/order (nth orders idx)]) (conj same-order-siblings eid))))
acc))
acc)))
(let [updates (->> (attr-updates-from-tx db tx-data :block/order)
(filter (fn [{:keys [e v]}] (and e v))))
groups (group-by (fn [{:keys [e v]}]
(let [parent (:block/parent (d/entity db e))]
[(:db/id parent) v]))
updates)
fixes (reduce-kv
(fn [acc [parent-eid value] group]
(if (and parent-eid value)
(let [update-eids (->> group (map :e) sort vec)
siblings (d/datoms db :avet :block/parent parent-eid)
update-eids-set (set update-eids)
existing-same (->> siblings
(keep (fn [datom]
(let [sib-eid (:e datom)]
(when (and (not (contains? update-eids-set sib-eid))
(= value (:block/order (d/entity db sib-eid))))
sib-eid)))))
need-fix? (or (seq existing-same)
(> (count update-eids) 1))]
(if need-fix?
(let [orders (->> siblings
(keep (fn [d]
(let [sib-eid (:e d)]
(when-not (contains? update-eids-set sib-eid)
(:block/order (d/entity db sib-eid)))))))
end (some (fn [order]
(when (> (compare order value) 0)
order))
orders)
new-orders (db-order/gen-n-keys (count update-eids) value end)]
(into acc
(map-indexed (fn [idx id]
[:db/add id :block/order (nth new-orders idx)])
update-eids)))
acc))
acc))
[]
updates)]
groups)]
(if (seq fixes)
(into tx-data fixes)
tx-data)))

View File

@@ -46,10 +46,7 @@ This guide helps AI agents implement and review db-sync features consistently ac
## Testing & Verification
- Local dev(client+server): `bb dev:db-sync-start` runs the db-sync watcher, `wrangler dev`, and `yarn watch` with `ENABLE_DB_SYNC_LOCAL=true`
- Unit tests: `bb dev:lint-and-test`
- DB-sync tests: `bb dev:db-sync-test`
- Single test example: `bb dev:test -v logseq.db-sync.storage-test/foo`
- Worker local build: `clojure -M:cljs release db-sync`
- DB-sync server side unit-tests: `bb dev:db-sync-test`
## Review Checklist
- Protocol versioning and error handling are consistent across client/server.