parent
227c94b94f
commit
f799b155e2
@ -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; |
||||
} |
||||
} |
@ -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,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); |
||||
} |
Loading…
Reference in new issue