diff --git a/biome.json b/biome.json index 0bf72dbc..9b0a0d04 100644 --- a/biome.json +++ b/biome.json @@ -7,7 +7,8 @@ "**/doc/*", "**/*_pb.js", "**/*_pb.ts", - "**/bundle/*" + "**/bundle/*", + "coverage/*" ] }, "linter": { diff --git a/examples/canvas/src/index.ts b/examples/canvas/src/index.ts index 45d01419..1394a4ec 100644 --- a/examples/canvas/src/index.ts +++ b/examples/canvas/src/index.ts @@ -46,7 +46,7 @@ function paint_pixel(pixel: HTMLDivElement) { random_int(256), ]; canvasDRP.paint([x, y], painting); - const [r, g, b] = canvasDRP.pixel(x, y).color(); + const [r, g, b] = canvasDRP.query_pixel(x, y).color(); pixel.style.backgroundColor = `rgb(${r}, ${g}, ${b})`; } diff --git a/examples/canvas/src/objects/canvas.ts b/examples/canvas/src/objects/canvas.ts index 2df0d461..fefe7de5 100644 --- a/examples/canvas/src/objects/canvas.ts +++ b/examples/canvas/src/objects/canvas.ts @@ -7,7 +7,6 @@ import { import { Pixel } from "./pixel"; export class Canvas implements DRP { - operations: string[] = ["splash", "paint"]; semanticsType: SemanticsType = SemanticsType.pair; width: number; @@ -26,18 +25,6 @@ export class Canvas implements DRP { offset: [number, number], size: [number, number], rgb: [number, number, number], - ): void { - this._splash(offset, size, rgb); - } - - paint(offset: [number, number], rgb: [number, number, number]): void { - this._paint(offset, rgb); - } - - private _splash( - offset: [number, number], - size: [number, number], - rgb: [number, number, number], ): void { if (offset[0] < 0 || this.width < offset[0]) return; if (offset[1] < 0 || this.height < offset[1]) return; @@ -49,17 +36,14 @@ export class Canvas implements DRP { } } - private _paint( - offset: [number, number], - rgb: [number, number, number], - ): void { + paint(offset: [number, number], rgb: [number, number, number]): void { if (offset[0] < 0 || this.canvas.length < offset[0]) return; if (offset[1] < 0 || this.canvas[offset[0]].length < offset[1]) return; this.canvas[offset[0]][offset[1]].paint(rgb); } - pixel(x: number, y: number): Pixel { + query_pixel(x: number, y: number): Pixel { return this.canvas[x][y]; } diff --git a/examples/chat/src/index.ts b/examples/chat/src/index.ts index a2f6a951..dd04da24 100644 --- a/examples/chat/src/index.ts +++ b/examples/chat/src/index.ts @@ -30,7 +30,7 @@ const render = () => { element_objectPeers.innerHTML = `[${objectPeers.join(", ")}]`; if (!chatDRP) return; - const chat = chatDRP.getMessages(); + const chat = chatDRP.query_messages(); const element_chat = document.getElementById("chat"); element_chat.innerHTML = ""; diff --git a/examples/chat/src/objects/chat.ts b/examples/chat/src/objects/chat.ts index 3dce2472..eb45bfc9 100644 --- a/examples/chat/src/objects/chat.ts +++ b/examples/chat/src/objects/chat.ts @@ -7,7 +7,6 @@ import { } from "@ts-drp/object"; export class Chat implements DRP { - operations: string[] = ["addMessage"]; semanticsType: SemanticsType = SemanticsType.pair; // store messages as strings in the format (timestamp, message, peerId) messages: Set; @@ -16,18 +15,10 @@ export class Chat implements DRP { } addMessage(timestamp: string, message: string, peerId: string): void { - this._addMessage(timestamp, message, peerId); - } - - private _addMessage( - timestamp: string, - message: string, - peerId: string, - ): void { this.messages.add(`(${timestamp}, ${message}, ${peerId})`); } - getMessages(): Set { + query_messages(): Set { return this.messages; } diff --git a/examples/grid/src/index.ts b/examples/grid/src/index.ts index 14ef435d..6a49f2b8 100644 --- a/examples/grid/src/index.ts +++ b/examples/grid/src/index.ts @@ -82,7 +82,7 @@ const render = () => { : `Your frens in GRID: [${objectPeers.map((peer) => `${formatPeerId(peer)}`).join(", ")}]`; if (!gridDRP) return; - const users = gridDRP.getUsers(); + const users = gridDRP.query_users(); const element_grid = document.getElementById("grid"); element_grid.innerHTML = ""; @@ -119,7 +119,7 @@ const render = () => { for (const userColorString of users) { const [id, color] = userColorString.split(":"); - const position = gridDRP.getUserPosition(userColorString); + const position = gridDRP.query_userPosition(userColorString); if (position) { const div = document.createElement("div"); diff --git a/examples/grid/src/objects/grid.ts b/examples/grid/src/objects/grid.ts index 2a331340..81111e15 100644 --- a/examples/grid/src/objects/grid.ts +++ b/examples/grid/src/objects/grid.ts @@ -7,7 +7,6 @@ import { } from "@ts-drp/object"; export class Grid implements DRP { - operations: string[] = ["addUser", "moveUser"]; semanticsType: SemanticsType = SemanticsType.pair; positions: Map; @@ -16,19 +15,11 @@ export class Grid implements DRP { } addUser(userId: string, color: string): void { - this._addUser(userId, color); - } - - private _addUser(userId: string, color: string): void { const userColorString = `${userId}:${color}`; this.positions.set(userColorString, { x: 0, y: 0 }); } moveUser(userId: string, direction: string): void { - this._moveUser(userId, direction); - } - - private _moveUser(userId: string, direction: string): void { const userColorString = [...this.positions.keys()].find((u) => u.startsWith(`${userId}:`), ); @@ -53,11 +44,11 @@ export class Grid implements DRP { } } - getUsers(): string[] { + query_users(): string[] { return [...this.positions.keys()]; } - getUserPosition( + query_userPosition( userColorString: string, ): { x: number; y: number } | undefined { const position = this.positions.get(userColorString); diff --git a/packages/blueprints/src/ACL/index.ts b/packages/blueprints/src/ACL/index.ts index fcda1f33..91c0e15c 100644 --- a/packages/blueprints/src/ACL/index.ts +++ b/packages/blueprints/src/ACL/index.ts @@ -13,7 +13,6 @@ export enum ACLConflictResolution { } export class ACL implements IACL, DRP { - operations: string[] = ["grant", "revoke"]; semanticsType = SemanticsType.pair; private _conflictResolution: ACLConflictResolution; @@ -35,7 +34,7 @@ export class ACL implements IACL, DRP { } grant(senderId: string, peerId: string, publicKey: string): void { - if (!this.isAdmin(senderId)) { + if (!this.query_isAdmin(senderId)) { throw new Error("Only admin nodes can grant permissions."); } this._grant(peerId, publicKey); @@ -46,10 +45,10 @@ export class ACL implements IACL, DRP { } revoke(senderId: string, peerId: string): void { - if (!this.isAdmin(senderId)) { + if (!this.query_isAdmin(senderId)) { throw new Error("Only admin nodes can revoke permissions."); } - if (this.isAdmin(peerId)) { + if (this.query_isAdmin(peerId)) { throw new Error( "Cannot revoke permissions from a node with admin privileges.", ); @@ -57,15 +56,15 @@ export class ACL implements IACL, DRP { this._revoke(peerId); } - isAdmin(peerId: string): boolean { + query_isAdmin(peerId: string): boolean { return this._admins.has(peerId); } - isWriter(peerId: string): boolean { + query_isWriter(peerId: string): boolean { return this._writers.has(peerId); } - getPeerKey(peerId: string): string | undefined { + query_getPeerKey(peerId: string): string | undefined { return this._writers.get(peerId); } diff --git a/packages/blueprints/src/AddWinsSet/index.ts b/packages/blueprints/src/AddWinsSet/index.ts index 0df232d3..2d030063 100644 --- a/packages/blueprints/src/AddWinsSet/index.ts +++ b/packages/blueprints/src/AddWinsSet/index.ts @@ -7,7 +7,6 @@ import { } from "@ts-drp/object"; export class AddWinsSet implements DRP { - operations: string[] = ["add", "remove"]; state: Map; semanticsType = SemanticsType.pair; @@ -15,27 +14,19 @@ export class AddWinsSet implements DRP { this.state = new Map(); } - private _add(value: T): void { - if (!this.state.get(value)) this.state.set(value, true); - } - add(value: T): void { - this._add(value); - } - - private _remove(value: T): void { - if (this.state.get(value)) this.state.set(value, false); + if (!this.state.get(value)) this.state.set(value, true); } remove(value: T): void { - this._remove(value); + if (this.state.get(value)) this.state.set(value, false); } - contains(value: T): boolean { + query_contains(value: T): boolean { return this.state.get(value) === true; } - values(): T[] { + query_getValues(): T[] { return Array.from(this.state.entries()) .filter(([_, exists]) => exists) .map(([value, _]) => value); diff --git a/packages/blueprints/src/AddWinsSetWithACL/index.ts b/packages/blueprints/src/AddWinsSetWithACL/index.ts index 8671a82a..91c0e9b8 100644 --- a/packages/blueprints/src/AddWinsSetWithACL/index.ts +++ b/packages/blueprints/src/AddWinsSetWithACL/index.ts @@ -19,27 +19,19 @@ export class AddWinsSetWithACL implements DRP { this.state = new Map(); } - private _add(value: T): void { - if (!this.state.get(value)) this.state.set(value, true); - } - add(value: T): void { - this._add(value); - } - - private _remove(value: T): void { - if (this.state.get(value)) this.state.set(value, false); + if (!this.state.get(value)) this.state.set(value, true); } remove(value: T): void { - this._remove(value); + if (this.state.get(value)) this.state.set(value, false); } - contains(value: T): boolean { + query_contains(value: T): boolean { return this.state.get(value) === true; } - values(): T[] { + query_getValues(): T[] { return Array.from(this.state.entries()) .filter(([_, exists]) => exists) .map(([value, _]) => value); @@ -55,15 +47,15 @@ export class AddWinsSetWithACL implements DRP { return { action: ActionType.Nop }; if ( - this.acl?.operations.includes(vertices[0].operation.type) && - this.acl?.operations.includes(vertices[0].operation.type) + ["grant", "revoke"].includes(vertices[0].operation.type) && + ["grant", "revoke"].includes(vertices[1].operation.type) ) { return this.acl.resolveConflicts(vertices); } if ( this.operations.includes(vertices[0].operation.type) && - this.operations.includes(vertices[0].operation.type) + this.operations.includes(vertices[1].operation.type) ) { return vertices[0].operation.type === "add" ? { action: ActionType.DropRight } diff --git a/packages/blueprints/src/PseudoRandomWinsSet/index.ts b/packages/blueprints/src/PseudoRandomWinsSet/index.ts index 26039b89..3558a7f9 100644 --- a/packages/blueprints/src/PseudoRandomWinsSet/index.ts +++ b/packages/blueprints/src/PseudoRandomWinsSet/index.ts @@ -26,7 +26,6 @@ function computeHash(s: string): number { The winning operation is chosen using a pseudo-random number generator. */ export class PseudoRandomWinsSet implements DRP { - operations: string[] = ["add", "remove"]; state: Map; semanticsType = SemanticsType.multiple; @@ -34,27 +33,19 @@ export class PseudoRandomWinsSet implements DRP { this.state = new Map(); } - private _add(value: T): void { - if (!this.state.get(value)) this.state.set(value, true); - } - add(value: T): void { - this._add(value); - } - - private _remove(value: T): void { - if (this.state.get(value)) this.state.set(value, false); + if (!this.state.get(value)) this.state.set(value, true); } remove(value: T): void { - this._remove(value); + if (this.state.get(value)) this.state.set(value, false); } - contains(value: T): boolean { + query_contains(value: T): boolean { return this.state.get(value) === true; } - values(): T[] { + query_getValues(): T[] { return Array.from(this.state.entries()) .filter(([_, exists]) => exists) .map(([value, _]) => value); diff --git a/packages/blueprints/tests/AddWinsSet.test.ts b/packages/blueprints/tests/AddWinsSet.test.ts index 17839831..8701d42e 100644 --- a/packages/blueprints/tests/AddWinsSet.test.ts +++ b/packages/blueprints/tests/AddWinsSet.test.ts @@ -10,26 +10,26 @@ describe("HashGraph for AddWinSet tests", () => { test("Test: Add", () => { drp.add(1); - let set = drp.values(); + let set = drp.query_getValues(); expect(set).toEqual([1]); drp.add(2); - set = drp.values(); + set = drp.query_getValues(); expect(set).toEqual([1, 2]); }); test("Test: Add and Remove", () => { drp.add(1); - let set = drp.values(); + let set = drp.query_getValues(); expect(set).toEqual([1]); drp.add(2); - set = drp.values(); + set = drp.query_getValues(); expect(set).toEqual([1, 2]); drp.remove(1); - set = drp.values(); - expect(drp.contains(1)).toBe(false); + set = drp.query_getValues(); + expect(drp.query_contains(1)).toBe(false); expect(set).toEqual([2]); }); }); diff --git a/packages/blueprints/tests/AddWinsSetWithACL.test.ts b/packages/blueprints/tests/AddWinsSetWithACL.test.ts index c296d9c1..7e435959 100644 --- a/packages/blueprints/tests/AddWinsSetWithACL.test.ts +++ b/packages/blueprints/tests/AddWinsSetWithACL.test.ts @@ -10,24 +10,24 @@ describe("AccessControl tests with RevokeWins resolution", () => { }); test("Admin nodes should have admin privileges", () => { - expect(drp.acl.isAdmin("peer1")).toBe(true); + expect(drp.acl.query_isAdmin("peer1")).toBe(true); }); test("Admin nodes should have write permissions", () => { - expect(drp.acl.isWriter("peer1")).toBe(true); + expect(drp.acl.query_isWriter("peer1")).toBe(true); }); test("Grant write permissions to a new writer", () => { drp.acl.grant("peer1", "peer3", "publicKey3"); - expect(drp.acl.isWriter("peer3")).toBe(true); + expect(drp.acl.query_isWriter("peer3")).toBe(true); }); test("Revoke write permissions from a writer", () => { drp.acl.grant("peer1", "peer3", "publicKey3"); drp.acl.revoke("peer1", "peer3"); - expect(drp.acl.isWriter("peer3")).toBe(false); + expect(drp.acl.query_isWriter("peer3")).toBe(false); }); test("Cannot revoke admin permissions", () => { @@ -35,7 +35,7 @@ describe("AccessControl tests with RevokeWins resolution", () => { drp.acl.revoke("peer1", "peer1"); }).toThrow("Cannot revoke permissions from a node with admin privileges."); - expect(drp.acl.isWriter("peer1")).toBe(true); + expect(drp.acl.query_isWriter("peer1")).toBe(true); }); test("Resolve conflicts with RevokeWins", () => { @@ -46,6 +46,7 @@ describe("AccessControl tests with RevokeWins resolution", () => { operation: { type: "grant", value: "peer3" }, dependencies: [], signature: "", + timestamp: 0, }, { hash: "", @@ -53,6 +54,7 @@ describe("AccessControl tests with RevokeWins resolution", () => { operation: { type: "revoke", value: "peer3" }, dependencies: [], signature: "", + timestamp: 0, }, ]; const result = drp.resolveConflicts(vertices); diff --git a/packages/blueprints/tests/PseudoRandomWinsSet.test.ts b/packages/blueprints/tests/PseudoRandomWinsSet.test.ts index 3616baab..57c374ad 100644 --- a/packages/blueprints/tests/PseudoRandomWinsSet.test.ts +++ b/packages/blueprints/tests/PseudoRandomWinsSet.test.ts @@ -10,26 +10,26 @@ describe("HashGraph for PseudoRandomWinsSet tests", () => { test("Test: Add", () => { drp.add(1); - let set = drp.values(); + let set = drp.query_getValues(); expect(set).toEqual([1]); drp.add(2); - set = drp.values(); + set = drp.query_getValues(); expect(set).toEqual([1, 2]); }); test("Test: Add and Remove", () => { drp.add(1); - let set = drp.values(); + let set = drp.query_getValues(); expect(set).toEqual([1]); drp.add(2); - set = drp.values(); + set = drp.query_getValues(); expect(set).toEqual([1, 2]); drp.remove(1); - set = drp.values(); - expect(drp.contains(1)).toBe(false); + set = drp.query_getValues(); + expect(drp.query_contains(1)).toBe(false); expect(set).toEqual([2]); }); }); diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index fe591f23..955d49dd 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -255,7 +255,7 @@ export async function verifyIncomingVertices( const signature = uint8ArrayFromString(vertex.signature, "base64"); - const publicKey = acl.getPeerKey(vertex.peerId); + const publicKey = acl.query_getPeerKey(vertex.peerId); if (!publicKey) { return null; } diff --git a/packages/object/package.json b/packages/object/package.json index a9463769..c4b8ace3 100644 --- a/packages/object/package.json +++ b/packages/object/package.json @@ -27,10 +27,12 @@ "devDependencies": { "@bufbuild/protobuf": "^2.0.0", "benchmark": "^2.1.4", - "tsx": "4.19.1", - "es-toolkit": "1.30.1" + "es-toolkit": "1.30.1", + "tsx": "4.19.1" }, "dependencies": { - "@ts-drp/logger": "^0.4.4" + "@ts-drp/logger": "^0.4.4", + "fast-deep-equal": "^3.1.3", + "fast-equals": "^5.2.2" } } diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index 07b7689d..710c4b71 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -1,5 +1,7 @@ import * as crypto from "node:crypto"; import { Logger, type LoggerOptions } from "@ts-drp/logger"; +import { cloneDeep } from "es-toolkit"; +import { deepEqual } from "fast-equals"; import { type Hash, HashGraph, @@ -13,18 +15,16 @@ import { ObjectSet } from "./utils/objectSet.js"; export * as ObjectPb from "./proto/drp/object/v1/object_pb.js"; export * from "./hashgraph/index.js"; -import { cloneDeep } from "es-toolkit"; export interface IACL { - isWriter: (peerId: string) => boolean; - isAdmin: (peerId: string) => boolean; grant: (senderId: string, peerId: string, publicKey: string) => void; revoke: (senderId: string, peerId: string) => void; - getPeerKey: (peerId: string) => string | undefined; + query_isWriter: (peerId: string) => boolean; + query_isAdmin: (peerId: string) => boolean; + query_getPeerKey: (peerId: string) => string | undefined; } export interface DRP { - operations: string[]; semanticsType: SemanticsType; resolveConflicts: (vertices: Vertex[]) => ResolveConflictsType; acl?: IACL & DRP; @@ -114,7 +114,14 @@ export class DRPObject implements IDRPObject { : String(propKey); return new Proxy(target[propKey as keyof object], { apply(applyTarget, thisArg, args) { - if ((thisArg.operations as string[]).includes(propKey as string)) + if ((propKey as string).startsWith("query_")) { + return Reflect.apply(applyTarget, thisArg, args); + } + const callerName = new Error().stack + ?.split("\n")[2] + ?.trim() + .split(" ")[1]; + if (!callerName?.startsWith("Proxy.")) obj.callFn(fullPropKey, args.length === 1 ? args[0] : args); return Reflect.apply(applyTarget, thisArg, args); }, @@ -137,8 +144,25 @@ export class DRPObject implements IDRPObject { // biome-ignore lint: value can't be unknown because of protobuf callFn(fn: string, args: any) { + const preOperationDRP = this._computeDRP(this.hashGraph.getFrontier()); + const drp = cloneDeep(preOperationDRP); + this._applyOperation(drp, { type: fn, value: args }); + + let stateChanged = false; + for (const key of Object.keys(preOperationDRP)) { + if (!deepEqual(preOperationDRP[key], drp[key])) { + stateChanged = true; + break; + } + } + + if (!stateChanged) { + return; + } + const vertex = this.hashGraph.addToFrontier({ type: fn, value: args }); - this._setState(vertex); + + this._setState(vertex, this._getDRPState(drp)); const serializedVertex = ObjectPb.Vertex.create({ hash: vertex.hash, @@ -205,7 +229,7 @@ export class DRPObject implements IDRPObject { // check if the given peer has write permission private _checkWriterPermission(drp: DRP, peerId: string): boolean { if (drp.acl) { - return drp.acl.isWriter(peerId); + return drp.acl.query_isWriter(peerId); } return true; } @@ -275,12 +299,8 @@ export class DRPObject implements IDRPObject { } // get the map representing the state of the given DRP by mapping variable names to their corresponding values - private _getDRPState( - drp: DRP, - // biome-ignore lint: values can be anything - ): DRPState { + private _getDRPState(drp: DRP): DRPState { const varNames: string[] = Object.keys(drp); - // biome-ignore lint: values can be anything const drpState: DRPState = { state: new Map(), }; @@ -290,22 +310,15 @@ export class DRPObject implements IDRPObject { return drpState; } - // compute the DRP state based on all dependencies of the current vertex private _computeDRPState( vertexDependencies: Hash[], vertexOperation?: Operation, - // biome-ignore lint: values can be anything ): DRPState { const drp = this._computeDRP(vertexDependencies, vertexOperation); return this._getDRPState(drp); } - // store the state of the DRP corresponding to the given vertex - private _setState( - vertex: Vertex, - // biome-ignore lint: values can be anything - drpState?: DRPState, - ) { + private _setState(vertex: Vertex, drpState?: DRPState) { this.states.set( vertex.hash, drpState ?? this._computeDRPState(vertex.dependencies, vertex.operation), diff --git a/packages/object/tests/hashgraph.test.ts b/packages/object/tests/hashgraph.test.ts index 3e8eeae7..3da6af75 100644 --- a/packages/object/tests/hashgraph.test.ts +++ b/packages/object/tests/hashgraph.test.ts @@ -31,7 +31,7 @@ describe("HashGraph construction tests", () => { expect(obj1.vertices).toEqual(obj1.hashGraph.getAllVertices()); }); - test("Test: HashGraph should be DAG compatibility", () => { + test("Test: HashGraph should be DAG compatible", () => { /* __ V1:ADD(1) ROOT / @@ -108,7 +108,7 @@ describe("HashGraph for AddWinSet tests", () => { const drp1 = obj1.drp as AddWinsSet; drp1.add(1); drp1.remove(1); - expect(drp1.contains(1)).toBe(false); + expect(drp1.query_contains(1)).toBe(false); const linearOps = obj1.hashGraph.linearizeOperations(); expect(linearOps).toEqual([ @@ -135,13 +135,14 @@ describe("HashGraph for AddWinSet tests", () => { obj1.merge(obj2.hashGraph.getAllVertices()); obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp1.contains(1)).toBe(true); + // Adding 1 again does not change the state + expect(drp1.query_contains(1)).toBe(false); expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); expect(linearOps).toEqual([ { type: "add", value: 1 }, - { type: "add", value: 1 }, + { type: "remove", value: 1 }, ]); }); @@ -163,8 +164,8 @@ describe("HashGraph for AddWinSet tests", () => { obj1.merge(obj2.hashGraph.getAllVertices()); obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp1.contains(1)).toBe(false); - expect(drp1.contains(2)).toBe(true); + expect(drp1.query_contains(1)).toBe(false); + expect(drp1.query_contains(2)).toBe(true); expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); @@ -191,21 +192,21 @@ describe("HashGraph for AddWinSet tests", () => { drp1.remove(1); drp2.add(1); drp1.add(10); + // Removing 5 does not change the state drp2.remove(5); obj1.merge(obj2.hashGraph.getAllVertices()); obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp1.contains(1)).toBe(true); - expect(drp1.contains(10)).toBe(true); - expect(drp1.contains(5)).toBe(false); + expect(drp1.query_contains(1)).toBe(false); + expect(drp1.query_contains(10)).toBe(true); + expect(drp1.query_contains(5)).toBe(false); expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); expect(linearOps).toEqual([ { type: "add", value: 1 }, - { type: "add", value: 1 }, + { type: "remove", value: 1 }, { type: "add", value: 10 }, - { type: "remove", value: 5 }, ]); }); @@ -229,15 +230,15 @@ describe("HashGraph for AddWinSet tests", () => { obj1.merge(obj2.hashGraph.getAllVertices()); obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp1.contains(1)).toBe(true); - expect(drp1.contains(2)).toBe(true); + expect(drp1.query_contains(1)).toBe(false); + expect(drp1.query_contains(2)).toBe(true); expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); expect(linearOps).toEqual([ { type: "add", value: 1 }, + { type: "remove", value: 1 }, { type: "add", value: 2 }, - { type: "add", value: 1 }, ]); }); @@ -280,20 +281,19 @@ describe("HashGraph for AddWinSet tests", () => { obj3.merge(obj1.hashGraph.getAllVertices()); obj3.merge(obj2.hashGraph.getAllVertices()); - expect(drp1.contains(1)).toBe(false); - expect(drp1.contains(2)).toBe(true); - expect(drp1.contains(3)).toBe(true); + expect(drp1.query_contains(1)).toBe(false); + expect(drp1.query_contains(2)).toBe(true); + expect(drp1.query_contains(3)).toBe(true); expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); expect(obj1.hashGraph.vertices).toEqual(obj3.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); expect(linearOps).toEqual([ { type: "add", value: 1 }, - { type: "add", value: 1 }, - { type: "remove", value: 2 }, - { type: "add", value: 2 }, { type: "remove", value: 1 }, + { type: "add", value: 2 }, { type: "add", value: 3 }, + { type: "remove", value: 1 }, ]); }); @@ -336,22 +336,18 @@ describe("HashGraph for AddWinSet tests", () => { obj3.merge(obj1.hashGraph.getAllVertices()); obj3.merge(obj2.hashGraph.getAllVertices()); - expect(drp1.contains(1)).toBe(false); - expect(drp1.contains(2)).toBe(true); - expect(drp1.contains(3)).toBe(true); + expect(drp1.query_contains(1)).toBe(false); + expect(drp1.query_contains(2)).toBe(true); + expect(drp1.query_contains(3)).toBe(true); expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); expect(obj1.hashGraph.vertices).toEqual(obj3.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); expect(linearOps).toEqual([ { type: "add", value: 1 }, - { type: "add", value: 1 }, - { type: "remove", value: 2 }, - { type: "remove", value: 2 }, { type: "remove", value: 1 }, { type: "add", value: 3 }, { type: "add", value: 2 }, - { type: "remove", value: 1 }, ]); }); @@ -377,8 +373,8 @@ describe("HashGraph for AddWinSet tests", () => { drp1.remove(2); obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp1.contains(1)).toBe(true); - expect(drp1.contains(2)).toBe(false); + expect(drp1.query_contains(1)).toBe(true); + expect(drp1.query_contains(2)).toBe(false); expect(obj1.hashGraph.vertices).toEqual(obj2.hashGraph.vertices); const linearOps = obj1.hashGraph.linearizeOperations(); @@ -564,7 +560,7 @@ describe("Vertex state tests", () => { const hashV8 = obj1.hashGraph.getFrontier()[0]; const drpStateV8 = obj1.states.get(hashV8); expect(drpStateV8?.state.get("state").get(1)).toBe(false); - expect(drpStateV8?.state.get("state").get(2)).toBe(true); + expect(drpStateV8?.state.get("state").get(2)).toBe(undefined); expect(drpStateV8?.state.get("state").get(3)).toBe(undefined); }); }); @@ -662,7 +658,7 @@ describe("Operation with ACL tests", () => { drp1.acl.grant("peer1", "peer2", "publicKey2"); obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp2.acl.isWriter("peer2")).toBe(true); + expect(drp2.acl.query_isWriter("peer2")).toBe(true); }); test("Node with writer permission can create vertices", () => { @@ -677,7 +673,7 @@ describe("Operation with ACL tests", () => { drp2.add(1); obj1.merge(obj2.hashGraph.getAllVertices()); - expect(drp1.contains(1)).toBe(true); + expect(drp1.query_contains(1)).toBe(true); }); test("Revoke permission from writer", () => { @@ -690,13 +686,13 @@ describe("Operation with ACL tests", () => { drp1.acl.grant("peer1", "peer2", "publicKey2"); obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp2.acl.isWriter("peer2")).toBe(true); + expect(drp2.acl.query_isWriter("peer2")).toBe(true); drp2.add(1); obj1.merge(obj2.hashGraph.getAllVertices()); drp1.acl.revoke("peer1", "peer2"); obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp2.acl.isWriter("peer2")).toBe(false); + expect(drp2.acl.query_isWriter("peer2")).toBe(false); }); }); @@ -717,8 +713,8 @@ describe("Writer permission tests", () => { drp.add(1); drp.add(2); - expect(drp.contains(1)).toBe(true); - expect(drp.contains(2)).toBe(true); + expect(drp.query_contains(1)).toBe(true); + expect(drp.query_contains(2)).toBe(true); }); test("Discard vertex if creator does not have write permission", () => { @@ -729,7 +725,7 @@ describe("Writer permission tests", () => { drp2.add(2); obj1.merge(obj2.hashGraph.getAllVertices()); - expect(drp1.contains(2)).toBe(false); + expect(drp1.query_contains(2)).toBe(false); }); test("Accept vertex if creator has write permission", () => { @@ -741,15 +737,15 @@ describe("Writer permission tests", () => { drp1.add(1); drp1.acl.grant("peer1", "peer2", "publicKey2"); - expect(drp1.acl.isAdmin("peer1")).toBe(true); + expect(drp1.acl.query_isAdmin("peer1")).toBe(true); obj2.merge(obj1.hashGraph.getAllVertices()); - expect(drp2.contains(1)).toBe(true); - expect(drp2.acl.isWriter("peer2")).toBe(true); + expect(drp2.query_contains(1)).toBe(true); + expect(drp2.acl.query_isWriter("peer2")).toBe(true); drp2.add(4); obj1.merge(obj2.hashGraph.getAllVertices()); - expect(drp1.contains(4)).toBe(true); + expect(drp1.query_contains(4)).toBe(true); }); test("Discard vertex if writer permission is revoked", () => { @@ -775,19 +771,19 @@ describe("Writer permission tests", () => { obj1.merge(obj3.hashGraph.getAllVertices()); obj2.merge(obj3.hashGraph.getAllVertices()); obj3.merge(obj2.hashGraph.getAllVertices()); - expect(drp1.contains(1)).toBe(true); - expect(drp1.contains(2)).toBe(true); + expect(drp1.query_contains(1)).toBe(true); + expect(drp1.query_contains(2)).toBe(true); drp1.acl.revoke("peer1", "peer3"); obj3.merge(obj1.hashGraph.getAllVertices()); drp3.add(3); obj2.merge(obj3.hashGraph.getAllVertices()); - expect(drp2.contains(3)).toBe(false); + expect(drp2.query_contains(3)).toBe(false); drp2.add(4); obj1.merge(obj2.hashGraph.getAllVertices()); obj1.merge(obj3.hashGraph.getAllVertices()); - expect(drp1.contains(3)).toBe(false); - expect(drp1.contains(4)).toBe(true); + expect(drp1.query_contains(3)).toBe(false); + expect(drp1.query_contains(4)).toBe(true); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 821decd4..efd96242 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -288,6 +288,12 @@ importers: '@ts-drp/logger': specifier: ^0.4.4 version: link:../logger + fast-deep-equal: + specifier: ^3.1.3 + version: 3.1.3 + fast-equals: + specifier: ^5.2.2 + version: 5.2.2 devDependencies: '@bufbuild/protobuf': specifier: ^2.0.0 @@ -2961,6 +2967,13 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-equals@5.2.2: + resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==} + engines: {node: '>=6.0.0'} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -8430,6 +8443,10 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-deep-equal@3.1.3: {} + + fast-equals@5.2.2: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5