diff --git a/packages/jsonld-dataset-proxy/src/JsonldDatasetProxyBuilder.ts b/packages/jsonld-dataset-proxy/src/JsonldDatasetProxyBuilder.ts index 0bd5e5a..c799db1 100644 --- a/packages/jsonld-dataset-proxy/src/JsonldDatasetProxyBuilder.ts +++ b/packages/jsonld-dataset-proxy/src/JsonldDatasetProxyBuilder.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( - predicate?: QuadMatch[1], + predicate: QuadMatch[1], object?: QuadMatch[2], graph?: QuadMatch[3], - ): T[] { - return this.proxyContext.createArrayProxy( + ): LdSet { + return this.proxyContext.createSetProxy( [null, predicate, object, graph], true, - ) as unknown as T[]; + ) as LdSet; } /** @@ -75,10 +76,10 @@ export class JsonldDatasetProxyBuilder { */ matchObject( subject?: QuadMatch[0], - predicate?: QuadMatch[1], + predicate: QuadMatch[1], graph?: QuadMatch[3], ): T[] { - return this.proxyContext.createArrayProxy([ + return this.proxyContext.createSetProxy([ subject, predicate, null, diff --git a/packages/jsonld-dataset-proxy/src/ProxyContext.ts b/packages/jsonld-dataset-proxy/src/ProxyContext.ts index f12817e..f0d4d95 100644 --- a/packages/jsonld-dataset-proxy/src/ProxyContext.ts +++ b/packages/jsonld-dataset-proxy/src/ProxyContext.ts @@ -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; } @@ -28,7 +25,7 @@ const rdfType = namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); */ export class ProxyContext { private subjectMap: Map = new Map(); - private arrayMap: Map = new Map(); + private setMap: Map> = 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) { - 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, }; diff --git a/packages/jsonld-dataset-proxy/src/graphOf.ts b/packages/jsonld-dataset-proxy/src/graphOf.ts index db50e68..ede4e0a 100644 --- a/packages/jsonld-dataset-proxy/src/graphOf.ts +++ b/packages/jsonld-dataset-proxy/src/graphOf.ts @@ -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: Subject, predicate: Key, - object?: NonNullable extends Array - ? number | ObjectLike - : ObjectLike, + object: NonNullable extends Set + ? T + : Subject[Key] | undefined, ): GraphNode[] { const subjectProxy = getSubjectProxyFromObject(subject); const proxyContext = subjectProxy[_proxyContext]; @@ -39,20 +36,6 @@ export function graphOf( 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]; diff --git a/packages/jsonld-dataset-proxy/src/index.ts b/packages/jsonld-dataset-proxy/src/index.ts index 1eedb26..9a43d16 100644 --- a/packages/jsonld-dataset-proxy/src/index.ts +++ b/packages/jsonld-dataset-proxy/src/index.ts @@ -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"; diff --git a/packages/jsonld-dataset-proxy/src/setProxy/ObjectSetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/ObjectSetProxy.ts new file mode 100644 index 0000000..456fd3f --- /dev/null +++ b/packages/jsonld-dataset-proxy/src/setProxy/ObjectSetProxy.ts @@ -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, +> extends WildcardObjectSetProxy { + 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; + } +} diff --git a/packages/jsonld-dataset-proxy/src/setProxy/setProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/SetProxy.ts similarity index 50% rename from packages/jsonld-dataset-proxy/src/setProxy/setProxy.ts rename to packages/jsonld-dataset-proxy/src/setProxy/SetProxy.ts index 754153c..787b7c1 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/setProxy.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/SetProxy.ts @@ -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 extends BasicLdSet { - 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 = NonNullable, +> extends BasicLdSet { + 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 extends BasicLdSet { 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 extends BasicLdSet { 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 extends BasicLdSet { set [_proxyContext](newContext: ProxyContext) { this.context = newContext; } + + get [_writeGraphs](): GraphNode[] { + return this.context.writeGraphs; + } } diff --git a/packages/jsonld-dataset-proxy/src/setProxy/SubjectSetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/SubjectSetProxy.ts new file mode 100644 index 0000000..f867c5f --- /dev/null +++ b/packages/jsonld-dataset-proxy/src/setProxy/SubjectSetProxy.ts @@ -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 { + 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; + } +} diff --git a/packages/jsonld-dataset-proxy/src/setProxy/WildcardObjectSetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/WildcardObjectSetProxy.ts new file mode 100644 index 0000000..f4913e1 --- /dev/null +++ b/packages/jsonld-dataset-proxy/src/setProxy/WildcardObjectSetProxy.ts @@ -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, +> extends SetProxy { + 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 { + // 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; + } + } +} diff --git a/packages/jsonld-dataset-proxy/src/setProxy/WildcardSubjectSetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/WildcardSubjectSetProxy.ts new file mode 100644 index 0000000..9486aaa --- /dev/null +++ b/packages/jsonld-dataset-proxy/src/setProxy/WildcardSubjectSetProxy.ts @@ -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 extends SetProxy { + 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; + } +} diff --git a/packages/jsonld-dataset-proxy/src/setProxy/createSetHandler.ts b/packages/jsonld-dataset-proxy/src/setProxy/createSetHandler.ts deleted file mode 100644 index 07e3afc..0000000 --- a/packages/jsonld-dataset-proxy/src/setProxy/createSetHandler.ts +++ /dev/null @@ -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 { - 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; - }, - }; -} diff --git a/packages/jsonld-dataset-proxy/src/setProxy/isSetProxy.ts b/packages/jsonld-dataset-proxy/src/setProxy/isSetProxy.ts index 60299c3..9ec6383 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/isSetProxy.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/isSetProxy.ts @@ -1,9 +1,6 @@ -import type { RawObject } from "../util/RawObject"; import { SetProxy } from "./setProxy"; -export function isSetProxy( - someObject?: unknown, -): someObject is SetProxy { +export function isSetProxy(someObject?: unknown): someObject is SetProxy { if (!someObject) return false; if (typeof someObject !== "object") return false; 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 65978bc..b6015b4 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/ldSet/BasicLdSet.ts @@ -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 +export class BasicLdSet = NonNullable> extends Set implements LdSet { @@ -19,6 +19,7 @@ export class BasicLdSet } private hashFn(value: T) { + if (typeof value !== "object") return value.toString(); return nodeToString(getNodeFromRawObject(value, this.context.contextUtil)); } diff --git a/packages/jsonld-dataset-proxy/src/setProxy/ldSet/LdSet.ts b/packages/jsonld-dataset-proxy/src/setProxy/ldSet/LdSet.ts index 8d5a7d3..2c85d04 100644 --- a/packages/jsonld-dataset-proxy/src/setProxy/ldSet/LdSet.ts +++ b/packages/jsonld-dataset-proxy/src/setProxy/ldSet/LdSet.ts @@ -2,7 +2,7 @@ /** * An abract representation for a set of Linked Data Objects */ -interface LdSet extends Set { +export interface LdSet extends Set { /** * =========================================================================== * BASE METHODS diff --git a/packages/jsonld-dataset-proxy/src/setProxy/modifyArray.ts b/packages/jsonld-dataset-proxy/src/setProxy/modifyArray.ts deleted file mode 100644 index 0281afd..0000000 --- a/packages/jsonld-dataset-proxy/src/setProxy/modifyArray.ts +++ /dev/null @@ -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( - 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[]; - 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); -} diff --git a/packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts b/packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts index 06a6ab4..0e2bb93 100644 --- a/packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts +++ b/packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts @@ -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") { diff --git a/packages/jsonld-dataset-proxy/src/types.ts b/packages/jsonld-dataset-proxy/src/types.ts index cfee416..a61d739 100644 --- a/packages/jsonld-dataset-proxy/src/types.ts +++ b/packages/jsonld-dataset-proxy/src/types.ts @@ -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"); diff --git a/packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts b/packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts index f66cd90..f24942f 100644 --- a/packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts +++ b/packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts @@ -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);