From 108b1d677bf290f2deda24e7a9734983e504fb7f Mon Sep 17 00:00:00 2001 From: Jackson Morgan Date: Tue, 25 Mar 2025 13:16:42 -0400 Subject: [PATCH] Before slight refactor to have dataset provided by context --- packages/react/src/createLdoReactMethods.ts | 7 +- packages/solid-react/package.json | 5 +- .../src/BrowserSolidLdoProvider.tsx | 168 ++++++++++-------- packages/solid-react/src/SolidLdoProvider.tsx | 55 ------ .../src/UnauthenticatedSolidLdoProvider.tsx | 3 +- packages/solid-react/src/defaultIntance.ts | 15 ++ packages/solid-react/src/index.ts | 7 +- packages/solid-react/src/useLdoMethods.ts | 84 --------- packages/solid-react/src/useMatchObject.ts | 21 --- packages/solid-react/src/useMatchSubject.ts | 21 --- packages/solid-react/src/useResource.ts | 114 ------------ packages/solid-react/src/useSubject.ts | 30 ---- .../solid-react/src/useSubscribeToResource.ts | 52 ------ .../src/util/TrackingProxyContext.ts | 61 ------- .../solid-react/src/util/TrackingSetProxy.ts | 56 ------ .../src/util/TrackingSubjectProxy.ts | 43 ----- .../solid-react/src/util/useTrackingProxy.ts | 55 ------ 17 files changed, 116 insertions(+), 681 deletions(-) delete mode 100644 packages/solid-react/src/SolidLdoProvider.tsx create mode 100644 packages/solid-react/src/defaultIntance.ts delete mode 100644 packages/solid-react/src/useLdoMethods.ts delete mode 100644 packages/solid-react/src/useMatchObject.ts delete mode 100644 packages/solid-react/src/useMatchSubject.ts delete mode 100644 packages/solid-react/src/useResource.ts delete mode 100644 packages/solid-react/src/useSubject.ts delete mode 100644 packages/solid-react/src/useSubscribeToResource.ts delete mode 100644 packages/solid-react/src/util/TrackingProxyContext.ts delete mode 100644 packages/solid-react/src/util/TrackingSetProxy.ts delete mode 100644 packages/solid-react/src/util/TrackingSubjectProxy.ts delete mode 100644 packages/solid-react/src/util/useTrackingProxy.ts diff --git a/packages/react/src/createLdoReactMethods.ts b/packages/react/src/createLdoReactMethods.ts index e84fbdc..57e3f2d 100644 --- a/packages/react/src/createLdoReactMethods.ts +++ b/packages/react/src/createLdoReactMethods.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { createUseLdo } from "./methods/useLdo"; import { createConnectedLdoDataset, @@ -9,9 +10,9 @@ import { createUseResource } from "./methods/useResource"; import { createUseSubject } from "./methods/useSubject"; import { createUseSubscribeToResource } from "./methods/useSubscribeToResource"; -export function createLdoReactMethods( - plugins: Plugins, -) { +export function createLdoReactMethods< + Plugins extends ConnectedPlugin[], +>(plugins: Plugins) { const dataset = createConnectedLdoDataset(plugins); dataset.setMaxListeners(1000); diff --git a/packages/solid-react/package.json b/packages/solid-react/package.json index b2b13a1..311e565 100644 --- a/packages/solid-react/package.json +++ b/packages/solid-react/package.json @@ -37,11 +37,8 @@ }, "dependencies": { "@inrupt/solid-client-authn-browser": "^2.0.0", - "@ldo/dataset": "^1.0.0-alpha.1", - "@ldo/jsonld-dataset-proxy": "^1.0.0-alpha.1", - "@ldo/ldo": "^1.0.0-alpha.1", "@ldo/connected": "^1.0.0-alpha.1", - "@ldo/subscribable-dataset": "^1.0.0-alpha.1", + "@ldo/connected-solid": "^1.0.0-alpha.1", "@rdfjs/data-model": "^1.2.0", "cross-fetch": "^3.1.6" }, diff --git a/packages/solid-react/src/BrowserSolidLdoProvider.tsx b/packages/solid-react/src/BrowserSolidLdoProvider.tsx index 6213890..69fd4dc 100644 --- a/packages/solid-react/src/BrowserSolidLdoProvider.tsx +++ b/packages/solid-react/src/BrowserSolidLdoProvider.tsx @@ -9,89 +9,109 @@ import { logout as libraryLogout, fetch as libraryFetch, } from "@inrupt/solid-client-authn-browser"; -import { SolidLdoProvider } from "./SolidLdoProvider"; +import type { ConnectedLdoDataset } from "@ldo/connected"; +import type { SolidConnectedPlugin } from "@ldo/connected-solid"; const PRE_REDIRECT_URI = "PRE_REDIRECT_URI"; -export const BrowserSolidLdoProvider: FunctionComponent = ({ - children, -}) => { - const [session, setSession] = useState(getDefaultSession().info); - const [ranInitialAuthCheck, setRanInitialAuthCheck] = useState(false); +export function createBrowserSolidLdoProvider( + dataset: ConnectedLdoDataset<(SolidConnectedPlugin | SolidConnectedPlugin)[]>, +) { + dataset.setContext("solid", { fetch: libraryFetch }); - const runInitialAuthCheck = useCallback(async () => { - if (!window.localStorage.getItem(PRE_REDIRECT_URI)) { - window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href); - } + const BrowserSolidLdoProvider: FunctionComponent = ({ + children, + }) => { + const [session, setSession] = useState( + getDefaultSession().info, + ); + const [ranInitialAuthCheck, setRanInitialAuthCheck] = useState(false); - await handleIncomingRedirect({ - restorePreviousSession: true, - }); - // Set timout to ensure this happens after the redirect - setTimeout(() => { - setSession({ ...getDefaultSession().info }); - window.history.replaceState( - {}, - "", - window.localStorage.getItem(PRE_REDIRECT_URI), - ); - window.localStorage.removeItem(PRE_REDIRECT_URI); + const runInitialAuthCheck = useCallback(async () => { + if (!window.localStorage.getItem(PRE_REDIRECT_URI)) { + window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href); + } + + await handleIncomingRedirect({ + restorePreviousSession: true, + }); + // Set timout to ensure this happens after the redirect + setTimeout(() => { + setSession({ ...getDefaultSession().info }); + window.history.replaceState( + {}, + "", + window.localStorage.getItem(PRE_REDIRECT_URI), + ); + window.localStorage.removeItem(PRE_REDIRECT_URI); - setRanInitialAuthCheck(true); - }, 0); - }, []); + setRanInitialAuthCheck(true); + }, 0); + }, []); - const login = useCallback(async (issuer: string, options?: LoginOptions) => { - const cleanUrl = new URL(window.location.href); - cleanUrl.hash = ""; - cleanUrl.search = ""; - const fullOptions = { - redirectUrl: cleanUrl.toString(), - clientName: "Solid App", - oidcIssuer: issuer, - ...options, - }; - window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href); - await libraryLogin(fullOptions); - setSession({ ...getDefaultSession().info }); - }, []); + const login = useCallback( + async (issuer: string, options?: LoginOptions) => { + const cleanUrl = new URL(window.location.href); + cleanUrl.hash = ""; + cleanUrl.search = ""; + const fullOptions = { + redirectUrl: cleanUrl.toString(), + clientName: "Solid App", + oidcIssuer: issuer, + ...options, + }; + window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href); + await libraryLogin(fullOptions); + setSession({ ...getDefaultSession().info }); + }, + [], + ); - const logout = useCallback(async () => { - await libraryLogout(); - setSession({ ...getDefaultSession().info }); - }, []); + const logout = useCallback(async () => { + await libraryLogout(); + setSession({ ...getDefaultSession().info }); + }, []); - const signUp = useCallback( - async (issuer: string, options?: LoginOptions) => { - // The typings on @inrupt/solid-client-authn-core have not yet been updated - // TODO: remove this ts-ignore when they are updated. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return login(issuer, { ...options, prompt: "create" }); - }, - [login], - ); + const signUp = useCallback( + async (issuer: string, options?: LoginOptions) => { + // The typings on @inrupt/solid-client-authn-core have not yet been updated + // TODO: remove this ts-ignore when they are updated. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return login(issuer, { ...options, prompt: "create" }); + }, + [login], + ); - useEffect(() => { - runInitialAuthCheck(); - }, []); + useEffect(() => { + runInitialAuthCheck(); + }, []); - const solidAuthFunctions = useMemo( - () => ({ - runInitialAuthCheck, - login, - logout, - signUp, - session, - ranInitialAuthCheck, - fetch: libraryFetch, - }), - [login, logout, ranInitialAuthCheck, runInitialAuthCheck, session, signUp], - ); + const solidAuthFunctions = useMemo( + () => ({ + runInitialAuthCheck, + login, + logout, + signUp, + session, + ranInitialAuthCheck, + fetch: libraryFetch, + }), + [ + login, + logout, + ranInitialAuthCheck, + runInitialAuthCheck, + session, + signUp, + ], + ); - return ( - - {children} - - ); -}; + return ( + + {children} + + ); + }; + return BrowserSolidLdoProvider; +} diff --git a/packages/solid-react/src/SolidLdoProvider.tsx b/packages/solid-react/src/SolidLdoProvider.tsx deleted file mode 100644 index a0c9664..0000000 --- a/packages/solid-react/src/SolidLdoProvider.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { createContext, useContext } from "react"; -import { - useMemo, - type FunctionComponent, - type PropsWithChildren, - useEffect, -} from "react"; -import { useSolidAuth } from "./SolidAuthContext"; -import type { SolidLdoDataset } from "@ldo/solid"; -import { createSolidLdoDataset } from "@ldo/solid"; -import type { UseLdoMethods } from "./useLdoMethods"; -import { createUseLdoMethods } from "./useLdoMethods"; - -export const SolidLdoReactContext = - // This will be set in the provider - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - createContext(undefined); - -export function useLdo(): UseLdoMethods { - return useContext(SolidLdoReactContext); -} - -export interface SolidLdoProviderProps extends PropsWithChildren {} - -export const SolidLdoProvider: FunctionComponent = ({ - children, -}) => { - const { fetch } = useSolidAuth(); - - // Initialize storeDependencies before render - const solidLdoDataset: SolidLdoDataset = useMemo(() => { - const ldoDataset = createSolidLdoDataset({ - fetch, - }); - ldoDataset.setMaxListeners(1000); - return ldoDataset; - }, []); - - // Keep context in sync with props - useEffect(() => { - solidLdoDataset.context.fetch = fetch; - }, [fetch]); - - const value: UseLdoMethods = useMemo( - () => createUseLdoMethods(solidLdoDataset), - [solidLdoDataset], - ); - - return ( - - {children} - - ); -}; diff --git a/packages/solid-react/src/UnauthenticatedSolidLdoProvider.tsx b/packages/solid-react/src/UnauthenticatedSolidLdoProvider.tsx index 8f25de1..623c3d8 100644 --- a/packages/solid-react/src/UnauthenticatedSolidLdoProvider.tsx +++ b/packages/solid-react/src/UnauthenticatedSolidLdoProvider.tsx @@ -4,7 +4,6 @@ import type { FunctionComponent, PropsWithChildren } from "react"; import type { LoginOptions, SessionInfo } from "./SolidAuthContext"; import { SolidAuthContext } from "./SolidAuthContext"; import libraryFetch from "cross-fetch"; -import { SolidLdoProvider } from "./SolidLdoProvider"; const DUMMY_SESSION: SessionInfo = { isLoggedIn: false, @@ -56,7 +55,7 @@ export const UnauthenticatedSolidLdoProvider: FunctionComponent< return ( - {children} + {children} ); }; diff --git a/packages/solid-react/src/defaultIntance.ts b/packages/solid-react/src/defaultIntance.ts new file mode 100644 index 0000000..39d72b3 --- /dev/null +++ b/packages/solid-react/src/defaultIntance.ts @@ -0,0 +1,15 @@ +import { solidConnectedPlugin } from "@ldo/connected-solid"; +import { createLdoReactMethods } from "@ldo/react"; +import { createBrowserSolidLdoProvider } from "./BrowserSolidLdoProvider"; + +export const { + dataset, + useLdo, + useMatchObject, + useMatchSubject, + useResource, + useSubject, + useSubscribeToResource, +} = createLdoReactMethods([solidConnectedPlugin]); + +export const BrowserSolidLdoProvider = createBrowserSolidLdoProvider(dataset); diff --git a/packages/solid-react/src/index.ts b/packages/solid-react/src/index.ts index edbcc74..025f86c 100644 --- a/packages/solid-react/src/index.ts +++ b/packages/solid-react/src/index.ts @@ -1,12 +1,7 @@ export * from "./BrowserSolidLdoProvider"; export * from "./UnauthenticatedSolidLdoProvider"; export * from "./SolidAuthContext"; - -export { useLdo } from "./SolidLdoProvider"; +export * from "./defaultIntance"; // hooks -export * from "./useResource"; -export * from "./useSubject"; -export * from "./useMatchSubject"; -export * from "./useMatchObject"; export * from "./useRootContainer"; diff --git a/packages/solid-react/src/useLdoMethods.ts b/packages/solid-react/src/useLdoMethods.ts deleted file mode 100644 index fc95869..0000000 --- a/packages/solid-react/src/useLdoMethods.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { LdoBase, ShapeType } from "@ldo/ldo"; -import type { SubjectNode } from "@ldo/rdf-utils"; -import type { - Resource, - SolidLdoDataset, - SolidLdoTransactionDataset, -} from "@ldo/solid"; -import { changeData, commitData } from "@ldo/solid"; - -export interface UseLdoMethods { - dataset: SolidLdoDataset; - getResource: SolidLdoDataset["getResource"]; - getSubject( - shapeType: ShapeType, - subject: string | SubjectNode, - ): Type; - createData( - shapeType: ShapeType, - subject: string | SubjectNode, - resource: Resource, - ...additionalResources: Resource[] - ): Type; - changeData( - input: Type, - resource: Resource, - ...additionalResources: Resource[] - ): Type; - commitData( - input: LdoBase, - ): ReturnType; -} - -export function createUseLdoMethods(dataset: SolidLdoDataset): UseLdoMethods { - return { - dataset: dataset, - /** - * Gets a resource - */ - getResource: dataset.getResource.bind(dataset), - /** - * Returns a Linked Data Object for a subject - * @param shapeType The shape type for the data - * @param subject Subject Node - * @returns A Linked Data Object - */ - getSubject( - shapeType: ShapeType, - subject: string | SubjectNode, - ): Type { - return dataset.usingType(shapeType).fromSubject(subject); - }, - /** - * Begins tracking changes to eventually commit for a new subject - * @param shapeType The shape type that defines the created data - * @param subject The RDF subject for a Linked Data Object - * @param resources Any number of resources to which this data should be written - * @returns A Linked Data Object to modify and commit - */ - createData( - shapeType: ShapeType, - subject: string | SubjectNode, - resource: Resource, - ...additionalResources: Resource[] - ): Type { - return dataset.createData( - shapeType, - subject, - resource, - ...additionalResources, - ); - }, - /** - * Begins tracking changes to eventually commit - * @param input A linked data object to track changes on - * @param resources - */ - changeData: changeData, - /** - * Commits the transaction to the global dataset, syncing all subscribing - * components and Solid Pods - */ - commitData: commitData, - }; -} diff --git a/packages/solid-react/src/useMatchObject.ts b/packages/solid-react/src/useMatchObject.ts deleted file mode 100644 index c338646..0000000 --- a/packages/solid-react/src/useMatchObject.ts +++ /dev/null @@ -1,21 +0,0 @@ -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"; -import { useTrackingProxy } from "./util/useTrackingProxy"; - -export function useMatchObject( - shapeType: ShapeType, - subject?: QuadMatch[0] | string, - predicate?: QuadMatch[1] | string, - graph?: QuadMatch[3] | string, -): LdSet { - const matchObject = useCallback( - (builder: LdoBuilder) => { - return builder.matchObject(subject, predicate, graph); - }, - [subject, predicate, graph], - ); - - return useTrackingProxy(shapeType, matchObject); -} diff --git a/packages/solid-react/src/useMatchSubject.ts b/packages/solid-react/src/useMatchSubject.ts deleted file mode 100644 index 494afc8..0000000 --- a/packages/solid-react/src/useMatchSubject.ts +++ /dev/null @@ -1,21 +0,0 @@ -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"; -import { useTrackingProxy } from "./util/useTrackingProxy"; - -export function useMatchSubject( - shapeType: ShapeType, - predicate?: QuadMatch[1] | string, - object?: QuadMatch[2] | string, - graph?: QuadMatch[3] | string, -): LdSet { - const matchSubject = useCallback( - (builder: LdoBuilder) => { - return builder.matchSubject(predicate, object, graph); - }, - [predicate, object, graph], - ); - - return useTrackingProxy(shapeType, matchSubject); -} diff --git a/packages/solid-react/src/useResource.ts b/packages/solid-react/src/useResource.ts deleted file mode 100644 index fb3fffb..0000000 --- a/packages/solid-react/src/useResource.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { useMemo, useEffect, useRef, useState, useCallback } from "react"; -import type { - Container, - ContainerUri, - LeafUri, - Resource, - Leaf, -} from "@ldo/solid"; -import { useLdo } from "./SolidLdoProvider"; - -export interface UseResourceOptions { - suppressInitialRead?: boolean; - reloadOnMount?: boolean; - subscribe?: boolean; -} - -export function useResource( - uri: ContainerUri, - options?: UseResourceOptions, -): Container; -export function useResource(uri: LeafUri, options?: UseResourceOptions): Leaf; -export function useResource( - uri: string, - options?: UseResourceOptions, -): Leaf | Container; -export function useResource( - uri?: ContainerUri, - options?: UseResourceOptions, -): Container | undefined; -export function useResource( - uri?: LeafUri, - options?: UseResourceOptions, -): Leaf | undefined; -export function useResource( - uri?: string, - options?: UseResourceOptions, -): Leaf | Container | undefined; -export function useResource( - uri?: string, - options?: UseResourceOptions, -): Leaf | Container | undefined { - const { getResource } = useLdo(); - const subscriptionIdRef = useRef(); - - // Get the resource - const resource = useMemo(() => { - if (uri) { - const resource = getResource(uri); - // Run read operations if necissary - if (!options?.suppressInitialRead) { - if (options?.reloadOnMount) { - resource.read(); - } else { - resource.readIfUnfetched(); - } - } - return resource; - } - return undefined; - }, [getResource, uri]); - const [resourceRepresentation, setResourceRepresentation] = - useState(resource); - const pastResource = useRef< - { resource?: Resource; callback: () => void } | undefined - >(); - - useEffect(() => { - if (options?.subscribe) { - resource - ?.subscribeToNotifications() - .then((subscriptionId) => (subscriptionIdRef.current = subscriptionId)); - } else if (subscriptionIdRef.current) { - resource?.unsubscribeFromNotifications(subscriptionIdRef.current); - } - return () => { - if (subscriptionIdRef.current) - resource?.unsubscribeFromNotifications(subscriptionIdRef.current); - }; - }, [resource, options?.subscribe]); - - // Callback function to force the react dom to reload. - const forceReload = useCallback( - // Wrap the resource in a proxy so it's techically a different object - () => { - if (resource) setResourceRepresentation(new Proxy(resource, {})); - }, - [resource], - ); - - useEffect(() => { - // Remove listeners for the previous resource - if (pastResource.current?.resource) { - pastResource.current.resource.off( - "update", - pastResource.current.callback, - ); - } - // Set a new past resource to the current resource - pastResource.current = { resource, callback: forceReload }; - if (resource) { - // Add listener - resource.on("update", forceReload); - setResourceRepresentation(new Proxy(resource, {})); - - // Unsubscribe on unmount - return () => { - resource.off("update", forceReload); - }; - } else { - setResourceRepresentation(undefined); - } - }, [resource]); - return resourceRepresentation; -} diff --git a/packages/solid-react/src/useSubject.ts b/packages/solid-react/src/useSubject.ts deleted file mode 100644 index c728c6f..0000000 --- a/packages/solid-react/src/useSubject.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { SubjectNode } from "@ldo/rdf-utils"; -import type { ShapeType } from "@ldo/ldo"; -import type { LdoBuilder } from "@ldo/ldo"; -import type { LdoBase } from "@ldo/ldo"; -import { useCallback } from "react"; - -import { useTrackingProxy } from "./util/useTrackingProxy"; - -export function useSubject( - shapeType: ShapeType, - subject: string | SubjectNode, -): Type; -export function useSubject( - shapeType: ShapeType, - subject?: string | SubjectNode, -): Type | undefined; -export function useSubject( - shapeType: ShapeType, - subject?: string | SubjectNode, -): Type | undefined { - const fromSubject = useCallback( - (builder: LdoBuilder) => { - if (!subject) return; - return builder.fromSubject(subject); - }, - [subject], - ); - - return useTrackingProxy(shapeType, fromSubject); -} diff --git a/packages/solid-react/src/useSubscribeToResource.ts b/packages/solid-react/src/useSubscribeToResource.ts deleted file mode 100644 index d6dc8b0..0000000 --- a/packages/solid-react/src/useSubscribeToResource.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useLdo } from "./SolidLdoProvider"; -import { useEffect, useRef } from "react"; - -export function useSubscribeToResource(...uris: string[]): void { - const { dataset } = useLdo(); - const currentlySubscribed = useRef>({}); - useEffect(() => { - const resources = uris.map((uri) => dataset.getResource(uri)); - const previousSubscriptions = { ...currentlySubscribed.current }; - Promise.all( - resources.map(async (resource) => { - if (!previousSubscriptions[resource.uri]) { - // Prevent multiple triggers from created subscriptions while waiting - // for connection - currentlySubscribed.current[resource.uri] = "AWAITING"; - // Read and subscribe - await resource.readIfUnfetched(); - currentlySubscribed.current[resource.uri] = - await resource.subscribeToNotifications(); - } else { - delete previousSubscriptions[resource.uri]; - } - }), - ).then(async () => { - // Unsubscribe from all remaining previous subscriptions - await Promise.all( - Object.entries(previousSubscriptions).map( - async ([resourceUri, subscriptionId]) => { - // Unsubscribe - delete currentlySubscribed.current[resourceUri]; - const resource = dataset.getResource(resourceUri); - await resource.unsubscribeFromNotifications(subscriptionId); - }, - ), - ); - }); - }, [uris]); - - // Cleanup Subscriptions - useEffect(() => { - return () => { - Promise.all( - Object.entries(currentlySubscribed.current).map( - async ([resourceUri, subscriptionId]) => { - const resource = dataset.getResource(resourceUri); - await resource.unsubscribeFromNotifications(subscriptionId); - }, - ), - ); - }; - }, []); -} diff --git a/packages/solid-react/src/util/TrackingProxyContext.ts b/packages/solid-react/src/util/TrackingProxyContext.ts deleted file mode 100644 index 3bbd84a..0000000 --- a/packages/solid-react/src/util/TrackingProxyContext.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { - 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 type { BlankNode, NamedNode, Quad } from "@rdfjs/types"; -import { createTrackingSubjectProxy } from "./TrackingSubjectProxy"; -import { createTrackingSetProxy } from "./TrackingSetProxy"; - -/** - * @internal - * Options to be passed to the tracking proxy - */ -export interface TrackingProxyContextOptions extends ProxyContextOptions { - dataset: SubscribableDataset; -} - -/** - * @internal - * This proxy exists to ensure react components rerender at the right time. It - * keeps track of every key accessed in a Linked Data Object and only when the - * dataset is updated with that key does it rerender the react component. - */ -export class TrackingProxyContext extends ProxyContext { - private listener: () => void; - private subscribableDataset: SubscribableDataset; - - constructor(options: TrackingProxyContextOptions, listener: () => void) { - super(options); - this.subscribableDataset = options.dataset; - this.listener = listener; - } - - // Adds the listener to the subscribable dataset while ensuring deduping of the listener - public addListener(eventName: QuadMatch) { - const listeners = this.subscribableDataset.listeners(eventName); - if (!listeners.includes(this.listener)) { - this.subscribableDataset.on(eventName, this.listener); - } - } - - protected createNewSubjectProxy(node: NamedNode | BlankNode): SubjectProxy { - return createTrackingSubjectProxy(this, node); - } - - protected createNewSetProxy( - quadMatch: QuadMatch, - isSubjectOriented?: boolean, - isLangStringSet?: boolean, - ): SetProxy { - return createTrackingSetProxy( - this, - quadMatch, - isSubjectOriented, - isLangStringSet, - ); - } -} diff --git a/packages/solid-react/src/util/TrackingSetProxy.ts b/packages/solid-react/src/util/TrackingSetProxy.ts deleted file mode 100644 index 9c34347..0000000 --- a/packages/solid-react/src/util/TrackingSetProxy.ts +++ /dev/null @@ -1,56 +0,0 @@ -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 deleted file mode 100644 index 54fa5fe..0000000 --- a/packages/solid-react/src/util/TrackingSubjectProxy.ts +++ /dev/null @@ -1,43 +0,0 @@ -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/src/util/useTrackingProxy.ts b/packages/solid-react/src/util/useTrackingProxy.ts deleted file mode 100644 index 05fa452..0000000 --- a/packages/solid-react/src/util/useTrackingProxy.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - ContextUtil, - JsonldDatasetProxyBuilder, -} from "@ldo/jsonld-dataset-proxy"; -import { LdoBuilder } from "@ldo/ldo"; -import type { LdoBase, ShapeType } from "@ldo/ldo"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { TrackingProxyContext } from "./TrackingProxyContext"; -import { defaultGraph } from "@rdfjs/data-model"; -import { useLdo } from "../SolidLdoProvider"; - -export function useTrackingProxy( - shapeType: ShapeType, - createLdo: (builder: LdoBuilder) => ReturnType, -): ReturnType { - const { dataset } = useLdo(); - - const [forceUpdateCounter, setForceUpdateCounter] = useState(0); - const forceUpdate = useCallback( - () => setForceUpdateCounter((val) => val + 1), - [], - ); - - // The main linked data object - const linkedDataObject = useMemo(() => { - // Remove all current subscriptions - dataset.removeListenerFromAllEvents(forceUpdate); - - // Rebuild the LdoBuilder from scratch to inject TrackingProxyContext - const contextUtil = new ContextUtil(shapeType.context); - const proxyContext = new TrackingProxyContext( - { - dataset, - contextUtil, - writeGraphs: [defaultGraph()], - languageOrdering: ["none", "en", "other"], - }, - forceUpdate, - ); - const builder = new LdoBuilder( - new JsonldDatasetProxyBuilder(proxyContext), - shapeType, - ); - return createLdo(builder); - }, [shapeType, dataset, forceUpdateCounter, forceUpdate, createLdo]); - - useEffect(() => { - // Unregister force update listener upon unmount - return () => { - dataset.removeListenerFromAllEvents(forceUpdate); - }; - }, [shapeType]); - - return linkedDataObject; -}