Completed Set Heirarchy

main
Jackson Morgan 7 months ago
parent 227c94b94f
commit f799b155e2
  1. 13
      packages/jsonld-dataset-proxy/src/JsonldDatasetProxyBuilder.ts
  2. 46
      packages/jsonld-dataset-proxy/src/ProxyContext.ts
  3. 29
      packages/jsonld-dataset-proxy/src/graphOf.ts
  4. 9
      packages/jsonld-dataset-proxy/src/index.ts
  5. 41
      packages/jsonld-dataset-proxy/src/setProxy/ObjectSetProxy.ts
  6. 108
      packages/jsonld-dataset-proxy/src/setProxy/SetProxy.ts
  7. 39
      packages/jsonld-dataset-proxy/src/setProxy/SubjectSetProxy.ts
  8. 117
      packages/jsonld-dataset-proxy/src/setProxy/WildcardObjectSetProxy.ts
  9. 63
      packages/jsonld-dataset-proxy/src/setProxy/WildcardSubjectSetProxy.ts
  10. 177
      packages/jsonld-dataset-proxy/src/setProxy/createSetHandler.ts
  11. 5
      packages/jsonld-dataset-proxy/src/setProxy/isSetProxy.ts
  12. 7
      packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts
  13. 2
      packages/jsonld-dataset-proxy/src/setProxy/ldSet/LdSet.ts
  14. 141
      packages/jsonld-dataset-proxy/src/setProxy/modifyArray.ts
  15. 4
      packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts
  16. 1
      packages/jsonld-dataset-proxy/src/types.ts
  17. 10
      packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts

