From a5738c475451837d109bf2ddcbf2a06584d0fca2 Mon Sep 17 00:00:00 2001 From: Joshua Parkin Date: Wed, 7 Dec 2022 11:36:49 -0800 Subject: [PATCH 1/4] add: core link(s) mutation methods --- Cargo.lock | 2 +- core/src/Ad4mClient.test.ts | 37 +++++++++++++++ core/src/links/Links.ts | 23 ++++++++++ core/src/perspectives/PerspectiveClient.ts | 43 +++++++++++++++++- core/src/perspectives/PerspectiveProxy.ts | 17 ++++++- core/src/perspectives/PerspectiveResolver.ts | 47 +++++++++++++++++++- executor/src/core/Perspective.ts | 6 ++- 7 files changed, 170 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 734d6b1de..a63783457 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,7 +55,7 @@ dependencies = [ [[package]] name = "ad4min" -version = "0.2.1" +version = "0.0.10" dependencies = [ "ad4m-client", "directories", diff --git a/core/src/Ad4mClient.test.ts b/core/src/Ad4mClient.test.ts index 6e357b1b1..f5015ca05 100644 --- a/core/src/Ad4mClient.test.ts +++ b/core/src/Ad4mClient.test.ts @@ -498,6 +498,43 @@ describe('Ad4mClient', () => { expect(link.data.target).toBe('lang://Qm123') }) + it('addLinks() smoke test', async () => { + const links = await ad4mClient.perspective.addLinks('00001', [{source: 'root', target: 'lang://Qm123', predicate: 'p'}]) + expect(links.length).toBe(2) + expect(links[0].author).toBe('did:ad4m:test') + expect(links[0].data.source).toBe('root') + expect(links[0].data.predicate).toBe('p') + expect(links[0].data.target) + }) + + it('removeLinks() smoke test', async () => { + const links = await ad4mClient.perspective.removeLinks('00001', [{source: 'root', target: 'lang://Qm123', predicate: 'p'}]) + expect(links.length).toBe(2) + expect(links[0].author).toBe('did:ad4m:test') + expect(links[0].data.source).toBe('root') + expect(links[0].data.predicate).toBe('p') + expect(links[0].data.target) + }) + + it('linkMutations() smoke test', async () => { + const mutations = await ad4mClient.perspective.linkMutations('00001', { + additions: [{source: 'root', target: 'lang://Qm123', predicate: 'p'}], + removals: [{source: 'root', target: 'lang://Qm123', predicate: 'p'}] + }); + expect(mutations.additions.length).toBe(2) + expect(mutations.removals.length).toBe(2) + + expect(mutations.additions[0].author).toBe('did:ad4m:test') + expect(mutations.additions[0].data.source).toBe('root') + expect(mutations.additions[0].data.predicate).toBe('p') + expect(mutations.additions[0].data.target).toBe('lang://Qm123') + + expect(mutations.removals[0].author).toBe('did:ad4m:test') + expect(mutations.removals[0].data.source).toBe('root') + expect(mutations.removals[0].data.predicate).toBe('p') + expect(mutations.removals[0].data.target).toBe('lang://Qm123') + }) + it('addLinkExpression() smoke test', async () => { const testLink = new LinkExpression() testLink.author = "did:ad4m:test" diff --git a/core/src/links/Links.ts b/core/src/links/Links.ts index d00e9609b..9986ef9bc 100644 --- a/core/src/links/Links.ts +++ b/core/src/links/Links.ts @@ -28,6 +28,29 @@ export class LinkMutations { removals: LinkExpression[]; } +@InputType() +export class LinkInputMutations { + @Field(type => [LinkInput]) + additions: LinkInput[]; + + @Field(type => [LinkInput]) + removals: LinkInput[]; +} + +@ObjectType() +export class LinkExpressionMutations { + @Field(type => [LinkExpression]) + additions: LinkExpression[]; + + @Field(type => [LinkExpression]) + removals: LinkExpression[]; + + constructor(additions: LinkExpression[], removals: LinkExpression[]) { + this.additions = additions + this.removals = removals + } +} + @InputType() export class LinkInput { @Field() diff --git a/core/src/perspectives/PerspectiveClient.ts b/core/src/perspectives/PerspectiveClient.ts index 2dea89bd9..6c5634727 100644 --- a/core/src/perspectives/PerspectiveClient.ts +++ b/core/src/perspectives/PerspectiveClient.ts @@ -1,5 +1,5 @@ import { ApolloClient, gql } from "@apollo/client/core"; -import { Link, LinkExpressionInput, LinkExpression, LinkInput } from "../links/Links"; +import { Link, LinkExpressionInput, LinkExpression, LinkInput, LinkInputMutations, LinkMutations, LinkExpressionMutations } from "../links/Links"; import unwrapApolloResult from "../unwrapApolloResult"; import { LinkQuery } from "./LinkQuery"; import { Perspective } from "./Perspective"; @@ -167,6 +167,47 @@ export class PerspectiveClient { return perspectiveAddLink } + async addLinks(uuid: string, links: Link[]): Promise { + const { perspectiveAddLinks } = unwrapApolloResult(await this.#apolloClient.mutate({ + mutation: gql`mutation perspectiveAddLinks($uuid: String!, $links: [LinkInput!]!){ + perspectiveAddLinks(links: $links, uuid: $uuid) { + ${LINK_EXPRESSION_FIELDS} + } + }`, + variables: { uuid, links } + })) + return perspectiveAddLinks + } + + async removeLinks(uuid: string, links: LinkInput[]): Promise { + const { perspectiveRemoveLinks } = unwrapApolloResult(await this.#apolloClient.mutate({ + mutation: gql`mutation perspectiveRemoveLinks($uuid: String!, $links: [LinkInput!]!){ + perspectiveRemoveLinks(links: $links, uuid: $uuid) { + ${LINK_EXPRESSION_FIELDS} + } + }`, + variables: { uuid, links } + })) + return perspectiveRemoveLinks + } + + async linkMutations(uuid: string, mutations: LinkInputMutations): Promise { + const { perspectiveLinkMutations } = unwrapApolloResult(await this.#apolloClient.mutate({ + mutation: gql`mutation perspectiveLinkMutations($uuid: String!, $mutations: LinkInputMutations!){ + perspectiveLinkMutations(mutations: $mutations, uuid: $uuid) { + additions { + ${LINK_EXPRESSION_FIELDS} + } + removals { + ${LINK_EXPRESSION_FIELDS} + } + } + }`, + variables: { uuid, mutations } + })) + return perspectiveLinkMutations + } + async addLinkExpression(uuid: string, link: LinkExpressionInput): Promise { const { perspectiveAddLinkExpression } = unwrapApolloResult(await this.#apolloClient.mutate({ mutation: gql`mutation perspectiveAddLinkExpression($uuid: String!, $link: LinkExpressionInput!){ diff --git a/core/src/perspectives/PerspectiveProxy.ts b/core/src/perspectives/PerspectiveProxy.ts index b7fcfc4cb..619888b40 100644 --- a/core/src/perspectives/PerspectiveProxy.ts +++ b/core/src/perspectives/PerspectiveProxy.ts @@ -1,5 +1,5 @@ import { LinkCallback, PerspectiveClient } from "./PerspectiveClient"; -import { Link, LinkExpression, LinkExpressionInput } from "../links/Links"; +import { Link, LinkExpression, LinkExpressionInput, LinkExpressionMutations, LinkInput, LinkInputMutations, LinkMutations } from "../links/Links"; import { LinkQuery } from "./LinkQuery"; import { Neighbourhood } from "../neighbourhood/Neighbourhood"; import { PerspectiveHandle } from './PerspectiveHandle' @@ -96,6 +96,21 @@ export class PerspectiveProxy { return await this.#client.addLink(this.#handle.uuid, link) } + /** Adds multiple links to this perspective **/ + async addLinks(links: Link[]): Promise { + return await this.#client.addLinks(this.#handle.uuid, links) + } + + /** Removes multiple links from this perspective **/ + async removeLinks(links: LinkInput[]): Promise { + return await this.#client.removeLinks(this.#handle.uuid, links) + } + + /** Adds and removes multiple links from this perspective **/ + async mutateMany(mutations: LinkInputMutations): Promise { + return await this.#client.linkMutations(this.#handle.uuid, mutations) + } + /** Adds a linkExpression to this perspective */ async addLinkExpression(link: LinkExpression): Promise { return await this.#client.addLinkExpression(this.#handle.uuid, link) diff --git a/core/src/perspectives/PerspectiveResolver.ts b/core/src/perspectives/PerspectiveResolver.ts index cd31af278..edebe1dad 100644 --- a/core/src/perspectives/PerspectiveResolver.ts +++ b/core/src/perspectives/PerspectiveResolver.ts @@ -1,5 +1,5 @@ import { Arg, Mutation, PubSub, PubSubEngine, Query, Resolver, Subscription } from "type-graphql"; -import { LinkExpression, LinkExpressionInput, LinkInput } from "../links/Links"; +import { LinkExpression, LinkExpressionInput, LinkExpressionMutations, LinkInput, LinkInputMutations } from "../links/Links"; import { Neighbourhood } from "../neighbourhood/Neighbourhood"; import { LinkQuery } from "./LinkQuery"; import { Perspective } from "./Perspective"; @@ -98,6 +98,51 @@ export default class PerspectiveResolver { return l } + @Mutation(returns => [LinkExpression]) + perspectiveAddLinks(@Arg('uuid') uuid: string, @Arg('links', type => [LinkInput]) links: LinkInput[], @PubSub() pubSub: any): LinkExpression[] { + const l = new LinkExpression() + l.author = 'did:ad4m:test' + l.timestamp = Date.now() + l.proof = testLink.proof + l.data = links[0] + + const l2 = new LinkExpression() + l2.author = 'did:ad4m:test' + l2.timestamp = Date.now() + l2.proof = testLink.proof + l2.data = links[0] + + pubSub.publish(LINK_ADDED_TOPIC, { link: l }) + pubSub.publish(LINK_ADDED_TOPIC, { link: l2 }) + return [l, l2] + } + + @Mutation(returns => [LinkExpression]) + perspectiveRemoveLinks(@Arg('uuid') uuid: string, @Arg('links', type => [LinkInput]) links: LinkInput[], @PubSub() pubSub: any): LinkExpression[] { + const l = new LinkExpression() + l.author = 'did:ad4m:test' + l.timestamp = Date.now() + l.proof = testLink.proof + l.data = links[0] + + const l2 = new LinkExpression() + l2.author = 'did:ad4m:test' + l2.timestamp = Date.now() + l2.proof = testLink.proof + l2.data = links[0] + + pubSub.publish(LINK_REMOVED_TOPIC, { link: l }) + pubSub.publish(LINK_ADDED_TOPIC, { link: l2 }) + return [l, l2] + } + + @Mutation(returns => LinkExpressionMutations) + perspectiveLinkMutations(@Arg('uuid') uuid: string, @Arg('mutations') mutations: LinkInputMutations, @PubSub() pubSub: any): LinkExpressionMutations { + const perspectiveAddLinks = this.perspectiveAddLinks(uuid, mutations.additions, pubSub); + const perspectiveRemoveLinks = this.perspectiveRemoveLinks(uuid, mutations.removals, pubSub); + return new LinkExpressionMutations(perspectiveAddLinks, perspectiveRemoveLinks) + } + @Mutation(returns => LinkExpression) perspectiveAddLinkExpression(@Arg('uuid') uuid: string, @Arg('link') link: LinkExpressionInput, @PubSub() pubSub: any): LinkExpression { pubSub.publish(LINK_ADDED_TOPIC, { link }) diff --git a/executor/src/core/Perspective.ts b/executor/src/core/Perspective.ts index 39e3871a1..0900684d6 100644 --- a/executor/src/core/Perspective.ts +++ b/executor/src/core/Perspective.ts @@ -1,4 +1,4 @@ -import { Agent, Expression, Neighbourhood, LinkExpression, LinkExpressionInput, LinkInput, LanguageRef, PerspectiveHandle, Literal, PerspectiveDiff, parseExprUrl, Perspective as Ad4mPerspective } from "@perspect3vism/ad4m" +import { Agent, Expression, Neighbourhood, LinkExpression, LinkExpressionInput, LinkInput, LanguageRef, PerspectiveHandle, Literal, PerspectiveDiff, parseExprUrl, Perspective as Ad4mPerspective, LinkMutations } from "@perspect3vism/ad4m" import { Link, linkEqual, LinkQuery } from "@perspect3vism/ad4m"; import { SHA3 } from "sha3"; import type AgentService from "./agent/AgentService"; @@ -338,6 +338,10 @@ export default class Perspective { } } + async addLinkMutation(linkMutation: LinkMutations): Promise { + + } + async addLink(link: LinkInput | LinkExpressionInput): Promise { const linkExpression = this.ensureLinkExpression(link); const diff = { From a1ebc5cc1c40d80d7e2bfd94e0d14390049916ae Mon Sep 17 00:00:00 2001 From: Joshua Parkin Date: Wed, 7 Dec 2022 12:30:52 -0800 Subject: [PATCH 2/4] fix: revert back removeLinks() to use LinkExpressionInput not LinkInput --- core/src/links/Links.ts | 14 ++------------ core/src/perspectives/PerspectiveClient.ts | 10 +++++----- core/src/perspectives/PerspectiveProxy.ts | 12 ++++++------ core/src/perspectives/PerspectiveResolver.ts | 10 +++++----- 4 files changed, 18 insertions(+), 28 deletions(-) diff --git a/core/src/links/Links.ts b/core/src/links/Links.ts index 9986ef9bc..99bf17e2e 100644 --- a/core/src/links/Links.ts +++ b/core/src/links/Links.ts @@ -24,19 +24,9 @@ export class LinkMutations { @Field(type => [LinkInput]) additions: LinkInput[]; - @Field(type => [LinkExpression]) - removals: LinkExpression[]; -} - -@InputType() -export class LinkInputMutations { - @Field(type => [LinkInput]) - additions: LinkInput[]; - - @Field(type => [LinkInput]) - removals: LinkInput[]; + @Field(type => [LinkExpressionInput]) + removals: LinkExpressionInput[]; } - @ObjectType() export class LinkExpressionMutations { @Field(type => [LinkExpression]) diff --git a/core/src/perspectives/PerspectiveClient.ts b/core/src/perspectives/PerspectiveClient.ts index 6c5634727..c95fb4414 100644 --- a/core/src/perspectives/PerspectiveClient.ts +++ b/core/src/perspectives/PerspectiveClient.ts @@ -1,5 +1,5 @@ import { ApolloClient, gql } from "@apollo/client/core"; -import { Link, LinkExpressionInput, LinkExpression, LinkInput, LinkInputMutations, LinkMutations, LinkExpressionMutations } from "../links/Links"; +import { Link, LinkExpressionInput, LinkExpression, LinkInput, LinkMutations, LinkExpressionMutations } from "../links/Links"; import unwrapApolloResult from "../unwrapApolloResult"; import { LinkQuery } from "./LinkQuery"; import { Perspective } from "./Perspective"; @@ -179,9 +179,9 @@ export class PerspectiveClient { return perspectiveAddLinks } - async removeLinks(uuid: string, links: LinkInput[]): Promise { + async removeLinks(uuid: string, links: LinkExpressionInput[]): Promise { const { perspectiveRemoveLinks } = unwrapApolloResult(await this.#apolloClient.mutate({ - mutation: gql`mutation perspectiveRemoveLinks($uuid: String!, $links: [LinkInput!]!){ + mutation: gql`mutation perspectiveRemoveLinks($uuid: String!, $links: [LinkExpressionInput!]!){ perspectiveRemoveLinks(links: $links, uuid: $uuid) { ${LINK_EXPRESSION_FIELDS} } @@ -191,9 +191,9 @@ export class PerspectiveClient { return perspectiveRemoveLinks } - async linkMutations(uuid: string, mutations: LinkInputMutations): Promise { + async linkMutations(uuid: string, mutations: LinkMutations): Promise { const { perspectiveLinkMutations } = unwrapApolloResult(await this.#apolloClient.mutate({ - mutation: gql`mutation perspectiveLinkMutations($uuid: String!, $mutations: LinkInputMutations!){ + mutation: gql`mutation perspectiveLinkMutations($uuid: String!, $mutations: LinkMutations!){ perspectiveLinkMutations(mutations: $mutations, uuid: $uuid) { additions { ${LINK_EXPRESSION_FIELDS} diff --git a/core/src/perspectives/PerspectiveProxy.ts b/core/src/perspectives/PerspectiveProxy.ts index 619888b40..7fa24e7a5 100644 --- a/core/src/perspectives/PerspectiveProxy.ts +++ b/core/src/perspectives/PerspectiveProxy.ts @@ -1,5 +1,5 @@ import { LinkCallback, PerspectiveClient } from "./PerspectiveClient"; -import { Link, LinkExpression, LinkExpressionInput, LinkExpressionMutations, LinkInput, LinkInputMutations, LinkMutations } from "../links/Links"; +import { Link, LinkExpression, LinkExpressionInput, LinkExpressionMutations, LinkInput, LinkMutations } from "../links/Links"; import { LinkQuery } from "./LinkQuery"; import { Neighbourhood } from "../neighbourhood/Neighbourhood"; import { PerspectiveHandle } from './PerspectiveHandle' @@ -102,25 +102,25 @@ export class PerspectiveProxy { } /** Removes multiple links from this perspective **/ - async removeLinks(links: LinkInput[]): Promise { + async removeLinks(links: LinkExpressionInput[]): Promise { return await this.#client.removeLinks(this.#handle.uuid, links) } /** Adds and removes multiple links from this perspective **/ - async mutateMany(mutations: LinkInputMutations): Promise { + async linkMutations(mutations: LinkMutations): Promise { return await this.#client.linkMutations(this.#handle.uuid, mutations) } /** Adds a linkExpression to this perspective */ - async addLinkExpression(link: LinkExpression): Promise { + async addLinkExpression(link: LinkExpressionInput): Promise { return await this.#client.addLinkExpression(this.#handle.uuid, link) } - async update(oldLink: LinkExpression, newLink: Link) { + async update(oldLink: LinkExpressionInput, newLink: Link) { return await this.#client.updateLink(this.#handle.uuid, oldLink, newLink) } - async remove(link: LinkExpression) { + async remove(link: LinkExpressionInput) { return await this.#client.removeLink(this.#handle.uuid, link) } diff --git a/core/src/perspectives/PerspectiveResolver.ts b/core/src/perspectives/PerspectiveResolver.ts index edebe1dad..49056f02a 100644 --- a/core/src/perspectives/PerspectiveResolver.ts +++ b/core/src/perspectives/PerspectiveResolver.ts @@ -1,5 +1,5 @@ import { Arg, Mutation, PubSub, PubSubEngine, Query, Resolver, Subscription } from "type-graphql"; -import { LinkExpression, LinkExpressionInput, LinkExpressionMutations, LinkInput, LinkInputMutations } from "../links/Links"; +import { LinkExpression, LinkExpressionInput, LinkExpressionMutations, LinkInput, LinkMutations } from "../links/Links"; import { Neighbourhood } from "../neighbourhood/Neighbourhood"; import { LinkQuery } from "./LinkQuery"; import { Perspective } from "./Perspective"; @@ -118,18 +118,18 @@ export default class PerspectiveResolver { } @Mutation(returns => [LinkExpression]) - perspectiveRemoveLinks(@Arg('uuid') uuid: string, @Arg('links', type => [LinkInput]) links: LinkInput[], @PubSub() pubSub: any): LinkExpression[] { + perspectiveRemoveLinks(@Arg('uuid') uuid: string, @Arg('links', type => [LinkExpressionInput]) links: LinkExpressionInput[], @PubSub() pubSub: any): LinkExpression[] { const l = new LinkExpression() l.author = 'did:ad4m:test' l.timestamp = Date.now() l.proof = testLink.proof - l.data = links[0] + l.data = links[0].data const l2 = new LinkExpression() l2.author = 'did:ad4m:test' l2.timestamp = Date.now() l2.proof = testLink.proof - l2.data = links[0] + l2.data = links[0].data pubSub.publish(LINK_REMOVED_TOPIC, { link: l }) pubSub.publish(LINK_ADDED_TOPIC, { link: l2 }) @@ -137,7 +137,7 @@ export default class PerspectiveResolver { } @Mutation(returns => LinkExpressionMutations) - perspectiveLinkMutations(@Arg('uuid') uuid: string, @Arg('mutations') mutations: LinkInputMutations, @PubSub() pubSub: any): LinkExpressionMutations { + perspectiveLinkMutations(@Arg('uuid') uuid: string, @Arg('mutations') mutations: LinkMutations, @PubSub() pubSub: any): LinkExpressionMutations { const perspectiveAddLinks = this.perspectiveAddLinks(uuid, mutations.additions, pubSub); const perspectiveRemoveLinks = this.perspectiveRemoveLinks(uuid, mutations.removals, pubSub); return new LinkExpressionMutations(perspectiveAddLinks, perspectiveRemoveLinks) From 676debc0d387ab052fb2b227a1842c9558446170 Mon Sep 17 00:00:00 2001 From: Joshua Parkin Date: Wed, 7 Dec 2022 12:31:05 -0800 Subject: [PATCH 3/4] fix: smoke tests for removeLinks() --- core/src/Ad4mClient.test.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/core/src/Ad4mClient.test.ts b/core/src/Ad4mClient.test.ts index f5015ca05..9b9a874f6 100644 --- a/core/src/Ad4mClient.test.ts +++ b/core/src/Ad4mClient.test.ts @@ -499,7 +499,10 @@ describe('Ad4mClient', () => { }) it('addLinks() smoke test', async () => { - const links = await ad4mClient.perspective.addLinks('00001', [{source: 'root', target: 'lang://Qm123', predicate: 'p'}]) + const links = await ad4mClient.perspective.addLinks('00001', [ + {source: 'root', target: 'lang://Qm123', predicate: 'p'}, + {source: 'root', target: 'lang://Qm123', predicate: 'p'} + ]) expect(links.length).toBe(2) expect(links[0].author).toBe('did:ad4m:test') expect(links[0].data.source).toBe('root') @@ -508,7 +511,10 @@ describe('Ad4mClient', () => { }) it('removeLinks() smoke test', async () => { - const links = await ad4mClient.perspective.removeLinks('00001', [{source: 'root', target: 'lang://Qm123', predicate: 'p'}]) + const links = await ad4mClient.perspective.removeLinks('00001', [ + {author: '', timestamp: '', proof: {signature: '', key: ''}, data: {source: 'root', target: 'lang://Qm123', predicate: 'p'}}, + {author: '', timestamp: '', proof: {signature: '', key: ''}, data: {source: 'root', target: 'lang://Qm123', predicate: 'p'}} + ]) expect(links.length).toBe(2) expect(links[0].author).toBe('did:ad4m:test') expect(links[0].data.source).toBe('root') @@ -518,8 +524,14 @@ describe('Ad4mClient', () => { it('linkMutations() smoke test', async () => { const mutations = await ad4mClient.perspective.linkMutations('00001', { - additions: [{source: 'root', target: 'lang://Qm123', predicate: 'p'}], - removals: [{source: 'root', target: 'lang://Qm123', predicate: 'p'}] + additions: [ + {source: 'root', target: 'lang://Qm123', predicate: 'p'}, + {source: 'root', target: 'lang://Qm123', predicate: 'p'} + ], + removals: [ + {author: '', timestamp: '', proof: {signature: '', key: ''}, data: {source: 'root', target: 'lang://Qm123', predicate: 'p'}}, + {author: '', timestamp: '', proof: {signature: '', key: ''}, data: {source: 'root', target: 'lang://Qm123', predicate: 'p'}} + ] }); expect(mutations.additions.length).toBe(2) expect(mutations.removals.length).toBe(2) From dd753a8ca8a102d1c77e37a7c64555a443dd06bc Mon Sep 17 00:00:00 2001 From: Joshua Parkin Date: Wed, 7 Dec 2022 12:31:29 -0800 Subject: [PATCH 4/4] add: graphql methods for new link mutation methods & integration tests --- executor/src/core/Perspective.ts | 84 +++++++++++++++++-- .../src/core/graphQL-interface/GraphQL.ts | 21 +++++ executor/src/tests/perspective.ts | 44 ++++++++++ 3 files changed, 144 insertions(+), 5 deletions(-) diff --git a/executor/src/core/Perspective.ts b/executor/src/core/Perspective.ts index 0900684d6..142cd4302 100644 --- a/executor/src/core/Perspective.ts +++ b/executor/src/core/Perspective.ts @@ -1,4 +1,4 @@ -import { Agent, Expression, Neighbourhood, LinkExpression, LinkExpressionInput, LinkInput, LanguageRef, PerspectiveHandle, Literal, PerspectiveDiff, parseExprUrl, Perspective as Ad4mPerspective, LinkMutations } from "@perspect3vism/ad4m" +import { Agent, Expression, Neighbourhood, LinkExpression, LinkExpressionInput, LinkInput, LanguageRef, PerspectiveHandle, Literal, PerspectiveDiff, parseExprUrl, Perspective as Ad4mPerspective, LinkMutations, LinkExpressionMutations } from "@perspect3vism/ad4m" import { Link, linkEqual, LinkQuery } from "@perspect3vism/ad4m"; import { SHA3 } from "sha3"; import type AgentService from "./agent/AgentService"; @@ -338,10 +338,6 @@ export default class Perspective { } } - async addLinkMutation(linkMutation: LinkMutations): Promise { - - } - async addLink(link: LinkInput | LinkExpressionInput): Promise { const linkExpression = this.ensureLinkExpression(link); const diff = { @@ -364,6 +360,84 @@ export default class Perspective { return linkExpression } + async addLinks(links: (LinkInput | LinkExpressionInput)[]): Promise { + const linkExpressions = links.map(l => this.ensureLinkExpression(l)); + const diff = { + additions: linkExpressions, + removals: [] + } as PerspectiveDiff + const addLinks = await this.commit(diff); + + if (!addLinks) { + this.#db.addPendingDiff(this.uuid, diff); + } + + linkExpressions.forEach(l => this.addLocalLink(l)) + this.#prologNeedsRebuild = true; + for (const link of linkExpressions) { + this.#pubsub.publish(PubSub.LINK_ADDED_TOPIC, { + perspective: this.plain(), + link: link + }) + }; + + return linkExpressions + } + + async removeLinks(links: LinkInput[]): Promise { + const linkExpressions = links.map(l => this.ensureLinkExpression(l)); + const diff = { + additions: [], + removals: linkExpressions + } as PerspectiveDiff + const removeLinks = await this.commit(diff); + + if (!removeLinks) { + this.#db.addPendingDiff(this.uuid, diff); + } + + linkExpressions.forEach(l => this.removeLocalLink(l)) + this.#prologNeedsRebuild = true; + for (const link of linkExpressions) { + this.#pubsub.publish(PubSub.LINK_ADDED_TOPIC, { + perspective: this.plain(), + link: link + }) + }; + + return linkExpressions + } + + async linkMutations(mutations: LinkMutations): Promise { + const diff = { + additions: mutations.additions.map(l => this.ensureLinkExpression(l)), + removals: mutations.removals.map(l => this.ensureLinkExpression(l)) + }; + const mutation = await this.commit(diff); + + if (!mutation) { + this.#db.addPendingDiff(this.uuid, diff); + }; + + diff.additions.forEach(l => this.addLocalLink(l)) + diff.removals.forEach(l => this.removeLocalLink(l)) + this.#prologNeedsRebuild = true; + for (const link of diff.additions) { + this.#pubsub.publish(PubSub.LINK_ADDED_TOPIC, { + perspective: this.plain(), + link: link + }); + }; + for (const link of diff.removals) { + this.#pubsub.publish(PubSub.LINK_REMOVED_TOPIC, { + perspective: this.plain(), + link: link + }); + }; + + return diff; + } + private findLink(linkToFind: LinkExpressionInput): string | undefined { const allLinks = this.#db.getAllLinks(this.uuid) for(const {name, link} of allLinks) { diff --git a/executor/src/core/graphQL-interface/GraphQL.ts b/executor/src/core/graphQL-interface/GraphQL.ts index 64643a9cf..c01cd241a 100644 --- a/executor/src/core/graphQL-interface/GraphQL.ts +++ b/executor/src/core/graphQL-interface/GraphQL.ts @@ -524,6 +524,13 @@ function createResolvers(core: PerspectivismCore, config: OuterConfig) { return await perspective.addLink(link) }, //@ts-ignore + perspectiveAddLinks: async (parent, args, context, info) => { + const { uuid, links } = args + checkCapability(context.capabilities, Auth.perspectiveUpdateCapability([uuid])) + const perspective = core.perspectivesController.perspective(uuid) + return await perspective.addLinks(links) + }, + //@ts-ignore perspectiveAddLinkExpression: async (parent, args, context, info) => { const { uuid, link } = args checkCapability(context.capabilities, Auth.perspectiveUpdateCapability([uuid])) @@ -547,6 +554,20 @@ function createResolvers(core: PerspectivismCore, config: OuterConfig) { return true }, //@ts-ignore + perspectiveRemoveLinks: async (parent, args, context, info) => { + const { uuid, links } = args + checkCapability(context.capabilities, Auth.perspectiveUpdateCapability([uuid])) + const perspective = core.perspectivesController.perspective(uuid) + return await perspective.removeLinks(links) + }, + //@ts-ignore + perspectiveLinkMutations: async (parent, args, context, info) => { + const { uuid, mutations } = args + checkCapability(context.capabilities, Auth.perspectiveUpdateCapability([uuid])) + const perspective = core.perspectivesController.perspective(uuid) + return await perspective.linkMutations(mutations) + }, + //@ts-ignore perspectiveUpdate: (parent, args, context, info) => { const { uuid, name } = args checkCapability(context.capabilities, Auth.perspectiveUpdateCapability([uuid])) diff --git a/executor/src/tests/perspective.ts b/executor/src/tests/perspective.ts index a363e7c1f..288ad9f3b 100644 --- a/executor/src/tests/perspective.ts +++ b/executor/src/tests/perspective.ts @@ -58,6 +58,50 @@ export default function perspectiveTests(testContext: TestContext) { expect(snapshot.links.length).to.equal(0); }) + it('can make mutations using perspective addLinks(), removeLinks() & linkMutations()', async () => { + const ad4mClient = testContext.ad4mClient!; + + const create = await ad4mClient.perspective.add("test-mutations"); + expect(create.name).to.equal("test-mutations"); + + const links = [ + new Link({ + source: "test://test-source", + predicate: "test://test-predicate", + target: "test://test-target" + }), + new Link({ + source: "test://test-source2", + predicate: "test://test-predicate2", + target: "test://test-target2" + }) + ]; + const linkAdds = await create.addLinks(links); + expect(linkAdds.length).to.equal(2); + + const linksPostAdd = await create.get({} as LinkQuery); + expect(linksPostAdd.length).to.equal(2); + + const linkRemoves = await create.removeLinks(linkAdds); + expect(linkRemoves.length).to.equal(2); + + const linksPostRemove = await create.get({} as LinkQuery); + expect(linksPostRemove.length).to.equal(0); + + const addTwoMore = await create.addLinks(links); + + const linkMutation = { + additions: links, + removals: addTwoMore + }; + const linkMutations = await create.linkMutations(linkMutation); + expect(linkMutations.additions.length).to.equal(2); + expect(linkMutations.removals.length).to.equal(2); + + const linksPostMutation = await create.get({} as LinkQuery); + expect(linksPostMutation.length).to.equal(2); + }) + it('test local perspective links - time query', async () => { const ad4mClient = testContext.ad4mClient!