From 227c94b94ffcce0c6e0af6f2bea5e9f8d70f6c4b Mon Sep 17 00:00:00 2001 From: Jackson Morgan Date: Tue, 18 Feb 2025 13:41:07 -0500 Subject: [PATCH] Created LdSet --- .../jsonld-dataset-proxy/src/ProxyContext.ts | 4 +- ...ateArrayHandler.ts => createSetHandler.ts} | 0 .../src/setProxy/isSetProxy.ts | 25 +-- .../src/setProxy/ldSet/BasicLdSet.ts | 78 ++++++- .../src/setProxy/ldSet/LdSet.ts | 2 +- .../src/setProxy/modifyArray.ts | 2 +- .../src/setProxy/setProxy.ts | 204 +++++++++++------- .../src/subjectProxy/SubjectProxy.ts | 2 +- packages/jsonld-dataset-proxy/src/types.ts | 2 - 9 files changed, 207 insertions(+), 112 deletions(-) rename packages/jsonld-dataset-proxy/src/setProxy/{createArrayHandler.ts => createSetHandler.ts} (100%) diff --git a/packages/jsonld-dataset-proxy/src/ProxyContext.ts b/packages/jsonld-dataset-proxy/src/ProxyContext.ts index 3f6dc45..f12817e 100644 --- a/packages/jsonld-dataset-proxy/src/ProxyContext.ts +++ b/packages/jsonld-dataset-proxy/src/ProxyContext.ts @@ -1,7 +1,7 @@ import type { GraphNode, QuadMatch, SubjectNode } from "@ldo/rdf-utils"; import type { BlankNode, Dataset, NamedNode } from "@rdfjs/types"; -import type { ArrayProxyTarget } from "./setProxy/createArrayHandler"; -import { createArrayHandler } from "./setProxy/createArrayHandler"; +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"; diff --git a/packages/jsonld-dataset-proxy/src/setProxy/createArrayHandler.ts b/packages/jsonld-dataset-proxy/src/setProxy/createSetHandler.ts similarity index 100% rename from packages/jsonld-dataset-proxy/src/setProxy/createArrayHandler.ts rename to packages/jsonld-dataset-proxy/src/setProxy/createSetHandler.ts diff --git a/packages/jsonld-dataset-proxy/src/setProxy/isSetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/isSetProxy.ts index 17ce027..60299c3 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/isSetProxy.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/isSetProxy.ts @@ -1,23 +1,10 @@ -import { - _getNodeAtIndex, - _getUnderlyingArrayTarget, - _getUnderlyingDataset, - _getUnderlyingMatch, - _getUnderlyingNode, - _proxyContext, - _writeGraphs, -} from "../types"; -import type { ArrayProxy } from "./ArrayProxy"; +import type { RawObject } from "../util/RawObject"; +import { SetProxy } from "./setProxy"; -export function isArrayProxy(someObject?: unknown): someObject is ArrayProxy { +export function isSetProxy( + someObject?: unknown, +): someObject is SetProxy { if (!someObject) return false; if (typeof someObject !== "object") return false; - const potentialArrayProxy = someObject as ArrayProxy; - - return !( - typeof potentialArrayProxy[_getUnderlyingDataset] !== "object" || - typeof potentialArrayProxy[_getUnderlyingMatch] !== "object" || - typeof potentialArrayProxy[_getNodeAtIndex] !== "function" || - typeof potentialArrayProxy[_getUnderlyingArrayTarget] !== "object" - ); + return someObject instanceof SetProxy; } diff --git a/packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts b/packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts index 5c11633..65978bc 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts @@ -1,5 +1,67 @@ +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"; + /* eslint-disable @typescript-eslint/no-explicit-any */ -export class BasicLdSet extends Set implements LdSet { +export class BasicLdSet + extends Set + implements LdSet +{ + protected context: ProxyContext; + private hashMap = new Map(); + + constructor(proxyContext: ProxyContext) { + super(); + this.context = proxyContext; + } + + private hashFn(value: T) { + return nodeToString(getNodeFromRawObject(value, this.context.contextUtil)); + } + + /** + * =========================================================================== + * Base Set Functions + * =========================================================================== + */ + + add(value: T): this { + const key = this.hashFn(value); + if (!this.hashMap.has(key)) { + this.hashMap.set(key, value); + super.add(value); + } + return this; + } + + clear(): void { + this.hashMap.clear(); + super.clear(); + } + + delete(value: T): boolean { + const key = this.hashFn(value); + if (this.hashMap.has(key)) { + this.hashMap.delete(key); + return super.delete(value); + } + return false; + } + + has(value: T): boolean { + const key = this.hashFn(value); + return this.hashMap.has(key); + } + + /** + * =========================================================================== + * Array Functions + * =========================================================================== + */ + every( predicate: (value: T, set: LdSet) => value is S, thisArg?: any, @@ -54,7 +116,7 @@ export class BasicLdSet extends Set implements LdSet { predicate: (value: T, set: LdSet) => any, thisArg?: unknown, ): LdSet { - const newSet = new BasicLdSet(); + const newSet = new BasicLdSet(this.context); for (const value of this) { if (predicate.call(thisArg, value, this)) newSet.add(value); } @@ -95,12 +157,18 @@ export class BasicLdSet extends Set implements LdSet { return accumulator; } + /** + * =========================================================================== + * Set Methods + * =========================================================================== + */ + difference(other: Set): LdSet { return this.filter((value) => !other.has(value)); } intersection(other: Set): LdSet { - const newSet = new BasicLdSet(); + const newSet = new BasicLdSet(this.context); const iteratingSet = this.size < other.size ? this : other; const comparingSet = this.size < other.size ? other : this; for (const value of iteratingSet) { @@ -137,7 +205,7 @@ export class BasicLdSet extends Set implements LdSet { } symmetricDifference(other: Set): LdSet { - const newSet = new BasicLdSet(); + const newSet = new BasicLdSet(this.context); this.forEach((value) => newSet.add(value)); other.forEach((value) => { if (newSet.has(value)) { @@ -150,7 +218,7 @@ export class BasicLdSet extends Set implements LdSet { } union(other: Set): LdSet { - const newSet = new BasicLdSet(); + const newSet = new BasicLdSet(this.context); this.forEach((value) => newSet.add(value)); other.forEach((value) => newSet.add(value)); return newSet; diff --git a/packages/jsonld-dataset-proxy/src/setProxy/ldSet/LdSet.ts b/packages/jsonld-dataset-proxy/src/setProxy/ldSet/LdSet.ts index 1c76708..8d5a7d3 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/ldSet/LdSet.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/ldSet/LdSet.ts @@ -13,7 +13,7 @@ interface LdSet extends Set { */ add(value: T): this; /** - * Clears the set of value + * Clears this set of all values, but keeps the values in the datastore */ clear(): void; /** diff --git a/packages/jsonld-dataset-proxy/src/setProxy/modifyArray.ts b/packages/jsonld-dataset-proxy/src/setProxy/modifyArray.ts index f58758d..0281afd 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/modifyArray.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/modifyArray.ts @@ -15,7 +15,7 @@ import { import { nodeToString } from "../util/NodeSet"; import type { ObjectJsonRepresentation } from "../util/nodeToJsonldRepresentation"; import type { RawObject, RawValue } from "../util/RawObject"; -import type { ArrayProxyTarget } from "./createArrayHandler"; +import type { ArrayProxyTarget } from "./createSetHandler"; export function checkArrayModification( target: ArrayProxyTarget, diff --git a/packages/jsonld-dataset-proxy/src/setProxy/setProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/setProxy.ts index c33031c..754153c 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/setProxy.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/setProxy.ts @@ -3,139 +3,181 @@ * helper methods */ import type { Dataset } from "@rdfjs/types"; -import type { ObjectNode, QuadMatch } from "@ldo/rdf-utils"; -import type { ArrayProxyTarget } from "./createArrayHandler"; import type { - _getNodeAtIndex, - _getUnderlyingArrayTarget, + ObjectNode, + PredicateNode, + QuadMatch, + SubjectNode, +} from "@ldo/rdf-utils"; +import { + _isSubjectOriented, _getUnderlyingDataset, - _getUnderlyingMatch, _proxyContext, + _isLangString, + _getUnderlyingMatch, + _getUnderlyingNode, } from "../types"; -import { _getUnderlyingNode } from "../types"; import type { ProxyContext } from "../ProxyContext"; +import { addObjectToDataset } from "../util/addObjectToDataset"; +import type { RawObject } from "../util/RawObject"; +import { nodeToJsonldRepresentation } from "../util/nodeToJsonldRepresentation"; +import { BasicLdSet } from "./ldSet/BasicLdSet"; +import { getNodeFromRawObject } from "../util/getNodeFromRaw"; -export class SetProxy implements LdSet { - context: ProxyContext; - quadMatch: QuadMatch; - isSubjectOriented?: boolean; - isLangStringSet?: boolean; +export class SetProxy extends BasicLdSet { + private quadMatch: QuadMatch; + private isLangStringSet?: boolean; constructor( context: ProxyContext, quadMatch: QuadMatch, - isSubjectOriented?: boolean, isLangStringSet?: boolean, ) { - this.context = context; + super(context); this.quadMatch = quadMatch; - this.isSubjectOriented = isSubjectOriented; 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): { + subject?: SubjectNode; + 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 { - throw new Error("Method not implemented."); + // 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); + } + return this; } clear(): void { - throw new Error("Method not implemented."); + for (const value of this) { + this.delete(value); + } } delete(value: T): boolean { - throw new Error("Method not implemented."); + const { dataset } = this.context; + const { subject, predicate, object } = this.getSPO(value); + dataset.deleteMatches(subject, predicate, object); + return true; } has(value: T): boolean { - throw new Error("Method not implemented."); + const { dataset } = this.context; + const { subject, predicate, object } = this.getSPO(value); + return dataset.match(subject, predicate, object).size > 0; } - get size(): number { - throw new Error("Method not implemented."); + get size() { + const { dataset } = this.context; + const { subject, predicate, object } = this.getSPO(); + return dataset.match(subject, predicate, object).size; } entries(): SetIterator<[T, T]> { - throw new Error("Method not implemented."); + const iteratorSet = new Set<[T, T]>(); + for (const value of this) { + iteratorSet.add([value, value]); + } + return iteratorSet[Symbol.iterator](); } keys(): SetIterator { - throw new Error("Method not implemented."); + return this.values(); } values(): SetIterator { - throw new Error("Method not implemented."); - } - - every(predicate: (value: T, set: LdSet) => value is S, thisArg?: any): this is LdSet; - - every(predicate: (value: T, set: LdSet) => unknown, thisArg?: any): boolean; - every(predicate: unknown, thisArg?: unknown): boolean { - throw new Error("Method not implemented."); - } - - some(predicate: (value: T, set: LdSet) => unknown, thisArg?: any): boolean { - throw new Error("Method not implemented."); - } - - forEach(callbackfn: (value: T, value2: T, set: LdSet) => void, thisArg?: any): void { - throw new Error("Method not implemented."); + return this[Symbol.iterator](); } - map(callbackfn: (value: T, set: LdSet) => U, thisArg?: any): LdSet { - throw new Error("Method not implemented."); - } - - filter(predicate: (value: T, set: LdSet) => value is S, thisArg?: any): LdSet; - filter(predicate: (value: T, set: LdSet) => unknown, thisArg?: any): LdSet; - filter(predicate: unknown, thisArg?: unknown): LdSet | LdSet { - throw new Error("Method not implemented."); - } - - reduce(callbackfn: (previousValue: T, currentValue: T, set: LdSet) => T): T; - reduce(callbackfn: (previousValue: T, currentValue: T, set: LdSet) => T, initialValue: T): T; - reduce(callbackfn: (previousValue: U, currentValue: T, array: LdSet) => U, initialValue: U): U; - reduce(callbackfn: unknown, initialValue?: unknown): T | U { - throw new Error("Method not implemented."); - } - - difference(other: Set): LdSet { - throw new Error("Method not implemented."); + [Symbol.iterator](): SetIterator { + const { dataset } = this.context; + 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; + return nodeToJsonldRepresentation(quadSubject, this.context) as T; + }); + return new Set(collection)[Symbol.iterator](); } - intersection(other: Set): LdSet { - throw new Error("Method not implemented."); + get [Symbol.toStringTag]() { + // TODO: Change this to be human readable. + return "LdSet"; } - isDisjointFrom(other: Set): boolean { - throw new Error("Method not implemented."); + get [_getUnderlyingDataset](): Dataset { + return this.context.dataset; } - isSubsetOf(other: Set): boolean { - throw new Error("Method not implemented."); + get [_getUnderlyingMatch](): QuadMatch { + return this.quadMatch; } - isSupersetOf(other: Set): boolean { - throw new Error("Method not implemented."); + get [_isSubjectOriented](): boolean { + return this.isSubjectOriented(); } - symmetricDifference(other: Set): LdSet { - throw new Error("Method not implemented."); + get [_isLangString](): boolean { + return !!this.isLangStringSet; } - union(other: Set): LdSet { - throw new Error("Method not implemented."); + get [_proxyContext](): ProxyContext { + return this.context; } - [Symbol.iterator](): SetIterator { - throw new Error("Method not implemented."); + set [_proxyContext](newContext: ProxyContext) { + this.context = newContext; } - - [Symbol.toStringTag]: string; } - -export type ArrayProxy = Array & { - readonly [_getUnderlyingDataset]: Dataset; - readonly [_getUnderlyingMatch]: ArrayProxyTarget[0]; - readonly [_getNodeAtIndex]: (index: number) => ObjectNode | undefined; - readonly [_getUnderlyingArrayTarget]: ArrayProxyTarget; - [_proxyContext]: ProxyContext; -}; diff --git a/packages/jsonld-dataset-proxy/src/subjectProxy/SubjectProxy.ts b/packages/jsonld-dataset-proxy/src/subjectProxy/SubjectProxy.ts index 6f44e4c..efb4e91 100644 --- a/packages/jsonld-dataset-proxy/src/subjectProxy/SubjectProxy.ts +++ b/packages/jsonld-dataset-proxy/src/subjectProxy/SubjectProxy.ts @@ -12,7 +12,7 @@ import type { export type SubjectProxy = { "@id"?: string; "@context": ContextDefinition; - readonly [key: string | number | symbol]: unknown; + [key: string | number | symbol]: unknown; readonly [_getUnderlyingDataset]: Dataset; readonly [_getUnderlyingNode]: NamedNode | BlankNode; [_proxyContext]: ProxyContext; diff --git a/packages/jsonld-dataset-proxy/src/types.ts b/packages/jsonld-dataset-proxy/src/types.ts index 4f518b9..cfee416 100644 --- a/packages/jsonld-dataset-proxy/src/types.ts +++ b/packages/jsonld-dataset-proxy/src/types.ts @@ -1,9 +1,7 @@ export const _getUnderlyingNode = Symbol("_getUnderlyingNode"); export const _getUnderlyingMatch = Symbol("_getUnderlyingMatch"); export const _isSubjectOriented = Symbol("_isSubjectOriented"); -export const _getNodeAtIndex = Symbol("_getNodeAtIndex"); export const _getUnderlyingDataset = Symbol("_getUnderlyingDataset"); -export const _getUnderlyingArrayTarget = Symbol("_getUnderlyingArrayTarget"); export const _proxyContext = Symbol("_proxyContext"); export const _writeGraphs = Symbol("_writeGraphs");