import type { ArrayProxyTarget, SubjectProxyTarget, ProxyContextOptions, } 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"; /** * @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 private 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 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; } } const qualifiedArrayMethods = new Set([ "forEach", "map", "reduce", Symbol.iterator, "entries", "every", "filter", "find", "findIndex", "findLast", "findLastIndex", "includes, indexOf", "keys", "lastIndexOf", "reduceRight", "some", "values", ]);