From 8122c18f6032696302340637d4e23e818fed7fb3 Mon Sep 17 00:00:00 2001 From: Akash S M Date: Mon, 26 Aug 2024 07:51:53 +0530 Subject: [PATCH] feat: hashgraph (#103) Co-authored-by: Oak --- packages/crdt/package.json | 3 + packages/crdt/src/cros/AddWinsSet/index.ts | 65 ++++ packages/crdt/src/index.ts | 2 + packages/crdt/tests/AddWinsSet.test.ts | 339 +++++++++++++++++++++ packages/crdt/tests/RGA.test.ts | 174 +++++------ packages/crdt/tsconfig.json | 5 + packages/node/src/version.ts | 2 +- packages/object/src/hashgraph.ts | 252 +++++++++++++++ packages/object/src/index.ts | 2 + yarn.lock | 243 ++++++++------- 10 files changed, 881 insertions(+), 206 deletions(-) create mode 100644 packages/crdt/src/cros/AddWinsSet/index.ts create mode 100644 packages/crdt/tests/AddWinsSet.test.ts create mode 100644 packages/object/src/hashgraph.ts diff --git a/packages/crdt/package.json b/packages/crdt/package.json index 4a6eab57..a967465f 100644 --- a/packages/crdt/package.json +++ b/packages/crdt/package.json @@ -25,5 +25,8 @@ "clean": "rm -rf dist/ node_modules/", "prepack": "tsc -b", "test": "vitest" + }, + "dependencies": { + "@topology-foundation/object": "0.0.23-5" } } diff --git a/packages/crdt/src/cros/AddWinsSet/index.ts b/packages/crdt/src/cros/AddWinsSet/index.ts new file mode 100644 index 00000000..795d7e88 --- /dev/null +++ b/packages/crdt/src/cros/AddWinsSet/index.ts @@ -0,0 +1,65 @@ +import { ActionType, HashGraph, Operation, OperationType } from "@topology-foundation/object"; + +/// AddWinsSet with support for state and op changes +export class AddWinsSet { + state: Map; + hashGraph: HashGraph; + + constructor(nodeId: string) { + this.state = new Map(); + this.hashGraph = new HashGraph(this.resolveConflicts, nodeId); + } + + resolveConflicts(op1: Operation, op2: Operation): ActionType { + if (op1.type !== op2.type && op1.value === op2.value) { + return op1.type === OperationType.Add ? ActionType.DropRight : ActionType.DropLeft; + } + return ActionType.Nop; + } + + add(value: T): void { + const op = new Operation(OperationType.Add, value); + this.state.set(value, (this.state.get(value) || 0) + 1); + } + + remove(value: T): void { + const op = new Operation(OperationType.Remove, value); + this.add(value); + } + + getValue(value: T): number { + return this.state.get(value) || 0; + } + + isInSet(value: T): boolean { + const count = this.getValue(value); + return count > 0 && count % 2 === 1; + } + + values(): T[] { + return Array.from(this.state.entries()) + .filter(([_, count]) => count % 2 === 1) + .map(([value, _]) => value); + } + + merge(other: AddWinsSet): void { + for (const [value, count] of other.state) { + this.state.set(value, Math.max(this.getValue(value), count)); + } + } + + read(): T[] { + const operations = this.hashGraph.linearizeOps(); + const tempCounter = new AddWinsSet(""); + + for (const op of operations) { + if (op.type === OperationType.Add) { + tempCounter.add(op.value); + } else { + tempCounter.remove(op.value); + } + } + + return tempCounter.values(); + } +} diff --git a/packages/crdt/src/index.ts b/packages/crdt/src/index.ts index 9bf66ca1..f8d881b9 100644 --- a/packages/crdt/src/index.ts +++ b/packages/crdt/src/index.ts @@ -6,3 +6,5 @@ export * from "./crdts/LWWElementSet/index.js"; export * from "./crdts/LWWRegister/index.js"; export * from "./crdts/PNCounter/index.js"; export * from "./crdts/RGA/index.js"; + +export * from "./cros/AddWinsSet/index.js"; diff --git a/packages/crdt/tests/AddWinsSet.test.ts b/packages/crdt/tests/AddWinsSet.test.ts new file mode 100644 index 00000000..cb821d9e --- /dev/null +++ b/packages/crdt/tests/AddWinsSet.test.ts @@ -0,0 +1,339 @@ +import { describe, test, expect, beforeEach } from "vitest"; +import { AddWinsSet } from "../src/cros/AddWinsSet/index.js"; +import { Operation, OperationType } from "@topology-foundation/object"; + +describe("HashGraph for AddWinSet tests", () => { + let cro: AddWinsSet; + let op0: Operation; + let vertexHash0: string + const peerId = "peerId0" + + beforeEach(() => { + cro = new AddWinsSet("peer0"); + op0 = new Operation(OperationType.Nop, 0); + vertexHash0 = cro.hashGraph.rootHash; + }); + + test("Test: Add Two Vertices", () => { + + /* + V1:NOP <- V2:ADD(1) <- V2:REMOVE(1) + */ + let op1: Operation = new Operation(OperationType.Add, 1); + let deps1: string[] = [vertexHash0] + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1, peerId); + let linearOps = cro.hashGraph.linearizeOps(); + expect(linearOps).toEqual([op0, op1]); + + // Add second vertex + let op2: Operation = new Operation(OperationType.Remove, 1); + let deps2: string[] = [vertexHash1]; + let vertexHash2 = cro.hashGraph.addVertex(op2, deps2, peerId); + linearOps = cro.hashGraph.linearizeOps(); + let orderArray = cro.hashGraph.topologicalSort(); + expect(linearOps).toEqual([op0, op1, op2]); + }); + + test("Test: Add Two Concurrent Vertices With Same Value", () => { + /* + _ V2:REMOVE(1) + V1:ADD(1) / + \ _ V3:ADD(1) + */ + + let op1: Operation = new Operation(OperationType.Add, 1);; + let deps1: string[] = [vertexHash0] + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1, peerId); + + let linearOps = cro.hashGraph.linearizeOps(); + expect(linearOps).toEqual([op0, op1]); + + // Add second vertex + let op2: Operation = new Operation(OperationType.Remove, 1); + let deps2: string[] = [vertexHash1]; + let vertexHash2 = cro.hashGraph.addVertex(op2, deps2, peerId); + + linearOps = cro.hashGraph.linearizeOps(); + expect(linearOps).toEqual([op0, op1, op2]); + + // Add the third vertex V3 concurrent with V2 + let op3: Operation = new Operation(OperationType.Add, 1); + let deps3: string[] = [vertexHash1]; + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3, peerId); + + linearOps = cro.hashGraph.linearizeOps(); + expect(linearOps).toEqual([op0, op1, op3]); + }); + + test("Test: Add Two Concurrent Vertices With Different Values", () => { + /* + _ V2:REMOVE(1) + V1:ADD(1) / + \ _ V3:ADD(2) + */ + + let op1: Operation = new Operation(OperationType.Add, 1); + let deps1: string[] = [vertexHash0] + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1, peerId); + let linearOps = cro.hashGraph.linearizeOps(); + expect(linearOps).toEqual([op0, op1]); + + // Add second vertex + let op2: Operation = new Operation(OperationType.Remove, 1); + let deps2: string[] = [vertexHash1]; + let vertexHash2 = cro.hashGraph.addVertex(op2, deps2, peerId); + linearOps = cro.hashGraph.linearizeOps(); + expect(linearOps).toEqual([op0, op1, op2]); + + // Add the third vertex V3 concurrent with V2 + let op3: Operation = new Operation(OperationType.Add, 3); + let deps3: string[] = [vertexHash1]; + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3, peerId); + linearOps = cro.hashGraph.linearizeOps(); + expect([[op0, op1, op2, op3], [op0, op1, op3, op2]]).toContainEqual(linearOps); + }); + + test("Test: Tricky Case", () => { + /* + ___ V2:REMOVE(1) <- V4:ADD(10) + V1:ADD(1) / + \ ___ V3:ADD(1) <- V5:REMOVE(5) + */ + + let op1: Operation = new Operation(OperationType.Add, 1); + let deps1: string[] = [vertexHash0] + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1, peerId); + + // Add second vertex + let op2: Operation = new Operation(OperationType.Remove, 1); + let deps2: string[] = [vertexHash1]; + let vertexHash2 = cro.hashGraph.addVertex(op2, deps2, peerId); + + // Add the third vertex V3 concurrent with V2 + let op3: Operation = new Operation(OperationType.Add, 1); + let deps3: string[] = [vertexHash1]; + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3, peerId); + + // Add the vertex V4 with dependency on V2 + let op4: Operation = new Operation(OperationType.Add, 10); + let deps4: string[] = [vertexHash2]; + let vertexHash4 = cro.hashGraph.addVertex(op4, deps4, peerId); + + // Add the vertex V5 with dependency on V3 + let op5: Operation = new Operation(OperationType.Remove, 5); + let deps5: string[] = [vertexHash3]; + let vertexHash5 = cro.hashGraph.addVertex(op5, deps5, peerId); + const linearOps = cro.hashGraph.linearizeOps(); + expect([[op0, op1, op4, op3, op5], [op0, op1, op3, op5, op4]]).toContainEqual(linearOps); + + }); + + test("Test: Yuta Papa's Case", () => { + /* + ___ V2:REMOVE(1) <- V4:ADD(2) + V1:ADD(1) / + \ ___ V3:REMOVE(2) <- V5:ADD(1) + */ + + let op1: Operation = new Operation(OperationType.Add, 1); + let deps1: string[] = [vertexHash0] + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1, peerId); + + // Add second vertex + let op2: Operation = new Operation(OperationType.Remove, 1); + let deps2: string[] = [vertexHash1]; + let vertexHash2 = cro.hashGraph.addVertex(op2, deps2, peerId); + + + // Add the third vertex V3 concurrent with V2 + let op3: Operation = new Operation(OperationType.Remove, 2); + let deps3: string[] = [vertexHash1]; + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3, peerId); + + // Add the vertex V4 with dependency on V2 + let op4: Operation = new Operation(OperationType.Add, 2); + let deps4: string[] = [vertexHash2]; + let vertexHash4 = cro.hashGraph.addVertex(op4, deps4, peerId); + + // Add the vertex V5 with dependency on V3 + let op5: Operation = new Operation(OperationType.Add, 1); + let deps5: string[] = [vertexHash3]; + let vertexHash5 = cro.hashGraph.addVertex(op5, deps5, peerId); + const linearOps = cro.hashGraph.linearizeOps(); + expect([[op0, op1, op4, op5], [op0, op1, op5, op4]]).toContainEqual(linearOps); + }); + + test("Test: Mega Complex Case", () => { + /* + __ V6:ADD(3) + / + ___ V2:ADD(1) <-- V3:RM(2) <-- V7:RM(1) <-- V8:RM(3) + / ______________/ + V1:ADD(1)/ / + \ / + \ ___ V4:RM(2) <-- V5:ADD(2) <-- V9:RM(1) + + Topological Sorted Array: + [V1, V4, V5, V9, V2, V3, V7, V8, V6] + OR + [V1, V2, V3, V6, V7, V4, V5, V8, V9] + OR + [V1, V2, V3, V6, V7, V4, V5, V9, V8] + */ + + let op1: Operation = new Operation(OperationType.Add, 1); + let deps1: string[] = [vertexHash0] + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1, peerId); + console.log("vertex1: ", vertexHash1); + // Add second vertex + let op2: Operation = new Operation(OperationType.Add, 1); + let deps2: string[] = [vertexHash1]; + let vertexHash2 = cro.hashGraph.addVertex(op2, deps2, peerId); + console.log("vertex2: ", vertexHash2); + // Add the third vertex V3 with dependency on V2 + let op3: Operation = new Operation(OperationType.Remove, 2); + let deps3: string[] = [vertexHash2]; + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3, peerId); + console.log("vertex3: ", vertexHash3); + // Add the vertex V4 -> [V1] + let op4: Operation = new Operation(OperationType.Remove, 2); + let deps4: string[] = [vertexHash1]; + let vertexHash4 = cro.hashGraph.addVertex(op4, deps4, peerId); + console.log("vertex4: ", vertexHash4); + // Add the vertex V5 -> [V4] + let op5: Operation = new Operation(OperationType.Add, 2); + let deps5: string[] = [vertexHash4]; + let vertexHash5 = cro.hashGraph.addVertex(op5, deps5, peerId); + console.log("vertex5: ", vertexHash5); + // Add the vertex V6 ->[V3] + let op6: Operation = new Operation(OperationType.Add, 3); + let deps6: string[] = [vertexHash3]; + let vertexHash6 = cro.hashGraph.addVertex(op6, deps6, peerId); + console.log("vertex6: ", vertexHash6); + // Add the vertex V7 -> [V3] + let op7: Operation = new Operation(OperationType.Remove, 1); + let deps7: string[] = [vertexHash3]; + let vertexHash7 = cro.hashGraph.addVertex(op7, deps7, peerId); + console.log("vertex7: ", vertexHash7); + // Add the vertex V8 -> [V7, V5] + let op8: Operation = new Operation(OperationType.Remove, 3); + let deps8: string[] = [vertexHash7, vertexHash5]; + let vertexHash8 = cro.hashGraph.addVertex(op8, deps8, peerId); + console.log("vertex8: ", vertexHash8); + // Add the vertex V9 -> [V5] + let op9: Operation = new Operation(OperationType.Remove, 1); + let deps9: string[] = [vertexHash5]; + let vertexHash9 = cro.hashGraph.addVertex(op9, deps9, peerId); + console.log("vertex9: ", vertexHash9); + + let sortedOrder = cro.hashGraph.topologicalSort(); + expect([[vertexHash0, vertexHash1, vertexHash4, vertexHash5, vertexHash9, vertexHash2, vertexHash3, vertexHash7, vertexHash8, vertexHash6]]).toContainEqual(sortedOrder); + console.log(sortedOrder) + let linearOps = cro.hashGraph.linearizeOps(); + // expect([[op0, op1, op2, op6, op7, op4, op5], [op0, op1, op4, op5, op9, op2, op3, op]]).toContainEqual(linearOps); + }); + + test("Test: Mega Complex Case 1", () => { + /* + __ V6:ADD(3) + / + ___ V2:ADD(1) <-- V3:RM(2) <-- V7:RM(1) <-- V8:RM(3) + / ^ + V1:ADD(1)/ \ + \ \ + \ ___ V4:RM(2) <-------------------- V5:ADD(2) <-- V9:RM(1) + 6, 7, 8, 3, 2, 9, 5, 4, 1 + Topological Sorted Array: + [V1, V2, V3, V6, V4, V5, V9, V7, V8] + OR + [V1, V4, V2, V3, V7, V8, V6, V5, V9] + OR + [1, 4, 2, 3, 7, 5, 9, 8, 6] + */ + + let op1: Operation = new Operation(OperationType.Add, 1); + let deps1: string[] = [vertexHash0] + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1, peerId); + console.log("vertex1: ", vertexHash1); + // Add second vertex + let op2: Operation = new Operation(OperationType.Add, 1); + let deps2: string[] = [vertexHash1]; + let vertexHash2 = cro.hashGraph.addVertex(op2, deps2, peerId); + console.log("vertex2: ", vertexHash2); + // Add the third vertex V3 with dependency on V2 + let op3: Operation = new Operation(OperationType.Remove, 2); + let deps3: string[] = [vertexHash2]; + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3, peerId); + console.log("vertex3: ", vertexHash3); + // Add the vertex V4 -> [V1] + let op4: Operation = new Operation(OperationType.Remove, 2); + let deps4: string[] = [vertexHash1]; + let vertexHash4 = cro.hashGraph.addVertex(op4, deps4, peerId); + console.log("vertex4: ", vertexHash4); + // Add the vertex V6 ->[V3] + let op6: Operation = new Operation(OperationType.Add, 3); + let deps6: string[] = [vertexHash3]; + let vertexHash6 = cro.hashGraph.addVertex(op6, deps6, peerId); + console.log("vertex6: ", vertexHash6); + // Add the vertex V7 -> [V3] + let op7: Operation = new Operation(OperationType.Remove, 1); + let deps7: string[] = [vertexHash3]; + let vertexHash7 = cro.hashGraph.addVertex(op7, deps7, peerId); + console.log("vertex7: ", vertexHash7); + // Add the vertex V5 -> [V4, V7] + let op5: Operation = new Operation(OperationType.Add, 2); + let deps5: string[] = [vertexHash4, vertexHash7]; + let vertexHash5 = cro.hashGraph.addVertex(op5, deps5, peerId); + console.log("vertex5: ", vertexHash5); + // Add the vertex V8 -> [V7] + let op8: Operation = new Operation(OperationType.Remove, 3); + let deps8: string[] = [vertexHash7, vertexHash5]; + let vertexHash8 = cro.hashGraph.addVertex(op8, deps8, peerId); + console.log("vertex8: ", vertexHash8); + // Add the vertex V9 -> [V5] + let op9: Operation = new Operation(OperationType.Remove, 1); + let deps9: string[] = [vertexHash5]; + let vertexHash9 = cro.hashGraph.addVertex(op9, deps9, peerId); + console.log("vertex9: ", vertexHash9); + + let sortedOrder = cro.hashGraph.topologicalSort(); + console.log(sortedOrder) + // expect([[op0, op1, op2, op3, op6, op4, op5, op9, op7, op8]]).toContainEqual(sortedOrder); + }); + + test("Test: Joao's latest brain teaser", () => { + /* + + __ V2:Add(2) <------------\ + V1:Add(1) / \ - V5:RM(2) + \__ V3:RM(2) <- V4:RM(2) <--/ + + */ + let op1: Operation = new Operation(OperationType.Add, 1); + let deps1: string[] = [vertexHash0] + let vertexHash1 = cro.hashGraph.addVertex(op1, deps1, peerId); + + // Add the second vertex V2 <- [V1] + let op2: Operation = new Operation(OperationType.Add, 2); + let deps2: string[] = [vertexHash1]; + let vertexHash2 = cro.hashGraph.addVertex(op2, deps2, peerId); + + // Add the third vertex V3 <- [V1] + let op3: Operation = new Operation(OperationType.Remove, 2); + let deps3: string[] = [vertexHash1]; + let vertexHash3 = cro.hashGraph.addVertex(op3, deps3, peerId); + + // Add the fourth vertex V4 <- [V3] + let op4: Operation = new Operation(OperationType.Remove, 2); + let deps4: string[] = [vertexHash3]; + let vertexHash4 = cro.hashGraph.addVertex(op4, deps4, peerId); + + // Add the fifth vertex V5 <- [V2, V4] + let op5: Operation = new Operation(OperationType.Remove, 2); + let deps5: string[] = [vertexHash2, vertexHash4]; + let vertexHash5 = cro.hashGraph.addVertex(op5, deps5, peerId); + + const linearOps = cro.hashGraph.linearizeOps(); + expect(linearOps).toEqual([op0, op1, op2, op5]); + }); +}); diff --git a/packages/crdt/tests/RGA.test.ts b/packages/crdt/tests/RGA.test.ts index 5b2c70a8..b438d48b 100644 --- a/packages/crdt/tests/RGA.test.ts +++ b/packages/crdt/tests/RGA.test.ts @@ -1,91 +1,91 @@ import { describe, test, expect, beforeEach } from "vitest"; -import { RGA } from "../src/builtins/RGA"; // Adjust the import path according to your project structure +import { RGA } from "../src/crdts/RGA"; describe("Replicable Growable Array Tests", () => { - let rga: RGA; - let peerRGA: RGA; - - beforeEach(() => { - rga = new RGA("node1"); - peerRGA = new RGA("node2"); - }); - - test("Test Insert", () => { - rga.insert(0, "A"); - rga.insert(1, "B"); - rga.insert(1, "C"); - rga.insert(0, "D"); - - expect(rga.getArray()).toEqual(["D", "A", "C", "B"]); - }); - - test("Test Read", () => { - rga.insert(0, "A"); - rga.insert(1, "B"); - rga.insert(1, "C"); - rga.delete(1); - - expect(rga.read(0)).toBe("A"); - expect(rga.read(1)).toBe("B"); - }); - - test("Test Insert and Delete", () => { - rga.insert(0, "A"); - rga.insert(1, "B"); - rga.insert(1, "C"); - rga.delete(0); - rga.delete(0); - expect(rga.getArray()).toEqual(["B"]); - - rga.clear(); - - rga.insert(0, "A"); - rga.insert(1, "B"); - rga.delete(0); - - expect(rga.getArray()).toEqual(["B"]); - - rga.insert(0, "C"); - rga.insert(1, "D"); - expect(rga.getArray()).toEqual(["C", "D", "B"]); - - rga.delete(1); - expect(rga.getArray()).toEqual(["C", "B"]); - - rga.delete(1); - expect(rga.getArray()).toEqual(["C"]); - - peerRGA.insert(0, "E"); - peerRGA.insert(0, "F"); - peerRGA.insert(2, "G"); - peerRGA.insert(3, "H"); - peerRGA.delete(1); - peerRGA.delete(1); - peerRGA.delete(1); - expect(peerRGA.getArray()).toEqual(["F"]); - }); - - test("Test Update", () => { - rga.insert(0, "A"); - rga.insert(1, "B"); - rga.update(0, "C"); - rga.update(1, "D"); - - expect(rga.getArray()).toEqual(["C", "D"]); - }); - - test("Test Merge", () => { - rga.insert(0, "A"); - rga.insert(1, "B"); - - peerRGA.insert(0, "C"); - peerRGA.insert(1, "D"); - peerRGA.insert(0, "E"); - - rga.merge(peerRGA); - expect(rga.getArray()).toEqual(["E", "C", "A", "D", "B"]); - - peerRGA.merge(rga); - expect(peerRGA.getArray()).toEqual(rga.getArray()); - }); + let rga: RGA; + let peerRGA: RGA; + + beforeEach(() => { + rga = new RGA("node1"); + peerRGA = new RGA("node2"); + }); + + test("Test Insert", () => { + rga.insert(0, "A"); + rga.insert(1, "B"); + rga.insert(1, "C"); + rga.insert(0, "D"); + + expect(rga.getArray()).toEqual(["D", "A", "C", "B"]); + }); + + test("Test Read", () => { + rga.insert(0, "A"); + rga.insert(1, "B"); + rga.insert(1, "C"); + rga.delete(1); + + expect(rga.read(0)).toBe("A"); + expect(rga.read(1)).toBe("B"); + }); + + test("Test Insert and Delete", () => { + rga.insert(0, "A"); + rga.insert(1, "B"); + rga.insert(1, "C"); + rga.delete(0); + rga.delete(0); + expect(rga.getArray()).toEqual(["B"]); + + rga.clear(); + + rga.insert(0, "A"); + rga.insert(1, "B"); + rga.delete(0); + + expect(rga.getArray()).toEqual(["B"]); + + rga.insert(0, "C"); + rga.insert(1, "D"); + expect(rga.getArray()).toEqual(["C", "D", "B"]); + + rga.delete(1); + expect(rga.getArray()).toEqual(["C", "B"]); + + rga.delete(1); + expect(rga.getArray()).toEqual(["C"]); + + peerRGA.insert(0, "E"); + peerRGA.insert(0, "F"); + peerRGA.insert(2, "G"); + peerRGA.insert(3, "H"); + peerRGA.delete(1); + peerRGA.delete(1); + peerRGA.delete(1); + expect(peerRGA.getArray()).toEqual(["F"]); + }); + + test("Test Update", () => { + rga.insert(0, "A"); + rga.insert(1, "B"); + rga.update(0, "C"); + rga.update(1, "D"); + + expect(rga.getArray()).toEqual(["C", "D"]); + }); + + test("Test Merge", () => { + rga.insert(0, "A"); + rga.insert(1, "B"); + + peerRGA.insert(0, "C"); + peerRGA.insert(1, "D"); + peerRGA.insert(0, "E"); + + rga.merge(peerRGA); + expect(rga.getArray()).toEqual(["E", "C", "A", "D", "B"]); + + peerRGA.merge(rga); + expect(peerRGA.getArray()).toEqual(rga.getArray()); + }); }); diff --git a/packages/crdt/tsconfig.json b/packages/crdt/tsconfig.json index 280f82e7..3bbb9415 100644 --- a/packages/crdt/tsconfig.json +++ b/packages/crdt/tsconfig.json @@ -3,5 +3,10 @@ "compilerOptions": { "outDir": "dist" }, + "references": [ + { + "path": "../object" + } + ], "include": ["src/**/*.ts"] } diff --git a/packages/node/src/version.ts b/packages/node/src/version.ts index 85ceb686..2d05f320 100644 --- a/packages/node/src/version.ts +++ b/packages/node/src/version.ts @@ -1 +1 @@ -export const VERSION = "0.0.22"; +export const VERSION = "0.0.23-5"; diff --git a/packages/object/src/hashgraph.ts b/packages/object/src/hashgraph.ts new file mode 100644 index 00000000..2fec2f97 --- /dev/null +++ b/packages/object/src/hashgraph.ts @@ -0,0 +1,252 @@ +import * as crypto from "crypto"; + +type Hash = string; + +class Vertex { + constructor( + readonly hash: Hash, + readonly operation: Operation, + readonly dependencies: Set + ) { } +} + +export enum ActionType { + DropLeft, + DropRight, + Nop, + Swap +} + +export enum OperationType { + Add, + Remove, + Nop +} + +export class Operation { + constructor( + readonly type: OperationType, + readonly value: T + ) { } +} + +export interface IHashGraph { + addVertex(op: T, deps: Hash[], nodeId: string): Hash; + addToFrontier(op: T): Hash; + topologicalSort(): Hash[]; + areCausallyRelated(vertexHash1: Hash, vertexHash2: Hash): boolean; + getFrontier(): Hash[]; + getDependencies(vertexHash: Hash): Hash[] | undefined; + getVertex(vertexHash: Hash): Vertex | undefined; + getAllVertices(): Vertex[]; +} + +export class HashGraph { + private vertices: Map> = new Map(); + private frontier: Set = new Set(); + private forwardEdges: Map> = new Map(); + rootHash: Hash = ""; + + constructor(private resolveConflicts: (op1: Operation, op2: Operation) => ActionType, private nodeId: string) { + // Create and add the NOP root vertex + const nopOperation = new Operation(OperationType.Nop, 0 as T); + this.rootHash = this.computeHash(nopOperation, [], ""); + const rootVertex = new Vertex(this.rootHash, nopOperation, new Set()); + this.vertices.set(this.rootHash, rootVertex); + this.frontier.add(this.rootHash); + this.forwardEdges.set(this.rootHash, new Set()); + } + + // Time complexity: O(1), Space complexity: O(1) + private computeHash(op: Operation, deps: Hash[], nodeId: String): Hash { + const serialized = JSON.stringify({ op, deps, nodeId }); + const hash = crypto + .createHash("sha256") + .update(serialized) + .digest("hex"); + + return hash; + } + + addToFrontier(operation: Operation): Hash { + const deps = this.getFrontier(); + const hash = this.computeHash(operation, deps, this.nodeId) + const vertex = new Vertex(hash, operation, new Set(deps)); + + this.vertices.set(hash, vertex); + this.frontier.add(hash); + + // Update forward edges + for (const dep of deps) { + if (!this.forwardEdges.has(dep)) { + this.forwardEdges.set(dep, new Set()); + } + this.forwardEdges.get(dep)!.add(hash); + this.frontier.delete(dep); + } + return hash; + } + // Time complexity: O(d), where d is the number of dependencies + // Space complexity: O(d) + addVertex(op: Operation, deps: Hash[], nodeId: string): Hash { + + // Temporary fix: don't add the vertex if the dependencies are not present in the local HG. + if (!deps.every(dep => this.forwardEdges.has(dep) || this.vertices.has(dep))) { + console.log("Invalid dependency detected."); + return "" + } + + const hash = this.computeHash(op, deps, nodeId); + if (this.vertices.has(hash)) { + return hash; // Vertex already exists + } + + const vertex = new Vertex(hash, op, new Set(deps)); + this.vertices.set(hash, vertex); + this.frontier.add(hash); + + // Update forward edges + for (const dep of deps) { + if (!this.forwardEdges.has(dep)) { + this.forwardEdges.set(dep, new Set()); + } + this.forwardEdges.get(dep)!.add(hash); + this.frontier.delete(dep); + } + + return hash; + } + + // Time complexity: O(V + E), Space complexity: O(V) + topologicalSort(): Hash[] { + const result: Hash[] = []; + const visited = new Set(); + + const visit = (hash: Hash) => { + if (visited.has(hash)) return; + + visited.add(hash); + + const children = this.forwardEdges.get(hash) || new Set(); + for (const child of children) { + visit(child); + } + result.push(hash); + }; + // Start with the root vertex + visit(this.rootHash); + + return result.reverse(); + } + + linearizeOps(): Operation[] { + const order = this.topologicalSort(); + const result: Operation[] = []; + let i = 0; + + while (i < order.length) { + const anchor = order[i]; + let j = i + 1; + let shouldIncrementI = true; + + while (j < order.length) { + const moving = order[j]; + + if (!this.areCausallyRelated(anchor, moving)) { + const op1 = this.vertices.get(anchor)!.operation; + const op2 = this.vertices.get(moving)!.operation; + const action = this.resolveConflicts(op1, op2); + + switch (action) { + case ActionType.DropLeft: + order.splice(i, 1); + j = order.length; // Break out of inner loop + shouldIncrementI = false; + continue; // Continue outer loop without incrementing i + case ActionType.DropRight: + order.splice(j, 1); + continue; // Continue with the same j + case ActionType.Swap: + [order[i], order[j]] = [order[j], order[i]]; + j = order.length; // Break out of inner loop + break; + case ActionType.Nop: + j++; + break; + } + } else { + j++; + } + } + + if (shouldIncrementI) { + result.push(this.vertices.get(order[i])!.operation); + i++; + } + } + + return result; + } + + // Time complexity: O(V), Space complexity: O(V) + areCausallyRelated(hash1: Hash, hash2: Hash): boolean { + const visited = new Set(); + const stack = [hash1]; + + while (stack.length > 0) { + const current = stack.pop()!; + if (current === hash2) return true; + visited.add(current); + + const vertex = this.vertices.get(current)!; + for (const dep of vertex.dependencies) { + if (!visited.has(dep)) { + stack.push(dep); + } + } + } + + visited.clear(); + stack.push(hash2); + + while (stack.length > 0) { + const current = stack.pop()!; + if (current === hash1) return true; + visited.add(current); + + const vertex = this.vertices.get(current)!; + for (const dep of vertex.dependencies) { + if (!visited.has(dep)) { + stack.push(dep); + } + } + } + + return false; + } + + // Time complexity: O(1), Space complexity: O(1) + getFrontier(): Hash[] { + return Array.from(this.frontier); + } + + // Time complexity: O(1), Space complexity: O(1) + getRoot(): Hash { + return this.rootHash; + } + + // Time complexity: O(1), Space complexity: O(1) + getDependencies(vertexHash: Hash): Hash[] { + return Array.from(this.vertices.get(vertexHash)?.dependencies || []); + } + + // Time complexity: O(1), Space complexity: O(1) + getVertex(hash: Hash): Vertex | undefined { + return this.vertices.get(hash); + } + + // Time complexity: O(V), Space complexity: O(V) + getAllVertices(): Vertex[] { + return Array.from(this.vertices.values()); + } +} diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 380bc870..b4b13ccd 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -1,5 +1,7 @@ import * as crypto from "crypto"; +export * from "./hashgraph.js" + export abstract class TopologyObject { // TODO generate functions from the abi private abi?: string; diff --git a/yarn.lock b/yarn.lock index d5d10e55..58271020 100644 --- a/yarn.lock +++ b/yarn.lock @@ -287,10 +287,10 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -973,85 +973,85 @@ walk-sync "^2.0.2" yaml "^2.1.1" -"@rollup/rollup-android-arm-eabi@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" - integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== - -"@rollup/rollup-android-arm64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" - integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== - -"@rollup/rollup-darwin-arm64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" - integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== - -"@rollup/rollup-darwin-x64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c" - integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== - -"@rollup/rollup-linux-arm-gnueabihf@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" - integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== - -"@rollup/rollup-linux-arm-musleabihf@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" - integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== - -"@rollup/rollup-linux-arm64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" - integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== - -"@rollup/rollup-linux-arm64-musl@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" - integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== - -"@rollup/rollup-linux-powerpc64le-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" - integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== - -"@rollup/rollup-linux-riscv64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" - integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== - -"@rollup/rollup-linux-s390x-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" - integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== - -"@rollup/rollup-linux-x64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942" - integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== - -"@rollup/rollup-linux-x64-musl@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d" - integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== - -"@rollup/rollup-win32-arm64-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" - integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== - -"@rollup/rollup-win32-ia32-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" - integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== - -"@rollup/rollup-win32-x64-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" - integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== +"@rollup/rollup-android-arm-eabi@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz#c3f5660f67030c493a981ac1d34ee9dfe1d8ec0f" + integrity sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA== + +"@rollup/rollup-android-arm64@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz#64161f0b67050023a3859e723570af54a82cff5c" + integrity sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ== + +"@rollup/rollup-darwin-arm64@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz#25f3d57b1da433097cfebc89341b355901615763" + integrity sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q== + +"@rollup/rollup-darwin-x64@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz#d8ddaffb636cc2f59222c50316e27771e48966df" + integrity sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz#41bd4fcffa20fb84f3dbac6c5071638f46151885" + integrity sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA== + +"@rollup/rollup-linux-arm-musleabihf@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz#842077c5113a747eb5686f19f2f18c33ecc0acc8" + integrity sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw== + +"@rollup/rollup-linux-arm64-gnu@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz#65d1d5b6778848f55b7823958044bf3e8737e5b7" + integrity sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ== + +"@rollup/rollup-linux-arm64-musl@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz#50eef7d6e24d0fe3332200bb666cad2be8afcf86" + integrity sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q== + +"@rollup/rollup-linux-powerpc64le-gnu@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz#8837e858f53c84607f05ad0602943e96d104c6b4" + integrity sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw== + +"@rollup/rollup-linux-riscv64-gnu@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz#c894ade2300caa447757ddf45787cca246e816a4" + integrity sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA== + +"@rollup/rollup-linux-s390x-gnu@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz#5841e5390d4c82dd5cdf7b2c95a830e3c2f47dd3" + integrity sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg== + +"@rollup/rollup-linux-x64-gnu@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz#cc1f26398bf777807a99226dc13f47eb0f6c720d" + integrity sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew== + +"@rollup/rollup-linux-x64-musl@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz#1507465d9056e0502a590d4c1a00b4d7b1fda370" + integrity sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg== + +"@rollup/rollup-win32-arm64-msvc@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz#86a221f01a2c248104dd0defb4da119f2a73642e" + integrity sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA== + +"@rollup/rollup-win32-ia32-msvc@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz#8bc8f77e02760aa664694b4286d6fbea7f1331c5" + integrity sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A== + +"@rollup/rollup-win32-x64-msvc@4.20.0": + version "4.20.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz#601fffee719a1e8447f908aca97864eec23b2784" + integrity sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg== "@shikijs/core@1.10.3": version "1.10.3" @@ -2321,7 +2321,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.3.4, debug@^4.3.5: +debug@4, debug@^4.1.0, debug@^4.3.4: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== @@ -2335,6 +2335,13 @@ debug@4.3.4: dependencies: ms "2.1.2" +debug@^4.3.5: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + dependencies: + ms "2.1.2" + decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -4054,11 +4061,11 @@ macos-release@^3.1.0: integrity sha512-fSErXALFNsnowREYZ49XCdOHF8wOPWuFOGQrAhP7x5J/BqQv+B02cNsTykGpDgRVx43EKg++6ANmTaGTtW+hUA== magic-string@^0.30.10: - version "0.30.10" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e" - integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ== + version "0.30.11" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954" + integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A== dependencies: - "@jridgewell/sourcemap-codec" "^1.4.15" + "@jridgewell/sourcemap-codec" "^1.5.0" make-error@^1.1.1: version "1.3.6" @@ -4737,10 +4744,10 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -postcss@^8.4.39: - version "8.4.39" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.39.tgz#aa3c94998b61d3a9c259efa51db4b392e1bde0e3" - integrity sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw== +postcss@^8.4.40: + version "8.4.41" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681" + integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ== dependencies: nanoid "^3.3.7" picocolors "^1.0.1" @@ -5151,28 +5158,28 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: inherits "^2.0.1" rollup@^4.13.0: - version "4.18.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.18.0.tgz#497f60f0c5308e4602cf41136339fbf87d5f5dda" - integrity sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg== + version "4.20.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.20.0.tgz#f9d602161d29e178f0bf1d9f35f0a26f83939492" + integrity sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw== dependencies: "@types/estree" "1.0.5" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.18.0" - "@rollup/rollup-android-arm64" "4.18.0" - "@rollup/rollup-darwin-arm64" "4.18.0" - "@rollup/rollup-darwin-x64" "4.18.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.18.0" - "@rollup/rollup-linux-arm-musleabihf" "4.18.0" - "@rollup/rollup-linux-arm64-gnu" "4.18.0" - "@rollup/rollup-linux-arm64-musl" "4.18.0" - "@rollup/rollup-linux-powerpc64le-gnu" "4.18.0" - "@rollup/rollup-linux-riscv64-gnu" "4.18.0" - "@rollup/rollup-linux-s390x-gnu" "4.18.0" - "@rollup/rollup-linux-x64-gnu" "4.18.0" - "@rollup/rollup-linux-x64-musl" "4.18.0" - "@rollup/rollup-win32-arm64-msvc" "4.18.0" - "@rollup/rollup-win32-ia32-msvc" "4.18.0" - "@rollup/rollup-win32-x64-msvc" "4.18.0" + "@rollup/rollup-android-arm-eabi" "4.20.0" + "@rollup/rollup-android-arm64" "4.20.0" + "@rollup/rollup-darwin-arm64" "4.20.0" + "@rollup/rollup-darwin-x64" "4.20.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.20.0" + "@rollup/rollup-linux-arm-musleabihf" "4.20.0" + "@rollup/rollup-linux-arm64-gnu" "4.20.0" + "@rollup/rollup-linux-arm64-musl" "4.20.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.20.0" + "@rollup/rollup-linux-riscv64-gnu" "4.20.0" + "@rollup/rollup-linux-s390x-gnu" "4.20.0" + "@rollup/rollup-linux-x64-gnu" "4.20.0" + "@rollup/rollup-linux-x64-musl" "4.20.0" + "@rollup/rollup-win32-arm64-msvc" "4.20.0" + "@rollup/rollup-win32-ia32-msvc" "4.20.0" + "@rollup/rollup-win32-x64-msvc" "4.20.0" fsevents "~2.3.2" run-applescript@^7.0.0: @@ -5712,9 +5719,9 @@ thunky@^1.0.2: integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== tinybench@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.8.0.tgz#30e19ae3a27508ee18273ffed9ac7018949acd7b" - integrity sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw== + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== tinypool@^1.0.0: version "1.0.0" @@ -6007,12 +6014,12 @@ vite-node@2.0.5: vite "^5.0.0" vite@^5.0.0: - version "5.3.3" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.3.tgz#5265b1f0a825b3b6564c2d07524777c83e3c04c2" - integrity sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A== + version "5.4.0" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.0.tgz#11dca8a961369ba8b5cae42d068c7ad684d5370f" + integrity sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg== dependencies: esbuild "^0.21.3" - postcss "^8.4.39" + postcss "^8.4.40" rollup "^4.13.0" optionalDependencies: fsevents "~2.3.3"