diff --git a/packages/crdt/src/builtins/LWWRegister/index.ts b/packages/crdt/src/builtins/LWWRegister/index.ts new file mode 100644 index 00000000..94fbd5ea --- /dev/null +++ b/packages/crdt/src/builtins/LWWRegister/index.ts @@ -0,0 +1,47 @@ +export class LWWRegister { + private _element: T; + private _timestamp: number; + private _nodeId: string; + + constructor(element: T, nodeId: string) { + this._element = element; + this._timestamp = Date.now(); + this._nodeId = nodeId; + } + + assign(element: T, nodeId: string): void { + this._element = element; + this._timestamp = Date.now(); + this._nodeId = nodeId; + } + + getElement(): T { + return this._element; + } + + getTimestamp(): number { + return this._timestamp; + } + + getNodeId(): string { + return this._nodeId; + } + + compare(register: LWWRegister): boolean { + return (this._timestamp <= register.getTimestamp()); + } + + merge(register: LWWRegister): void { + const otherTimestamp = register.getTimestamp(); + const otherNodeId = register.getNodeId(); + if (otherTimestamp < this._timestamp) { + return; + } + if (otherTimestamp === this._timestamp && otherNodeId <= this._nodeId) { + return; + } + this._element = register.getElement(); + this._timestamp = otherTimestamp; + this._nodeId = otherNodeId; + } +} \ No newline at end of file diff --git a/packages/crdt/tests/LWWRegister.test.ts b/packages/crdt/tests/LWWRegister.test.ts new file mode 100644 index 00000000..8f4e9231 --- /dev/null +++ b/packages/crdt/tests/LWWRegister.test.ts @@ -0,0 +1,64 @@ +import { describe, test, expect, beforeEach, vi } from "vitest"; +import { LWWRegister } from "../src/builtins/LWWRegister/index.ts"; + +describe('LWW-Register Tests', () => { + test('Test Assign', () => { + let register1 = new LWWRegister("alice", "node1"); + + expect(register1.getElement()).toBe("alice"); + expect(register1.getNodeId()).toEqual("node1"); + register1.assign("bob", "node2"); + expect(register1.getElement()).toBe("bob"); + expect(register1.getNodeId()).toEqual("node2"); + }); + + test('Test Compare', () => { + vi.useFakeTimers(); + const date = new Date(2000,1,1,13); + vi.setSystemTime(date); + + let register1 = new LWWRegister("alice", "node1"); + + vi.useRealTimers(); + + let register2 = new LWWRegister("alice", "node2"); + + expect(register1.compare(register2)).toEqual(true); + expect(register2.compare(register1)).toEqual(false); + }); + + test('Test Merge', () => { + let register1 = new LWWRegister("alice", "node1"); + let register2 = new LWWRegister("bob", "node2"); + + register1.merge(register2); + expect(register1.getElement()).toEqual("bob"); + expect(register2.getNodeId()).toEqual("node2"); + + register2.merge(register1); + expect(register1.getElement()).toEqual("bob"); + expect(register2.getNodeId()).toEqual("node2"); + }); + + test('Test Merge w/same timestamp', () => { + vi.useFakeTimers(); + const date = new Date(2000,1,1,13); + vi.setSystemTime(date); + + let register1 = new LWWRegister("alice", "node1"); + let register2 = new LWWRegister("bob", "node2"); + + expect(register1.getElement()).toBe("alice"); + expect(register1.getNodeId()).toEqual("node1"); + + register1.merge(register2); + expect(register1.getElement()).toBe("bob"); + expect(register1.getNodeId()).toEqual("node2"); + + register2.merge(register1); + expect(register1.getElement()).toBe("bob"); + expect(register1.getNodeId()).toEqual("node2"); + + vi.useRealTimers(); + }); +}); \ No newline at end of file