@ -4,6 +4,7 @@ import type { GraphNode, QuadMatch } from "@ldo/rdf-utils";
import type { LanguageOrdering } from "./language/languageTypes";
import type { ProxyContext } from "./ProxyContext";
import type { ObjectLike } from "./types";
import type { LdSet } from "./setProxy/ldSet/LdSet";
/**
* Helps build JSON LD Dataset Proxies for a specific dataset and context
@ -56,14 +57,14 @@ export class JsonldDatasetProxyBuilder {
* @param graph The graph to match
*/
matchSubject<T extends ObjectLike>(
predicate?: QuadMatch[1],
predicate: QuadMatch[1],
object?: QuadMatch[2],
graph?: QuadMatch[3],
): T[] {
return this.proxyContext.createArrayProxy(
): LdSet<T> {
return this.proxyContext.createSetProxy(
[null, predicate, object, graph],
true,
) as unknown as T[];
) as LdSet<T>;
}
/**
@ -75,10 +76,10 @@ export class JsonldDatasetProxyBuilder {
*/
matchObject<T extends ObjectLike>(
subject?: QuadMatch[0],
predicate?: QuadMatch[1],
predicate: QuadMatch[1],
graph?: QuadMatch[3],
): T[] {
return this.proxyContext.createArrayProxy([
return this.proxyContext.createSetProxy([
subject,
predicate,
null,

@ -1,21 +1,18 @@
import type { GraphNode, QuadMatch, SubjectNode } from "@ldo/rdf-utils";
import type { BlankNode, Dataset, NamedNode } from "@rdfjs/types";
import type { ArrayProxyTarget } from "./setProxy/createSetHandler";
import { createArrayHandler } from "./setProxy/createSetHandler";
import { createSubjectHandler } from "./subjectProxy/createSubjectHandler";
import type { SubjectProxy } from "./subjectProxy/SubjectProxy";
import type { ArrayProxy } from "./setProxy/ldSet/LdSet";
import { _getUnderlyingArrayTarget } from "./types";
import { SetProxy } from "./setProxy/setProxy";
import type { ContextUtil } from "./ContextUtil";
import type { LanguageOrdering } from "./language/languageTypes";
import { namedNode } from "@rdfjs/data-model";
import type { RawObject } from "./util/RawObject";
export interface ProxyContextOptions {
dataset: Dataset;
contextUtil: ContextUtil;
writeGraphs: GraphNode[];
languageOrdering: LanguageOrdering;
prefilledArrayTargets?: ArrayProxyTarget[];
state?: Record<string, unknown>;
}
@ -28,7 +25,7 @@ const rdfType = namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
*/
export class ProxyContext {
private subjectMap: Map<string, SubjectProxy> = new Map();
private arrayMap: Map<string, ArrayProxy> = new Map();
private setMap: Map<string, SetProxy<RawObject>> = new Map();
readonly dataset: Dataset;
readonly contextUtil: ContextUtil;
@ -42,11 +39,6 @@ export class ProxyContext {
this.writeGraphs = options.writeGraphs;
this.languageOrdering = options.languageOrdering;
this.state = options.state || {};
if (options.prefilledArrayTargets) {
options.prefilledArrayTargets.forEach((target) => {
this.createArrayProxy(target[0], target[2], target);
});
}
}
public createSubjectProxy(node: NamedNode | BlankNode): SubjectProxy {
@ -64,7 +56,7 @@ export class ProxyContext {
return createSubjectHandler(this);
}
private getArrayKey(...quadMatch: QuadMatch) {
private getSetKey(...quadMatch: QuadMatch) {
return `${quadMatch[0]?.value || "undefined"}|${
quadMatch[1]?.value || "undefined"
}|${quadMatch[2]?.value || "undefined"}|${
@ -72,39 +64,25 @@ export class ProxyContext {
}`;
}
public createArrayProxy(
public createSetProxy(
quadMatch: QuadMatch,
isSubjectOriented = false,
initialTarget?: ArrayProxyTarget,
isLangStringArray?: boolean,
): ArrayProxy {
const key = this.getArrayKey(...quadMatch);
if (!this.arrayMap.has(key)) {
const proxy = new Proxy(
initialTarget || [quadMatch, [], isSubjectOriented, isLangStringArray],
this.createArrayHandler(),
) as unknown as ArrayProxy;
this.arrayMap.set(key, proxy);
isLangStringSet?: boolean,
): SetProxy {
const key = this.getSetKey(...quadMatch);
if (!this.setMap.has(key)) {
const proxy = new SetProxy(this, quadMatch, isLangStringSet);
this.setMap.set(key, proxy);
}
return this.arrayMap.get(key) as ArrayProxy;
}
protected createArrayHandler() {
return createArrayHandler(this);
return this.setMap.get(key)!;
}
public duplicate(alternativeOptions: Partial<ProxyContextOptions>) {
const prefilledArrayTargets: ArrayProxyTarget[] = [];
this.arrayMap.forEach((value) => {
prefilledArrayTargets.push(value[_getUnderlyingArrayTarget]);
});
const fullOptions: ProxyContextOptions = {
...{
dataset: this.dataset,
contextUtil: this.contextUtil,
writeGraphs: this.writeGraphs,
languageOrdering: this.languageOrdering,
prefilledArrayTargets,
},
...alternativeOptions,
};

@ -1,12 +1,8 @@
import type { ObjectNode, GraphNode } from "@ldo/rdf-utils";
import { namedNode } from "@rdfjs/data-model";
import {
getSubjectProxyFromObject,
isSubjectProxy,
} from "./subjectProxy/isSubjectProxy";
import { getSubjectProxyFromObject } from "./subjectProxy/isSubjectProxy";
import type { ObjectLike } from "./types";
import {
_getNodeAtIndex,
_getUnderlyingDataset,
_getUnderlyingMatch,
_getUnderlyingNode,
@ -17,15 +13,16 @@ import {
* Returns the graph for which a defined triple is a member
* @param subject A JsonldDatasetProxy that represents the subject
* @param predicate The key on the JsonldDatasetProxy
* @param object The direct object. This can be a JsonldDatasetProxy or the index
* @param object The direct object. This can be a JsonldDatasetProxy. This field
* is optional if this field does not have a "set" object.
* @returns a list of graphs for which the triples are members
*/
export function graphOf<Subject extends ObjectLike, Key extends keyof Subject>(
subject: Subject,
predicate: Key,
object?: NonNullable<Subject[Key]> extends Array<unknown>
? number | ObjectLike
: ObjectLike,
object: NonNullable<Subject[Key]> extends Set<infer T>
? T
: Subject[Key] | undefined,
): GraphNode[] {
const subjectProxy = getSubjectProxyFromObject(subject);
const proxyContext = subjectProxy[_proxyContext];
@ -39,20 +36,6 @@ export function graphOf<Subject extends ObjectLike, Key extends keyof Subject>(
let objectNode: ObjectNode | null;
if (object == null) {
objectNode = null;
} else if (typeof object === "number") {
const proxyArray = subject[predicate];
if (!proxyArray[_getUnderlyingMatch]) {
throw new Error(
`Key "${String(predicate)}" of ${subject} is not an array.`,
);
}
if (!proxyArray[object]) {
throw new Error(`Index ${object} does not exist.`);
}
if (isSubjectProxy(proxyArray[object])) {
objectNode = proxyArray[object][1];
}
objectNode = proxyArray[_getNodeAtIndex](object);
} else {
const objectProxy = getSubjectProxyFromObject(object);
objectNode = objectProxy[_getUnderlyingNode];

@ -17,11 +17,10 @@ export * from "./language/languageSet";
export * from "./language/languageTypes";
export * from "./language/languageUtils";
export * from "./arrayProxy/createArrayHandler";
export * from "./arrayProxy/arrayMethods";
export * from "./arrayProxy/ArrayProxy";
export * from "./arrayProxy/modifyArray";
export * from "./arrayProxy/isArrayProxy";
export * from "./setProxy/isSetProxy";
export * from "./setProxy/setProxy";
export * from "./setProxy/ldSet/LdSet";
export * from "./setProxy/ldSet/BasicLdSet";
export * from "./subjectProxy/createSubjectHandler";
export * from "./subjectProxy/SubjectProxy";

@ -0,0 +1,41 @@
import type { GraphNode, PredicateNode, SubjectNode } from "@ldo/rdf-utils";
import type { RawObject, RawValue } from "../util/RawObject";
import { WildcardObjectSetProxy } from "./WildcardObjectSetProxy";
import { addObjectToDataset } from "../util/addObjectToDataset";
import type { ProxyContext } from "../ProxyContext";
export type ObjectSetProxyQuadMatch = [
SubjectNode,
PredicateNode,
undefined | null,
GraphNode | undefined | null,
];
export class ObjectSetProxy<
T extends NonNullable<RawValue>,
> extends WildcardObjectSetProxy<T> {
protected quadMatch: ObjectSetProxyQuadMatch;
constructor(context: ProxyContext, quadMatch: ObjectSetProxyQuadMatch) {
super(context, quadMatch);
this.quadMatch = quadMatch;
}
/**
* Appends a new element with a specified value to the end of the Set.
*/
add(value: T): this {
addObjectToDataset(
{
"@id": this.quadMatch[0],
[this.context.contextUtil.iriToKey(
this.quadMatch[1].value,
this.context.getRdfType(this.quadMatch[0]),
)]: value,
} as RawObject,
false,
this.context,
);
return this;
}
}

@ -2,8 +2,9 @@
* This file handles the underlying functionality of a set, including hidden
* helper methods
*/
import type { Dataset } from "@rdfjs/types";
import type { Dataset, Quad } from "@rdfjs/types";
import type {
GraphNode,
ObjectNode,
PredicateNode,
QuadMatch,
@ -16,87 +17,46 @@ import {
_isLangString,
_getUnderlyingMatch,
_getUnderlyingNode,
_writeGraphs,
} from "../types";
import type { ProxyContext } from "../ProxyContext";
import { addObjectToDataset } from "../util/addObjectToDataset";
import type { RawObject } from "../util/RawObject";
import type { RawValue } from "../util/RawObject";
import { nodeToJsonldRepresentation } from "../util/nodeToJsonldRepresentation";
import { BasicLdSet } from "./ldSet/BasicLdSet";
import { getNodeFromRawObject } from "../util/getNodeFromRaw";
export class SetProxy<T extends RawObject> extends BasicLdSet<T> {
private quadMatch: QuadMatch;
private isLangStringSet?: boolean;
/**
* A Set Proxy represents a set of items in a dataset and is a proxy for
* accessing those items in the dataset.
*/
export abstract class SetProxy<
T extends NonNullable<RawValue> = NonNullable<RawValue>,
> extends BasicLdSet<T> {
protected quadMatch: QuadMatch;
constructor(
context: ProxyContext,
quadMatch: QuadMatch,
isLangStringSet?: boolean,
) {
constructor(context: ProxyContext, quadMatch: QuadMatch) {
super(context);
this.quadMatch = quadMatch;
this.isLangStringSet = isLangStringSet;
}
/**
* Detects if this set is subject oriented. The set is subject oriented if the
* given quadMatch has a predicate and an object but no subject, meanting
*/
private isSubjectOriented(): boolean {
if (this.quadMatch[0] && this.quadMatch[1] && !this.quadMatch[2])
return false;
if (this.quadMatch[1] && !this.quadMatch[0]) return true;
throw new Error(
`SetProxy has an invalid quad match: [${this.quadMatch[0]}, ${this.quadMatch[1]}, ${this.quadMatch[2]}, ${this.quadMatch[3]}]`,
);
}
/**
* Gets the subject, predicate and object for this set
*/
private getSPO(value?: T): {
protected abstract getSPO(value?: T): {
subject?: SubjectNode;
predicate: PredicateNode;
predicate?: PredicateNode;
object?: ObjectNode;
} {
const valueNode = value
? getNodeFromRawObject(value, this.context.contextUtil)
: undefined;
const subject: SubjectNode | undefined = this.isSubjectOriented()
? valueNode
: this.quadMatch[0]!;
const predicate = this.quadMatch[1]!;
const object: ObjectNode | undefined = this.isSubjectOriented()
? this.quadMatch[2] ?? undefined
: valueNode;
return { subject, predicate, object };
}
add(value: T): this {
// Add value
const added = addObjectToDataset(value as RawObject, false, this.context);
// Add connecting edges
if (!this.isSubjectOriented) {
addObjectToDataset(
{
"@id": this.quadMatch[0],
[this.context.contextUtil.iriToKey(
this.quadMatch[1]!.value,
this.context.getRdfType(this.quadMatch[0]!),
)]: added,
} as RawObject,
false,
this.context,
);
} else {
// Account for subject-oriented
added[
this.context.contextUtil.iriToKey(
this.quadMatch[1]!.value,
this.context.getRdfType(added[_getUnderlyingNode]),
)
] = nodeToJsonldRepresentation(this.quadMatch[2]!, this.context);
}
};
protected abstract getNodeOfFocus(quad: Quad): SubjectNode | ObjectNode;
/**
* The add method on a wildcard set does nothing.
* @deprecated You cannot add data to a wildcard set as it is simply a proxy to an underlying dataset
*/
add(_value: T) {
console.warn(
'You\'ve attempted to call "add" on a wildcard set. You cannot add data to a wildcard set as it is simply a proxy to an underlying dataset',
);
return this;
}
@ -146,7 +106,7 @@ export class SetProxy<T extends RawObject> extends BasicLdSet<T> {
const { subject, predicate, object } = this.getSPO();
const quads = dataset.match(subject, predicate, object);
const collection: T[] = quads.toArray().map((quad) => {
const quadSubject = this.isSubjectOriented() ? quad.object : quad.subject;
const quadSubject = this.getNodeOfFocus(quad);
return nodeToJsonldRepresentation(quadSubject, this.context) as T;
});
return new Set(collection)[Symbol.iterator]();
@ -165,14 +125,6 @@ export class SetProxy<T extends RawObject> extends BasicLdSet<T> {
return this.quadMatch;
}
get [_isSubjectOriented](): boolean {
return this.isSubjectOriented();
}
get [_isLangString](): boolean {
return !!this.isLangStringSet;
}
get [_proxyContext](): ProxyContext {
return this.context;
}
@ -180,4 +132,8 @@ export class SetProxy<T extends RawObject> extends BasicLdSet<T> {
set [_proxyContext](newContext: ProxyContext) {
this.context = newContext;
}
get [_writeGraphs](): GraphNode[] {
return this.context.writeGraphs;
}
}

@ -0,0 +1,39 @@
import type { GraphNode, ObjectNode, PredicateNode } from "@ldo/rdf-utils";
import type { RawObject } from "../util/RawObject";
import { addObjectToDataset } from "../util/addObjectToDataset";
import type { ProxyContext } from "../ProxyContext";
import { WildcardSubjectSetProxy } from "./WildcardSubjectSetProxy";
import { _getUnderlyingNode } from "../types";
import { quad } from "@rdfjs/data-model";
export type SubjectSetProxyQuadMatch = [
undefined | null,
PredicateNode,
ObjectNode,
GraphNode | undefined | null,
];
export class SubjectSetProxy<
T extends RawObject,
> extends WildcardSubjectSetProxy<T> {
protected quadMatch: SubjectSetProxyQuadMatch;
constructor(context: ProxyContext, quadMatch: SubjectSetProxyQuadMatch) {
super(context, quadMatch);
this.quadMatch = quadMatch;
}
/**
* Appends a new element with a specified value to the end of the Set.
*/
add(value: T): this {
const added = addObjectToDataset(value as RawObject, false, this.context);
const addedNode = added[_getUnderlyingNode];
this.context.writeGraphs.forEach((graph) => {
this.context.dataset.add(
quad(addedNode, this.quadMatch[1], this.quadMatch[2], graph),
);
});
return this;
}
}

@ -0,0 +1,117 @@
import type {
SubjectNode,
PredicateNode,
ObjectNode,
GraphNode,
} from "@ldo/rdf-utils";
import type { Dataset, Quad } from "@rdfjs/types";
import type { RawValue } from "../util/RawObject";
import { SetProxy } from "./setProxy";
import type { ProxyContext } from "../ProxyContext";
import { getNodeFromRawValue } from "../util/getNodeFromRaw";
export type WildcardObjectSetProxyQuadMatch = [
SubjectNode | undefined | null,
PredicateNode | undefined | null,
undefined | null,
GraphNode | undefined | null,
];
/**
* A WildcardObjectProxy represents a set of nodes in a dataset that are all the
* object of a given subject and predicate. Because this is a wildcard, the
* subject and predicate don't necissarily need to be defined.
*/
export class WildcardObjectSetProxy<
T extends NonNullable<RawValue>,
> extends SetProxy<T> {
protected quadMatch: WildcardObjectSetProxyQuadMatch;
constructor(
context: ProxyContext,
quadMatch: WildcardObjectSetProxyQuadMatch,
) {
super(context, quadMatch);
this.quadMatch = quadMatch;
}
protected getSPO(value?: T | undefined): {
subject?: SubjectNode;
predicate?: PredicateNode;
object?: ObjectNode;
} {
// Get the RDF Node that represents the value, skip is no value
const subject = this.quadMatch[0] ?? undefined;
const predicate = this.quadMatch[1] ?? undefined;
if (value) {
// Get datatype if applicable
let datatype: string | undefined = undefined;
if (this.quadMatch[0] && predicate) {
const rdfType = this.context.getRdfType(this.quadMatch[0]);
const key = this.context.contextUtil.iriToKey(predicate.value, rdfType);
datatype = this.context.contextUtil.getDataType(key, rdfType);
}
const valueNode = getNodeFromRawValue(value, this.context, datatype);
return {
subject,
predicate,
object: valueNode,
};
}
// SPO for no value
return {
subject,
predicate,
object: undefined,
};
}
protected getNodeOfFocus(quad: Quad): ObjectNode {
return quad.object as ObjectNode;
}
private manuallyMatchWithUnknownObjectNode(
subject: SubjectNode | undefined,
predicate: PredicateNode | undefined,
value: T,
): Dataset<Quad, Quad> {
// If there's not an object, that means that we don't know the object node
// and need to find it manually.
const matchingQuads = this.context.dataset.match(subject, predicate, null);
return matchingQuads.filter(
(quad) =>
quad.object.termType === "Literal" && quad.object.value === value,
);
}
delete(value: T): boolean {
const { dataset } = this.context;
const { subject, predicate, object } = this.getSPO(value);
if (!object) {
const matchedQuads = this.manuallyMatchWithUnknownObjectNode(
subject,
predicate,
value,
);
matchedQuads.forEach((quad) => dataset.delete(quad));
return matchedQuads.size > 0;
} else {
const willDelete = dataset.match(subject, predicate, object).size > 0;
dataset.deleteMatches(subject, predicate, object);
return willDelete;
}
}
has(value: T): boolean {
const { dataset } = this.context;
const { subject, predicate, object } = this.getSPO(value);
if (!object) {
return (
this.manuallyMatchWithUnknownObjectNode(subject, predicate, value)
.size > 0
);
} else {
return dataset.match(subject, predicate, object).size > 0;
}
}
}

@ -0,0 +1,63 @@
import type {
SubjectNode,
PredicateNode,
ObjectNode,
GraphNode,
} from "@ldo/rdf-utils";
import type { Quad } from "@rdfjs/types";
import type { RawObject } from "../util/RawObject";
import { SetProxy } from "./setProxy";
import type { ProxyContext } from "../ProxyContext";
import { getNodeFromRawObject } from "../util/getNodeFromRaw";
export type WildcardSubjectSetProxyQuadMatch = [
undefined | null,
PredicateNode | undefined | null,
ObjectNode | undefined | null,
GraphNode | undefined | null,
];
/**
* A WildcardObjectProxy represents a set of nodes in a dataset that are all the
* object of a given subject and predicate. Because this is a wildcard, the
* subject and predicate don't necissarily need to be defined.
*/
export class WildcardSubjectSetProxy<T extends RawObject> extends SetProxy<T> {
protected quadMatch: WildcardSubjectSetProxyQuadMatch;
constructor(
context: ProxyContext,
quadMatch: WildcardSubjectSetProxyQuadMatch,
) {
super(context, quadMatch);
this.quadMatch = quadMatch;
}
protected getSPO(value?: T | undefined): {
subject?: SubjectNode;
predicate?: PredicateNode;
object?: ObjectNode;
} {
// Get the RDF Node that represents the value, skip is no value
const predicate = this.quadMatch[1] ?? undefined;
const object = this.quadMatch[2] ?? undefined;
if (value) {
const valueNode = getNodeFromRawObject(value, this.context.contextUtil);
return {
subject: valueNode,
predicate,
object,
};
}
// SPO for no value
return {
subject: undefined,
predicate,
object,
};
}
protected getNodeOfFocus(quad: Quad): SubjectNode {
return quad.subject as SubjectNode;
}
}

@ -1,177 +0,0 @@
import { quad } from "@rdfjs/data-model";
import type { NamedNode } from "@rdfjs/types";
import type { ObjectNode, QuadMatch, SubjectNode } from "@ldo/rdf-utils";
import type { ObjectJsonRepresentation } from "../util/nodeToJsonldRepresentation";
import { nodeToJsonldRepresentation } from "../util/nodeToJsonldRepresentation";
import type { ArrayMethodBuildersType } from "./arrayMethods";
import { arrayMethodsBuilders, methodNames } from "./arrayMethods";
import {
_getNodeAtIndex,
_getUnderlyingArrayTarget,
_getUnderlyingDataset,
_getUnderlyingMatch,
_isSubjectOriented,
_proxyContext,
} from "../types";
import { modifyArray } from "./modifyArray";
import type { ProxyContext } from "../ProxyContext";
import { NodeSet } from "../util/NodeSet";
import { filterQuadsByLanguageOrdering } from "../language/languageUtils";
export type ArrayProxyTarget = [
quadMatch: QuadMatch,
curArray: ObjectNode[],
isSubjectOriented?: boolean,
isLangStringArray?: boolean,
];
function updateArrayOrder(
target: ArrayProxyTarget,
proxyContext: ProxyContext,
): void {
let quads = proxyContext.dataset.match(...target[0]);
if (target[3]) {
// Is lang string array
quads = filterQuadsByLanguageOrdering(quads, proxyContext.languageOrdering);
}
const datasetObjects = new NodeSet();
quads.toArray().forEach((quad) => {
// If this this a subject-oriented document
if (target[2]) {
datasetObjects.add(quad.subject as SubjectNode);
} else {
datasetObjects.add(quad.object as ObjectNode);
}
});
const processedObjects: ObjectNode[] = [];
target[1].forEach((arrItem) => {
if (datasetObjects.has(arrItem)) {
processedObjects.push(arrItem);
datasetObjects.delete(arrItem);
}
});
datasetObjects.toArray().forEach((datasetObject) => {
processedObjects.push(datasetObject);
});
target[1] = processedObjects;
}
function getProcessedArray(
target: ArrayProxyTarget,
proxyContext: ProxyContext,
): ObjectJsonRepresentation[] {
return target[1].map((node) => {
return nodeToJsonldRepresentation(node, proxyContext);
});
}
export function createArrayHandler(
proxyContext: ProxyContext,
): ProxyHandler<ArrayProxyTarget> {
return {
get(target, key, ...rest) {
switch (key) {
case _getUnderlyingDataset:
return proxyContext.dataset;
case _getUnderlyingMatch:
return target[0];
case _isSubjectOriented:
return target[2];
case _getUnderlyingArrayTarget:
return target;
case _proxyContext:
return proxyContext;
case _getNodeAtIndex:
return (index: number): ObjectNode | undefined => {
updateArrayOrder(target, proxyContext);
return target[1][index];
};
}
// TODO: Because of this, every get operation is O(n). Consider changing
// this
updateArrayOrder(target, proxyContext);
const processedArray = getProcessedArray(target, proxyContext);
if (methodNames.has(key as keyof ArrayMethodBuildersType)) {
return arrayMethodsBuilders[key as keyof ArrayMethodBuildersType](
target,
key as string,
proxyContext,
);
}
return Reflect.get(processedArray, key, ...rest);
},
getOwnPropertyDescriptor(target, key, ...rest) {
updateArrayOrder(target, proxyContext);
const processedArray = getProcessedArray(target, proxyContext);
return Reflect.getOwnPropertyDescriptor(processedArray, key, ...rest);
},
ownKeys(target, ...rest) {
updateArrayOrder(target, proxyContext);
const processedArray = getProcessedArray(target, proxyContext);
return Reflect.ownKeys(processedArray, ...rest);
},
getPrototypeOf(target, ...rest) {
updateArrayOrder(target, proxyContext);
const processedObjects = getProcessedArray(target, proxyContext);
return Reflect.getPrototypeOf(processedObjects, ...rest);
},
has(target, ...rest) {
updateArrayOrder(target, proxyContext);
const processedObjects = getProcessedArray(target, proxyContext);
return Reflect.has(processedObjects, ...rest);
},
set(target, key, value, ...rest) {
if (key === _proxyContext) {
proxyContext = value;
return true;
}
updateArrayOrder(target, proxyContext);
if (typeof key !== "symbol" && !isNaN(parseInt(key as string))) {
const index = parseInt(key);
return modifyArray(
{
target,
key,
toAdd: [value],
quadsToDelete(allQuads) {
return allQuads[index] ? [allQuads[index]] : [];
},
modifyCoreArray(coreArray, addedValues) {
coreArray[index] = addedValues[0];
return true;
},
},
proxyContext,
);
}
return Reflect.set(target[1], key, ...rest);
},
deleteProperty(target, key) {
const { dataset } = proxyContext;
if (typeof key !== "symbol" && !isNaN(parseInt(key as string))) {
const objectQuad = dataset.match(...target[0]).toArray()[parseInt(key)];
if (!objectQuad) {
return true;
}
const term = target[2] ? objectQuad.subject : objectQuad.object;
if (term.termType === "Literal") {
const subject = target[0][0] as NamedNode;
const predicate = target[0][1] as NamedNode;
if (subject && predicate) {
dataset.delete(quad(subject, predicate, term));
}
return true;
} else if (
term.termType === "NamedNode" ||
term.termType === "BlankNode"
) {
dataset.deleteMatches(term, undefined, undefined);
dataset.deleteMatches(undefined, undefined, term);
return true;
}
}
return true;
},
};
}

@ -1,9 +1,6 @@
import type { RawObject } from "../util/RawObject";
import { SetProxy } from "./setProxy";
export function isSetProxy(
someObject?: unknown,
): someObject is SetProxy<RawObject> {
export function isSetProxy(someObject?: unknown): someObject is SetProxy {
if (!someObject) return false;
if (typeof someObject !== "object") return false;
return someObject instanceof SetProxy;

@ -1,12 +1,12 @@
import type { ProxyContext } from "../../ProxyContext";
import type { SubjectProxy } from "../../subjectProxy/SubjectProxy";
import { _getUnderlyingNode } from "../../types";
import { getNodeFromRawObject } from "../../util/getNodeFromRaw";
import { nodeToString } from "../../util/NodeSet";
import type { RawObject } from "../../util/RawObject";
import type { RawValue } from "../../util/RawObject";
import type { LdSet } from "./LdSet";
/* eslint-disable @typescript-eslint/no-explicit-any */
export class BasicLdSet<T extends RawObject>
export class BasicLdSet<T extends NonNullable<RawValue> = NonNullable<RawValue>>
extends Set<T>
implements LdSet<T>
{
@ -19,6 +19,7 @@ export class BasicLdSet<T extends RawObject>
}
private hashFn(value: T) {
if (typeof value !== "object") return value.toString();
return nodeToString(getNodeFromRawObject(value, this.context.contextUtil));
}

@ -2,7 +2,7 @@
/**
* An abract representation for a set of Linked Data Objects
*/
interface LdSet<T> extends Set<T> {
export interface LdSet<T> extends Set<T> {
/**
* ===========================================================================
* BASE METHODS

@ -1,141 +0,0 @@
import { defaultGraph } from "@rdfjs/data-model";
import type { Quad } from "@rdfjs/types";
import type { ObjectNode } from "@ldo/rdf-utils";
import {
TransactionDataset,
createTransactionDatasetFactory,
} from "@ldo/subscribable-dataset";
import { createDatasetFactory } from "@ldo/dataset";
import type { ProxyContext } from "../ProxyContext";
import { addObjectToDataset } from "../util/addObjectToDataset";
import {
getNodeFromRawObject,
getNodeFromRawValue,
} from "../util/getNodeFromRaw";
import { nodeToString } from "../util/NodeSet";
import type { ObjectJsonRepresentation } from "../util/nodeToJsonldRepresentation";
import type { RawObject, RawValue } from "../util/RawObject";
import type { ArrayProxyTarget } from "./createSetHandler";
export function checkArrayModification(
target: ArrayProxyTarget,
objectsToAdd: RawValue[],
proxyContext: ProxyContext,
) {
if (target[2]) {
for (const objectToAdd of objectsToAdd) {
// Undefined is fine no matter what
if (objectToAdd === undefined) {
return;
}
if (typeof objectToAdd !== "object") {
throw new Error(
`Cannot add a literal "${objectToAdd}"(${typeof objectToAdd}) to a subject-oriented collection.`,
);
}
// Create a test dataset to see if the inputted data is valid
const testDataset = new TransactionDataset(
proxyContext.dataset,
createDatasetFactory(),
createTransactionDatasetFactory(),
);
addObjectToDataset(
objectToAdd as RawObject,
false,
proxyContext.duplicate({
writeGraphs: [defaultGraph()],
}),
);
const isValidAddition =
testDataset.match(
getNodeFromRawObject(objectToAdd, proxyContext.contextUtil),
target[0][1],
target[0][2],
).size !== 0;
if (!isValidAddition) {
throw new Error(
`Cannot add value to collection. This must contain a quad that matches (${nodeToString(
target[0][0],
)}, ${nodeToString(target[0][1])}, ${nodeToString(
target[0][2],
)}, ${nodeToString(target[0][3])})`,
);
}
}
} else if (!target[0][0] || !target[0][1]) {
throw new Error(
"A collection that does not specify a match for both a subject or predicate cannot be modified directly.",
);
}
}
export function modifyArray<ReturnType>(
config: {
target: ArrayProxyTarget;
key: string;
toAdd?: RawValue[];
quadsToDelete?: (quads: Quad[]) => Quad[];
modifyCoreArray: (
coreArray: ArrayProxyTarget[1],
addedValues: ArrayProxyTarget[1],
) => ReturnType;
},
proxyContext: ProxyContext,
): ReturnType {
const { target, toAdd, quadsToDelete, modifyCoreArray, key } = config;
const { dataset, contextUtil } = proxyContext;
checkArrayModification(target, toAdd || [], proxyContext);
// Remove appropriate Quads
if (quadsToDelete) {
const quadArr = dataset.match(...target[0]).toArray();
const deleteQuadArr = quadsToDelete(quadArr);
// Filter out overlapping items
deleteQuadArr.forEach((delQuad) => {
if (target[2]) {
dataset.deleteMatches(delQuad.subject, undefined, undefined);
} else {
dataset.delete(delQuad);
}
});
}
// Add new items to the dataset
const added = toAdd
?.map((item) => {
return typeof item === "object"
? addObjectToDataset(item, false, proxyContext)
: item;
})
.filter(
(val) => val != undefined,
) as NonNullable<ObjectJsonRepresentation>[];
if (!target[2] && target[0][0] && target[0][1] && added) {
addObjectToDataset(
{
"@id": target[0][0],
[contextUtil.iriToKey(
target[0][1].value,
proxyContext.getRdfType(target[0][0]),
)]: added,
} as RawObject,
false,
proxyContext,
);
}
const addedNodes = added
? (added
.map((addedValue) => {
return getNodeFromRawValue(
key,
addedValue,
target[0][0] ? proxyContext.getRdfType(target[0][0]) : [],
proxyContext,
);
})
.filter((val) => val != undefined) as ObjectNode[])
: [];
// Allow the base array to be modified
return modifyCoreArray(target[1], addedNodes);
}

@ -2,7 +2,7 @@ import type { SubjectProxyTarget } from "./createSubjectHandler";
import { namedNode } from "@rdfjs/data-model";
import { nodeToJsonldRepresentation } from "../util/nodeToJsonldRepresentation";
import type { SubjectProxy } from "./SubjectProxy";
import type { ArrayProxy } from "../setProxy/ldSet/LdSet";
import type { SetProxy } from "../setProxy/setProxy";
import type { ProxyContext } from "../ProxyContext";
import { filterQuadsByLanguageOrdering } from "../language/languageUtils";
@ -13,7 +13,7 @@ export function getValueForKey(
target: SubjectProxyTarget,
key: string | symbol,
proxyContext: ProxyContext,
): SubjectProxy | ArrayProxy | string | number | boolean | undefined {
): SubjectProxy | SetProxy | string | number | boolean | undefined {
const { contextUtil, dataset } = proxyContext;
if (key === "@id") {
if (target["@id"].termType === "BlankNode") {

@ -1,6 +1,7 @@
export const _getUnderlyingNode = Symbol("_getUnderlyingNode");
export const _getUnderlyingMatch = Symbol("_getUnderlyingMatch");
export const _isSubjectOriented = Symbol("_isSubjectOriented");
export const _isLangString = Symbol("_isLangString");
export const _getUnderlyingDataset = Symbol("_getUnderlyingDataset");
export const _proxyContext = Symbol("_proxyContext");
export const _writeGraphs = Symbol("_writeGraphs");

@ -23,10 +23,10 @@ export function getNodeFromRawObject(
}
export function getNodeFromRawValue(
key: string,
value: RawValue,
rdfTypes: NamedNode[],
proxyContext: ProxyContext,
// To get this run proxyContext.contextUtil.getDataType(key, proxyContext.getRdfType(subjectNode))
datatype?: string,
): BlankNode | NamedNode | Literal | undefined {
// Get the Object Node
if (value == undefined) {
@ -36,9 +36,9 @@ export function getNodeFromRawValue(
typeof value === "boolean" ||
typeof value === "number"
) {
// PICKUP: figure out how to handle looking for the RDF Types of a raw value
const datatype = proxyContext.contextUtil.getDataType(key, rdfTypes);
if (datatype === "@id") {
if (!datatype) {
return undefined;
} else if (datatype === "@id") {
return namedNode(value.toString());
} else {
return literal(value.toString(), datatype);

Loading…
Cancel
Save