“State-based CRDTs (CvRDT) synchronize by sending full states or deltas; Operation-based CRDTs (CmRDT) synchronize by sending commutative operations over reliable causal channels. A PN-Counter uses two G-Counters (P for increments, N for decrements). An LWW-Element-Set attaches Lamport timestamps to add and remove sets to resolve concurrent additions and deletions.”
Positive-Negative Counters, Last-Write-Wins registers, and Observed-Removed Sets (OR-Set).
// PN-Counter (Positive-Negative Counter)
class PNCounter {
private P = new Map<string, number>();
private N = new Map<string, number>();
increment(nodeId: string, val = 1) { this.P.set(nodeId, (this.P.get(nodeId) || 0) + val); }
decrement(nodeId: string, val = 1) { this.N.set(nodeId, (this.N.get(nodeId) || 0) + val); }
merge(other: PNCounter) {
for (const [k, v] of other.P) this.P.set(k, Math.max(this.P.get(k) || 0, v));
for (const [k, v] of other.N) this.N.set(k, Math.max(this.N.get(k) || 0, v));
}
value(): number {
const pSum = Array.from(this.P.values()).reduce((a, b) => a + b, 0);
const nSum = Array.from(this.N.values()).reduce((a, b) => a + b, 0);
return pSum - nSum;
}
}PN-Counter: Maintain positive counter map P and negative counter map N
Increment: P[nodeId] += 1; Decrement: N[nodeId] += 1
Value: sum(P) - sum(N)
Merge: P_local = max(P_local, P_remote) and N_local = max(N_local, N_remote)
Monotonic growth ensures no decrement can ever be lost during network partitions
Compacting tombstones in OR-Sets via causal stability analysis reclaims memory from permanently deleted items.