parent
e4f434eeb3
commit
0c4b5e0770
@ -0,0 +1,72 @@ |
|||||||
|
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 { |
||||||
|
nodeEventListener, |
||||||
|
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<Quad>; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @internal |
||||||
|
* A listener that gets triggered whenever there's an update |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @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: nodeEventListener<Quad>; |
||||||
|
private subscribableDataset: SubscribableDataset<Quad>; |
||||||
|
|
||||||
|
constructor( |
||||||
|
options: TrackingProxyContextOptions, |
||||||
|
listener: nodeEventListener<Quad>, |
||||||
|
) { |
||||||
|
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, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
import { createNewSetProxy, type SetProxy } from "@ldo/jsonld-dataset-proxy"; |
||||||
|
import type { TrackingProxyContext } from "./TrackingProxyContext"; |
||||||
|
import type { QuadMatch } from "@ldo/rdf-utils"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @internal |
||||||
|
* |
||||||
|
* Creates a tracking proxy for a set, a proxy that tracks the fields that have |
||||||
|
* been accessed. |
||||||
|
*/ |
||||||
|
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<string | symbol>(["add", "clear", "delete"]); |
@ -0,0 +1,49 @@ |
|||||||
|
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"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @internal |
||||||
|
* |
||||||
|
* Creates a tracking proxy for a single value, a proxy that tracks the fields |
||||||
|
* that have been accessed. |
||||||
|
*/ |
||||||
|
export function createTrackingSubjectProxy( |
||||||
|
proxyContext: TrackingProxyContext, |
||||||
|
node: NamedNode | BlankNode, |
||||||
|
): SubjectProxy { |
||||||
|
const baseHandler = createSubjectHandler(proxyContext); |
||||||
|
const oldGetFunction = baseHandler.get; |
||||||
|
const newGetFunction: ProxyHandler<SubjectProxyTarget>["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; |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
import { |
||||||
|
ContextUtil, |
||||||
|
JsonldDatasetProxyBuilder, |
||||||
|
} from "@ldo/jsonld-dataset-proxy"; |
||||||
|
import { LdoBuilder } from "@ldo/ldo"; |
||||||
|
import type { LdoBase, LdoDataset, ShapeType } from "@ldo/ldo"; |
||||||
|
import { TrackingProxyContext } from "./TrackingProxyContext"; |
||||||
|
import { defaultGraph } from "@rdfjs/data-model"; |
||||||
|
import type { nodeEventListener } from "@ldo/subscribable-dataset"; |
||||||
|
import type { Quad } from "@rdfjs/types"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @internal |
||||||
|
* Creates a Linked Data Object builder that when creating linked data objects |
||||||
|
* it tracks when something that was read from it is updated and triggers some |
||||||
|
* action based on that. |
||||||
|
*/ |
||||||
|
export function createTrackingProxyBuilder<Type extends LdoBase>( |
||||||
|
dataset: LdoDataset, |
||||||
|
shapeType: ShapeType<Type>, |
||||||
|
onUpdate: nodeEventListener<Quad>, |
||||||
|
): LdoBuilder<Type> { |
||||||
|
// Remove all current subscriptions
|
||||||
|
// dataset.removeListenerFromAllEvents(onUpdate);
|
||||||
|
|
||||||
|
// 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"], |
||||||
|
}, |
||||||
|
onUpdate, |
||||||
|
); |
||||||
|
const builder = new LdoBuilder( |
||||||
|
new JsonldDatasetProxyBuilder(proxyContext), |
||||||
|
shapeType, |
||||||
|
); |
||||||
|
return builder; |
||||||
|
} |
@ -1,15 +1,15 @@ |
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */ |
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||||
import EventEmitter from "events"; |
import EventEmitter from "events"; |
||||||
import type { ResourceError } from "../src"; |
import type { ResourceError } from "../../src"; |
||||||
import { |
import { |
||||||
Unfetched, |
Unfetched, |
||||||
type ConnectedResult, |
type ConnectedResult, |
||||||
type Resource, |
type Resource, |
||||||
type ResourceEventEmitter, |
type ResourceEventEmitter, |
||||||
} from "../src"; |
} from "../../src"; |
||||||
import type { DatasetChanges } from "@ldo/rdf-utils"; |
import type { DatasetChanges } from "@ldo/rdf-utils"; |
||||||
import type { ReadSuccess } from "../src/results/success/ReadSuccess"; |
import type { ReadSuccess } from "../../src/results/success/ReadSuccess"; |
||||||
import type { UpdateSuccess } from "../src/results/success/UpdateSuccess"; |
import type { UpdateSuccess } from "../../src/results/success/UpdateSuccess"; |
||||||
|
|
||||||
export class MockResouce |
export class MockResouce |
||||||
extends (EventEmitter as new () => ResourceEventEmitter) |
extends (EventEmitter as new () => ResourceEventEmitter) |
Loading…
Reference in new issue