From de5d056f7a6bfbfb9eb008fa695302d502276176 Mon Sep 17 00:00:00 2001 From: Jackson Morgan Date: Thu, 27 Feb 2025 22:58:31 -0500 Subject: [PATCH] Set Refactor for Solid-react complete --- .../jsonld-dataset-proxy/src/ProxyContext.ts | 30 +++++-- packages/jsonld-dataset-proxy/src/index.ts | 1 + packages/solid-react/src/useMatchObject.ts | 4 +- packages/solid-react/src/useMatchSubject.ts | 4 +- .../src/util/TrackingProxyContext.ts | 87 ++++--------------- .../solid-react/src/util/TrackingSetProxy.ts | 56 ++++++++++++ .../src/util/TrackingSubjectProxy.ts | 43 +++++++++ .../solid-react/test/.ldo/post.typings.ts | 8 +- .../solid-react/test/Integration.test.tsx | 14 ++- 9 files changed, 159 insertions(+), 88 deletions(-) create mode 100644 packages/solid-react/src/util/TrackingSetProxy.ts create mode 100644 packages/solid-react/src/util/TrackingSubjectProxy.ts diff --git a/packages/jsonld-dataset-proxy/src/ProxyContext.ts b/packages/jsonld-dataset-proxy/src/ProxyContext.ts index 344c0d5..24ccfd5 100644 --- a/packages/jsonld-dataset-proxy/src/ProxyContext.ts +++ b/packages/jsonld-dataset-proxy/src/ProxyContext.ts @@ -44,17 +44,17 @@ export class ProxyContext { public createSubjectProxy(node: NamedNode | BlankNode): SubjectProxy { if (!this.subjectMap.has(node.value)) { - const proxy = new Proxy( - { "@id": node }, - this.createSubjectHandler(), - ) as unknown as SubjectProxy; + const proxy = this.createNewSubjectProxy(node); this.subjectMap.set(node.value, proxy); } return this.subjectMap.get(node.value) as SubjectProxy; } - protected createSubjectHandler() { - return createSubjectHandler(this); + protected createNewSubjectProxy(node: NamedNode | BlankNode): SubjectProxy { + return new Proxy( + { "@id": node }, + createSubjectHandler(this), + ) as unknown as SubjectProxy; } private getSetKey(...quadMatch: QuadMatch) { @@ -72,10 +72,9 @@ export class ProxyContext { ): SetProxy { const key = this.getSetKey(...quadMatch); if (!this.setMap.has(key)) { - const proxy = createNewSetProxy( + const proxy = this.createNewSetProxy( quadMatch, - isSubjectOriented ?? false, - this, + isSubjectOriented, isLangStringSet, ); this.setMap.set(key, proxy); @@ -83,6 +82,19 @@ export class ProxyContext { return this.setMap.get(key)!; } + protected createNewSetProxy( + quadMatch: QuadMatch, + isSubjectOriented?: boolean, + isLangStringSet?: boolean, + ) { + return createNewSetProxy( + quadMatch, + isSubjectOriented ?? false, + this, + isLangStringSet, + ); + } + public duplicate(alternativeOptions: Partial) { const fullOptions: ProxyContextOptions = { ...{ diff --git a/packages/jsonld-dataset-proxy/src/index.ts b/packages/jsonld-dataset-proxy/src/index.ts index b006244..b75f426 100644 --- a/packages/jsonld-dataset-proxy/src/index.ts +++ b/packages/jsonld-dataset-proxy/src/index.ts @@ -17,6 +17,7 @@ export * from "./language/languageSet"; export * from "./language/languageTypes"; export * from "./language/languageUtils"; +export * from "./setProxy/createNewSetProxy"; export * from "./setProxy/isSetProxy"; export * from "./setProxy/SetProxy"; export * from "./setProxy/set"; diff --git a/packages/solid-react/src/useMatchObject.ts b/packages/solid-react/src/useMatchObject.ts index 53d7980..c338646 100644 --- a/packages/solid-react/src/useMatchObject.ts +++ b/packages/solid-react/src/useMatchObject.ts @@ -1,4 +1,4 @@ -import type { LdoBase, ShapeType } from "@ldo/ldo"; +import type { LdoBase, LdSet, ShapeType } from "@ldo/ldo"; import type { QuadMatch } from "@ldo/rdf-utils"; import type { LdoBuilder } from "@ldo/ldo"; import { useCallback } from "react"; @@ -9,7 +9,7 @@ export function useMatchObject( subject?: QuadMatch[0] | string, predicate?: QuadMatch[1] | string, graph?: QuadMatch[3] | string, -): Type[] { +): LdSet { const matchObject = useCallback( (builder: LdoBuilder) => { return builder.matchObject(subject, predicate, graph); diff --git a/packages/solid-react/src/useMatchSubject.ts b/packages/solid-react/src/useMatchSubject.ts index 80c1d81..494afc8 100644 --- a/packages/solid-react/src/useMatchSubject.ts +++ b/packages/solid-react/src/useMatchSubject.ts @@ -1,4 +1,4 @@ -import type { LdoBase, ShapeType } from "@ldo/ldo"; +import type { LdoBase, LdSet, ShapeType } from "@ldo/ldo"; import type { QuadMatch } from "@ldo/rdf-utils"; import type { LdoBuilder } from "@ldo/ldo"; import { useCallback } from "react"; @@ -9,7 +9,7 @@ export function useMatchSubject( predicate?: QuadMatch[1] | string, object?: QuadMatch[2] | string, graph?: QuadMatch[3] | string, -): Type[] { +): LdSet { const matchSubject = useCallback( (builder: LdoBuilder) => { return builder.matchSubject(predicate, object, graph); diff --git a/packages/solid-react/src/util/TrackingProxyContext.ts b/packages/solid-react/src/util/TrackingProxyContext.ts index 1b97717..3bbd84a 100644 --- a/packages/solid-react/src/util/TrackingProxyContext.ts +++ b/packages/solid-react/src/util/TrackingProxyContext.ts @@ -1,13 +1,14 @@ import type { - ArrayProxyTarget, - SubjectProxyTarget, ProxyContextOptions, + SubjectProxy, + SetProxy, } from "@ldo/jsonld-dataset-proxy"; import { ProxyContext } from "@ldo/jsonld-dataset-proxy"; import type { QuadMatch } from "@ldo/rdf-utils"; import type { SubscribableDataset } from "@ldo/subscribable-dataset"; -import { namedNode } from "@rdfjs/data-model"; -import type { Quad } from "@rdfjs/types"; +import type { BlankNode, NamedNode, Quad } from "@rdfjs/types"; +import { createTrackingSubjectProxy } from "./TrackingSubjectProxy"; +import { createTrackingSetProxy } from "./TrackingSetProxy"; /** * @internal @@ -34,77 +35,27 @@ export class TrackingProxyContext extends ProxyContext { } // Adds the listener to the subscribable dataset while ensuring deduping of the listener - private addListener(eventName: QuadMatch) { + public addListener(eventName: QuadMatch) { const listeners = this.subscribableDataset.listeners(eventName); if (!listeners.includes(this.listener)) { this.subscribableDataset.on(eventName, this.listener); } } - protected createSubjectHandler(): ProxyHandler { - const baseHandler = super.createSubjectHandler(); - const oldGetFunction = baseHandler.get; - const newGetFunction: ProxyHandler["get"] = ( - target: SubjectProxyTarget, - key: string | symbol, - receiver, - ) => { - const subject = target["@id"]; - const rdfTypes = this.getRdfType(subject); - if (typeof key === "symbol") { - // Do Nothing - } else if (key === "@id") { - this.addListener([subject, null, null, null]); - } else if (!this.contextUtil.isArray(key, rdfTypes)) { - const predicate = namedNode(this.contextUtil.keyToIri(key, rdfTypes)); - this.addListener([subject, predicate, null, null]); - } - return oldGetFunction && oldGetFunction(target, key, receiver); - }; - baseHandler.get = newGetFunction; - baseHandler.set = () => { - console.warn( - "You've attempted to set a value on a Linked Data Object from the useSubject, useMatchingSubject, or useMatchingObject hooks. These linked data objects should only be used to render data, not modify it. To modify data, use the `changeData` function.", - ); - return true; - }; - return baseHandler; + protected createNewSubjectProxy(node: NamedNode | BlankNode): SubjectProxy { + return createTrackingSubjectProxy(this, node); } - protected createArrayHandler(): ProxyHandler { - const baseHandler = super.createArrayHandler(); - const oldGetFunction = baseHandler.get; - const newGetFunction: ProxyHandler["get"] = ( - target: ArrayProxyTarget, - key: string | symbol, - receiver, - ) => { - if (qualifiedArrayMethods.has(key)) { - this.addListener([target[0][0], target[0][1], target[0][2], null]); - } - return oldGetFunction && oldGetFunction(target, key, receiver); - }; - baseHandler.get = newGetFunction; - return baseHandler; + protected createNewSetProxy( + quadMatch: QuadMatch, + isSubjectOriented?: boolean, + isLangStringSet?: boolean, + ): SetProxy { + return createTrackingSetProxy( + this, + quadMatch, + isSubjectOriented, + isLangStringSet, + ); } } - -const qualifiedArrayMethods = new Set([ - "forEach", - "map", - "reduce", - Symbol.iterator, - "entries", - "every", - "filter", - "find", - "findIndex", - "findLast", - "findLastIndex", - "includes, indexOf", - "keys", - "lastIndexOf", - "reduceRight", - "some", - "values", -]); diff --git a/packages/solid-react/src/util/TrackingSetProxy.ts b/packages/solid-react/src/util/TrackingSetProxy.ts new file mode 100644 index 0000000..9c34347 --- /dev/null +++ b/packages/solid-react/src/util/TrackingSetProxy.ts @@ -0,0 +1,56 @@ +import { createNewSetProxy, type SetProxy } from "@ldo/jsonld-dataset-proxy"; +import type { TrackingProxyContext } from "./TrackingProxyContext"; +import type { QuadMatch } from "@ldo/rdf-utils"; + +export function createTrackingSetProxy( + proxyContext: TrackingProxyContext, + quadMatch: QuadMatch, + isSubjectOriented?: boolean, + isLangStringSet?: boolean, +): SetProxy { + const baseSetProxy = createNewSetProxy( + quadMatch, + isSubjectOriented ?? false, + proxyContext, + isLangStringSet, + ); + + return new Proxy(baseSetProxy, { + get: (target: SetProxy, key: string | symbol, receiver) => { + if (trackingMethods.has(key)) { + proxyContext.addListener(quadMatch); + } else if (disallowedMethods.has(key)) { + console.warn( + "You've attempted to modify a value on a Linked Data Object from the useSubject, useMatchingSubject, or useMatchingObject hooks. These linked data objects should only be used to render data, not modify it. To modify data, use the `changeData` function.", + ); + } + return Reflect.get(target, key, receiver); + }, + }); +} + +const trackingMethods = new Set([ + "has", + "size", + "entries", + "keys", + "values", + Symbol.iterator, + "every", + "every", + "some", + "forEach", + "map", + "reduce", + "toArray", + "toJSON", + "difference", + "intersection", + "isDisjointFrom", + "isSubsetOf", + "isSupersetOf", + "symmetricDifference", + "union", +]); + +const disallowedMethods = new Set(["add", "clear", "delete"]); diff --git a/packages/solid-react/src/util/TrackingSubjectProxy.ts b/packages/solid-react/src/util/TrackingSubjectProxy.ts new file mode 100644 index 0000000..54fa5fe --- /dev/null +++ b/packages/solid-react/src/util/TrackingSubjectProxy.ts @@ -0,0 +1,43 @@ +import type { SubjectProxyTarget } from "@ldo/jsonld-dataset-proxy"; +import { + createSubjectHandler, + type SubjectProxy, +} from "@ldo/jsonld-dataset-proxy"; +import type { BlankNode, NamedNode } from "@rdfjs/types"; +import type { TrackingProxyContext } from "./TrackingProxyContext"; +import { namedNode } from "@rdfjs/data-model"; + +export function createTrackingSubjectProxy( + proxyContext: TrackingProxyContext, + node: NamedNode | BlankNode, +): SubjectProxy { + const baseHandler = createSubjectHandler(proxyContext); + const oldGetFunction = baseHandler.get; + const newGetFunction: ProxyHandler["get"] = ( + target: SubjectProxyTarget, + key: string | symbol, + receiver, + ) => { + const subject = target["@id"]; + const rdfTypes = proxyContext.getRdfType(subject); + if (typeof key === "symbol") { + // Do Nothing + } else if (key === "@id") { + proxyContext.addListener([subject, null, null, null]); + } else if (!proxyContext.contextUtil.isSet(key, rdfTypes)) { + const predicate = namedNode( + proxyContext.contextUtil.keyToIri(key, rdfTypes), + ); + proxyContext.addListener([subject, predicate, null, null]); + } + return oldGetFunction && oldGetFunction(target, key, receiver); + }; + baseHandler.get = newGetFunction; + baseHandler.set = () => { + console.warn( + "You've attempted to set a value on a Linked Data Object from the useSubject, useMatchingSubject, or useMatchingObject hooks. These linked data objects should only be used to render data, not modify it. To modify data, use the `changeData` function.", + ); + return true; + }; + return new Proxy({ "@id": node }, baseHandler) as unknown as SubjectProxy; +} diff --git a/packages/solid-react/test/.ldo/post.typings.ts b/packages/solid-react/test/.ldo/post.typings.ts index 1425a9a..bf9ac17 100644 --- a/packages/solid-react/test/.ldo/post.typings.ts +++ b/packages/solid-react/test/.ldo/post.typings.ts @@ -1,4 +1,4 @@ -import { ContextDefinition } from "jsonld"; +import { LdSet, LdoJsonldContext } from "@ldo/ldo"; /** * ============================================================================= @@ -11,7 +11,7 @@ import { ContextDefinition } from "jsonld"; */ export interface PostSh { "@id"?: string; - "@context"?: ContextDefinition; + "@context"?: LdoJsonldContext; type: | { "@id": "SocialMediaPosting"; @@ -39,7 +39,7 @@ export interface PostSh { /** * The publisher of the creative work. */ - publisher: { + publisher: LdSet<{ "@id": string; - }[]; + }>; } diff --git a/packages/solid-react/test/Integration.test.tsx b/packages/solid-react/test/Integration.test.tsx index 38ff66e..5dd3f1c 100644 --- a/packages/solid-react/test/Integration.test.tsx +++ b/packages/solid-react/test/Integration.test.tsx @@ -252,7 +252,7 @@ describe("Integration Tests", () => { await screen.findByText("test"); }); - it("renders the array value from the useSubject value", async () => { + it("renders the set value from the useSubject value", async () => { const UseSubjectTest: FunctionComponent = () => { const resource = useResource(SAMPLE_DATA_URI); const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`); @@ -260,7 +260,7 @@ describe("Integration Tests", () => { return (
-

{post.publisher[0]["@id"]}

+

{post.publisher.toArray()[0]["@id"]}

    {post.publisher.map((publisher) => { return
  • {publisher["@id"]}
  • ; @@ -349,7 +349,12 @@ describe("Integration Tests", () => { return (

    {post.articleBody}

    -
    @@ -368,6 +373,9 @@ describe("Integration Tests", () => { expect(warn).toHaveBeenCalledWith( "You've attempted to set a value on a Linked Data Object from the useSubject, useMatchingSubject, or useMatchingObject hooks. These linked data objects should only be used to render data, not modify it. To modify data, use the `changeData` function.", ); + expect(warn).toHaveBeenCalledWith( + "You've attempted to modify a value on a Linked Data Object from the useSubject, useMatchingSubject, or useMatchingObject hooks. These linked data objects should only be used to render data, not modify it. To modify data, use the `changeData` function.", + ); warn.mockReset(); });