From a9375da9b81fbb98782787e34e8efe855122f428 Mon Sep 17 00:00:00 2001 From: Sasha Mizov Date: Tue, 20 Jan 2026 17:09:23 +0200 Subject: [PATCH 1/2] Add delta OR set implementation --- DeltaBased/2-or-set.js | 124 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 DeltaBased/2-or-set.js diff --git a/DeltaBased/2-or-set.js b/DeltaBased/2-or-set.js new file mode 100644 index 0000000..914d26c --- /dev/null +++ b/DeltaBased/2-or-set.js @@ -0,0 +1,124 @@ +'use strict'; + +const crypto = require('node:crypto'); + +const init = () => ({ added: {}, removed: {} }); + +class DeltaORSet { + #added; + #removed; + + constructor({ added = {}, removed = {} } = {}) { + this.#added = { ...added }; + this.#removed = { ...removed }; + } + + add(item, tag = crypto.randomUUID()) { + const result = init(); + const added = this.#added[item]; + result.added[item] = added ? new Set([...added, tag]) : new Set([tag]); + return result; + } + + remove(item) { + const result = init(); + const added = this.#added[item]; + + if (!added) return result; + + const removed = this.#removed[item]; + result.removed[item] = removed + ? new Set([...removed, ...added]) + : new Set([...added]); + + return result; + } + + join(delta) { + const added = Object.entries(delta.added); + for (const [item, tags] of added) { + const deltaTags = tags || new Set(); + + const added = this.#added[item]; + this.#added[item] = added + ? new Set([...added, ...deltaTags]) + : new Set([...deltaTags]); + } + const removed = Object.entries(delta.removed); + for (const [item, tags] of removed) { + const deltaTags = tags || new Set(); + const removed = this.#removed[item]; + this.#removed[item] = removed + ? new Set([...removed, ...deltaTags]) + : new Set([...deltaTags]); + } + } + + get value() { + const result = []; + for (const item of Object.keys(this.#added)) { + const addTags = this.#added[item] || new Set(); + const remTags = this.#removed[item] || new Set(); + for (const tag of addTags) { + if (!remTags.has(tag)) { + result.push(item); + break; + } + } + } + return result; + } + + get added() { + return this.#added; + } + + get removed() { + return this.#removed; + } +} + +// Usage + +console.log('--------------------------------'); +console.log('Delta CRDT OR-Set'); +console.log('--------------------------------'); + +console.log('Replica 0'); +const set0 = new DeltaORSet(); +const delta01 = set0.add('a'); +set0.join(delta01); +const delta02 = set0.add('b'); +set0.join(delta02); +const delta03 = set0.remove('a'); +console.log({ delta03 }); +set0.join(delta03); +console.log({ id0: set0.value }); +console.log({ id0: set0.added }); +console.log({ id0: set0.removed }); + +console.log('Replica 1'); +const set1 = new DeltaORSet(); +const delta11 = set1.add('b'); +set1.join(delta11); +const delta12 = set1.add('c'); +set1.join(delta12); +const delta13 = set1.remove('b'); +set1.join(delta13); +const delta14 = set1.add('c'); +set1.join(delta14); +const delta15 = set1.remove('b'); +set1.join(delta15); +console.log({ id1: set1.value }); + +console.log('Sync'); +set0.join(set1); +set1.join(set0); +console.log({ id0Added: set0.added }); +console.log({ id0Removed: set0.removed }); +console.log({ id1Added: set1.added }); +console.log({ id1Removed: set1.removed }); + +console.log('Get value'); +console.log({ id0: set0.value }); +console.log({ id1: set1.value }); From afb398b7fcd2f87227c5f4411e49c9caf92797c3 Mon Sep 17 00:00:00 2001 From: Sasha Mizov Date: Tue, 20 Jan 2026 20:53:41 +0200 Subject: [PATCH 2/2] Address PR comments --- DeltaBased/2-or-set.js | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/DeltaBased/2-or-set.js b/DeltaBased/2-or-set.js index 914d26c..c74d4f3 100644 --- a/DeltaBased/2-or-set.js +++ b/DeltaBased/2-or-set.js @@ -2,8 +2,6 @@ const crypto = require('node:crypto'); -const init = () => ({ added: {}, removed: {} }); - class DeltaORSet { #added; #removed; @@ -14,44 +12,36 @@ class DeltaORSet { } add(item, tag = crypto.randomUUID()) { - const result = init(); - const added = this.#added[item]; - result.added[item] = added ? new Set([...added, tag]) : new Set([tag]); + const result = new DeltaORSet(); + const added = this.#added[item] || []; + result.#added[item] = new Set([...added, tag]); return result; } remove(item) { - const result = init(); + const result = new DeltaORSet(); const added = this.#added[item]; if (!added) return result; - const removed = this.#removed[item]; - result.removed[item] = removed - ? new Set([...removed, ...added]) - : new Set([...added]); + const removed = this.#removed[item] || []; + result.#removed[item] = new Set([...removed, ...added]); return result; } join(delta) { - const added = Object.entries(delta.added); - for (const [item, tags] of added) { - const deltaTags = tags || new Set(); - - const added = this.#added[item]; - this.#added[item] = added - ? new Set([...added, ...deltaTags]) - : new Set([...deltaTags]); + for (const [item, tags] of Object.entries(delta.added)) { + const added = this.#added[item] || []; + this.#added[item] = new Set([...added, ...tags]); } - const removed = Object.entries(delta.removed); - for (const [item, tags] of removed) { - const deltaTags = tags || new Set(); - const removed = this.#removed[item]; - this.#removed[item] = removed - ? new Set([...removed, ...deltaTags]) - : new Set([...deltaTags]); + + for (const [item, tags] of Object.entries(delta.removed)) { + const removed = this.#removed[item] || []; + this.#removed[item] = new Set([...removed, ...tags]); } + + return this; } get value() { @@ -79,7 +69,6 @@ class DeltaORSet { } // Usage - console.log('--------------------------------'); console.log('Delta CRDT OR-Set'); console.log('--------------------------------');