diff --git a/packages/object/src/index.ts b/packages/object/src/index.ts index fea8ce50..960c72ae 100644 --- a/packages/object/src/index.ts +++ b/packages/object/src/index.ts @@ -102,27 +102,32 @@ export class DRPObject implements IDRPObject { } // This function is black magic, it allows us to intercept calls to the DRP object - proxyDRPHandler(): ProxyHandler { + proxyDRPHandler(parentProp?: string): ProxyHandler { const obj = this; return { get(target, propKey, receiver) { const value = Reflect.get(target, propKey, receiver); if (typeof value === "function") { + const fullPropKey = parentProp + ? `${parentProp}.${String(propKey)}` + : String(propKey); return new Proxy(target[propKey as keyof object], { apply(applyTarget, thisArg, args) { if ((thisArg.operations as string[]).includes(propKey as string)) - obj.callFn( - propKey as string, - args.length === 1 ? args[0] : args, - ); + obj.callFn(fullPropKey, args.length === 1 ? args[0] : args); return Reflect.apply(applyTarget, thisArg, args); }, }); } if (propKey === "acl" && typeof value === "object" && value !== null) { - return new Proxy(value, this); + return new Proxy( + value, + obj.proxyDRPHandler( + parentProp ? `${parentProp}.${String(propKey)}` : String(propKey), + ), + ); } return value; @@ -191,6 +196,28 @@ export class DRPObject implements IDRPObject { } } + private _applyOperation(drp: DRP, operation: Operation) { + const { type, value } = operation; + + const typeParts = type.split("."); + // biome-ignore lint: target can be anything + let target: any = drp; + for (let i = 0; i < typeParts.length - 1; i++) { + target = target[typeParts[i]]; + if (!target) { + throw new Error(`Invalid operation type: ${type}`); + } + } + + const methodName = typeParts[typeParts.length - 1]; + if (typeof target[methodName] !== "function") { + throw new Error(`${type} is not a function`); + } + + const args = Array.isArray(value) ? value : [value]; + target[methodName](...args); + } + private _computeState( vertexDependencies: Hash[], vertexOperation?: Operation | undefined, @@ -223,14 +250,10 @@ export class DRPObject implements IDRPObject { } for (const op of linearizedOperations) { - const args = Array.isArray(op.value) ? op.value : [op.value]; - drp[op.type](...args); + this._applyOperation(drp, op); } if (vertexOperation) { - const args = Array.isArray(vertexOperation.value) - ? vertexOperation.value - : [vertexOperation.value]; - drp[vertexOperation.type](...args); + this._applyOperation(drp, vertexOperation); } const varNames: string[] = Object.keys(drp); diff --git a/packages/object/tests/hashgraph.test.ts b/packages/object/tests/hashgraph.test.ts index 19706c74..ca04cc46 100644 --- a/packages/object/tests/hashgraph.test.ts +++ b/packages/object/tests/hashgraph.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, test } from "vitest"; import { AddWinsSet } from "../../blueprints/src/AddWinsSet/index.js"; -import { PseudoRandomWinsSet } from "../../blueprints/src/PseudoRandomWinsSet/index.js"; import { DRPObject, type Operation, OperationType } from "../src/index.js"; +import { AddWinsSetWithACL } from "@topology-foundation/blueprints/src/AddWinsSetWithACL/index.js"; describe("HashGraph construction tests", () => { let obj1: DRPObject; @@ -633,3 +633,34 @@ describe("Vertex timestamp tests", () => { ).toThrowError("Invalid timestamp detected."); }); }); + +describe("Operation with ACL tests", () => { + let obj1: DRPObject; + let obj2: DRPObject; + + beforeEach(async () => { + const peerIdToPublicKey = new Map([ + ["peer1", "publicKey1"], + ]); + obj1 = new DRPObject( + "peer1", + new AddWinsSetWithACL(peerIdToPublicKey), + ); + obj2 = new DRPObject( + "peer2", + new AddWinsSetWithACL(peerIdToPublicKey), + ); + }); + + test("Node with admin permission can grant permission to other nodes", () => { + /* + ROOT -- V1:GRANT("peer2") + */ + + const drp1 = obj1.drp as AddWinsSetWithACL; + const drp2 = obj2.drp as AddWinsSetWithACL; + drp1.acl.grant("peer1", "peer2", "publicKey2"); + obj2.merge(obj1.hashGraph.getAllVertices()); + expect(drp2.acl.isWriter("peer2")).toBe(true); + }); +});