parent
02de7a1757
commit
bfce541e8f
@ -0,0 +1,3 @@ |
|||||||
|
{ |
||||||
|
"extends": ["../../.eslintrc"] |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const sharedConfig = require("../../jest.config.js"); |
||||||
|
module.exports = { |
||||||
|
...sharedConfig, |
||||||
|
rootDir: "./", |
||||||
|
}; |
@ -0,0 +1,46 @@ |
|||||||
|
{ |
||||||
|
"name": "@ldo/jsonld-dataset-proxy", |
||||||
|
"version": "0.0.0", |
||||||
|
"description": "", |
||||||
|
"main": "dist/index.js", |
||||||
|
"scripts": { |
||||||
|
"build": "tsc --project tsconfig.build.json", |
||||||
|
"build:watch": "tsc-watch", |
||||||
|
"test": "jest --coverage", |
||||||
|
"prepublishOnly": "npm run test && npm run build", |
||||||
|
"start": "ts-node ./example/example.ts", |
||||||
|
"start:lang": "ts-node ./example/languageExample.ts", |
||||||
|
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern" |
||||||
|
}, |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "git+https://github.com/o-development/jsonld-dataset-proxy.git" |
||||||
|
}, |
||||||
|
"author": "Jackson Morgan", |
||||||
|
"license": "MIT", |
||||||
|
"bugs": { |
||||||
|
"url": "https://github.com/o-development/jsonld-dataset-proxy/issues" |
||||||
|
}, |
||||||
|
"homepage": "https://github.com/o-development/jsonld-dataset-proxy#readme", |
||||||
|
"devDependencies": { |
||||||
|
"@rdfjs/types": "^1.0.1", |
||||||
|
"@types/jest": "^27.0.3", |
||||||
|
"@types/jsonld": "^1.5.6", |
||||||
|
"@types/n3": "^1.10.4", |
||||||
|
"@types/rdfjs__dataset": "^1.0.5", |
||||||
|
"@types/shexj": "2.1.4", |
||||||
|
"jest": "^27.4.5", |
||||||
|
"shex-test": "^0.5.5", |
||||||
|
"ts-jest": "^27.1.2", |
||||||
|
"ts-node": "^10.4.0", |
||||||
|
"tsc-watch": "^6.0.0" |
||||||
|
}, |
||||||
|
"files": [ |
||||||
|
"dist" |
||||||
|
], |
||||||
|
"dependencies": { |
||||||
|
"@rdfjs/data-model": "^1.2.0", |
||||||
|
"jsonld2graphobject": "^0.0.4", |
||||||
|
"o-dataset-pack": "^0.2.14" |
||||||
|
} |
||||||
|
} |
After Width: | Height: | Size: 269 KiB |
@ -0,0 +1,90 @@ |
|||||||
|
import type { ContextDefinition, ExpandedTermDefinition } from "jsonld"; |
||||||
|
|
||||||
|
// Create JSONLD Shorthands
|
||||||
|
const shorthandToIriMap: Record<string, string> = { |
||||||
|
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* Context Util |
||||||
|
* Handles the JSON-LD context and allows conversion between IRIs and terms |
||||||
|
*/ |
||||||
|
export class ContextUtil { |
||||||
|
public readonly context: ContextDefinition; |
||||||
|
private iriToKeyMap: Record<string, string>; |
||||||
|
|
||||||
|
constructor(context: ContextDefinition) { |
||||||
|
this.context = context; |
||||||
|
this.iriToKeyMap = {}; |
||||||
|
Object.entries(context).forEach(([contextKey, contextValue]) => { |
||||||
|
if (typeof contextValue === "string") { |
||||||
|
this.iriToKeyMap[this.keyIdToIri(contextValue)] = contextKey; |
||||||
|
} else if ( |
||||||
|
typeof contextValue === "object" && |
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(contextValue as any)["@id"] |
||||||
|
) { |
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
this.iriToKeyMap[this.keyIdToIri((contextValue as any)["@id"])] = |
||||||
|
contextKey; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public keyToIri(key: string): string { |
||||||
|
if (!this.context[key]) { |
||||||
|
return key; |
||||||
|
} else if (typeof this.context[key] === "string") { |
||||||
|
return this.keyIdToIri(this.context[key] as string); |
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} else if (this.context[key] && (this.context[key] as any)["@id"]) { |
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return this.keyIdToIri((this.context[key] as any)["@id"]); |
||||||
|
} |
||||||
|
return key; |
||||||
|
} |
||||||
|
|
||||||
|
private keyIdToIri(keyId: string) { |
||||||
|
if (shorthandToIriMap[keyId]) { |
||||||
|
return shorthandToIriMap[keyId]; |
||||||
|
} else { |
||||||
|
return keyId; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public iriToKey(iri: string): string { |
||||||
|
if (this.iriToKeyMap[iri]) { |
||||||
|
return this.iriToKeyMap[iri]; |
||||||
|
} |
||||||
|
return iri; |
||||||
|
} |
||||||
|
|
||||||
|
public getType(key: string): string { |
||||||
|
if ( |
||||||
|
typeof this.context[key] === "object" && |
||||||
|
(this.context[key] as ExpandedTermDefinition)["@type"] |
||||||
|
) { |
||||||
|
return (this.context[key] as ExpandedTermDefinition)["@type"] as string; |
||||||
|
} |
||||||
|
return "http://www.w3.org/2001/XMLSchema#string"; |
||||||
|
} |
||||||
|
|
||||||
|
public isArray(key: string): boolean { |
||||||
|
return !!( |
||||||
|
this.context[key] && |
||||||
|
typeof this.context[key] === "object" && |
||||||
|
(this.context[key] as ExpandedTermDefinition)["@container"] && |
||||||
|
(this.context[key] as ExpandedTermDefinition)["@container"] === "@set" |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
public isLangString(key: string): boolean { |
||||||
|
return !!( |
||||||
|
this.context[key] && |
||||||
|
typeof this.context[key] === "object" && |
||||||
|
(this.context[key] as ExpandedTermDefinition)["@type"] && |
||||||
|
(this.context[key] as ExpandedTermDefinition)["@type"] === |
||||||
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString" |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
import { blankNode, namedNode } from "@rdfjs/data-model"; |
||||||
|
import type { BlankNode, NamedNode } from "@rdfjs/types"; |
||||||
|
import type { LanguageOrdering } from "./language/languageTypes"; |
||||||
|
import type { ProxyContext } from "./ProxyContext"; |
||||||
|
import type { GraphType, ObjectLike, QuadMatch } from "./types"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Helps build JSON LD Dataset Proxies for a specific dataset and context |
||||||
|
*/ |
||||||
|
export class JsonldDatasetProxyBuilder { |
||||||
|
private proxyContext: ProxyContext; |
||||||
|
|
||||||
|
constructor(proxyContext: ProxyContext) { |
||||||
|
this.proxyContext = proxyContext; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Designates that all Jsonld Dataset Proxies created should write to the |
||||||
|
* specified graphs |
||||||
|
*/ |
||||||
|
write(...graphs: GraphType[]): JsonldDatasetProxyBuilder { |
||||||
|
return new JsonldDatasetProxyBuilder( |
||||||
|
this.proxyContext.duplicate({ writeGraphs: graphs }), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* List the language tags in the order they should be used. When a langString |
||||||
|
* is accessed, LDO will search for values in the order of language given. |
||||||
|
* @param languageOrdering The ordering of languages. For example |
||||||
|
* ("en", "fr", "none", "other"). Defaults to |
||||||
|
* ("none", "en", "other") |
||||||
|
*/ |
||||||
|
setLanguagePreferences( |
||||||
|
...languageOrdering: LanguageOrdering |
||||||
|
): JsonldDatasetProxyBuilder { |
||||||
|
return new JsonldDatasetProxyBuilder( |
||||||
|
this.proxyContext.duplicate({ languageOrdering }), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a JSON LD Dataset Proxy that matches the given subject |
||||||
|
* @param subject The node to match |
||||||
|
*/ |
||||||
|
fromSubject<T extends ObjectLike>(subject: NamedNode | BlankNode): T { |
||||||
|
return this.proxyContext.createSubjectProxy(subject) as unknown as T; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Matches Subjects to provided predicates, objects, and graphs. Returns a |
||||||
|
* JSON LD Dataset that can be read an modified. |
||||||
|
* @param predicate The predicate to match |
||||||
|
* @param object The object to match |
||||||
|
* @param graph The graph to match |
||||||
|
*/ |
||||||
|
matchSubject<T extends ObjectLike>( |
||||||
|
predicate?: QuadMatch[1], |
||||||
|
object?: QuadMatch[2], |
||||||
|
graph?: QuadMatch[3], |
||||||
|
): T[] { |
||||||
|
return this.proxyContext.createArrayProxy( |
||||||
|
[null, predicate, object, graph], |
||||||
|
true, |
||||||
|
) as unknown as T[]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Matches Objects to provided subjects, predicates, and graphs. Returns a |
||||||
|
* JSON LD Dataset that can be read an modified. |
||||||
|
* @param subject The subject to match |
||||||
|
* @param predicate The predicate to match |
||||||
|
* @param graph The graph to match |
||||||
|
*/ |
||||||
|
matchObject<T extends ObjectLike>( |
||||||
|
subject?: QuadMatch[0], |
||||||
|
predicate?: QuadMatch[1], |
||||||
|
graph?: QuadMatch[3], |
||||||
|
): T[] { |
||||||
|
return this.proxyContext.createArrayProxy([ |
||||||
|
subject, |
||||||
|
predicate, |
||||||
|
null, |
||||||
|
graph, |
||||||
|
]) as unknown as T[]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Takes a given object and places it in the dataset while returning a JSON LD |
||||||
|
* Dataset Proxy representing the object. |
||||||
|
* |
||||||
|
* @param inputData Initial Data |
||||||
|
* @param graph Optional graph to save this data to |
||||||
|
*/ |
||||||
|
fromJson<T extends ObjectLike>(inputData: T): T { |
||||||
|
const entryNode = inputData["@id"] |
||||||
|
? namedNode(inputData["@id"]) |
||||||
|
: blankNode(); |
||||||
|
const proxy = this.fromSubject<T>(entryNode); |
||||||
|
Object.entries(inputData).forEach(([key, value]) => { |
||||||
|
proxy[<keyof T>key] = value; |
||||||
|
}); |
||||||
|
return proxy; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,110 @@ |
|||||||
|
import type { BlankNode, Dataset, NamedNode } from "@rdfjs/types"; |
||||||
|
import type { ArrayProxyTarget } from "./arrayProxy/createArrayHandler"; |
||||||
|
import { createArrayHandler } from "./arrayProxy/createArrayHandler"; |
||||||
|
import { createSubjectHandler } from "./subjectProxy/createSubjectHandler"; |
||||||
|
import type { SubjectProxy } from "./subjectProxy/SubjectProxy"; |
||||||
|
import type { ArrayProxy } from "./arrayProxy/ArrayProxy"; |
||||||
|
import type { GraphType, QuadMatch } from "./types"; |
||||||
|
import { _getUnderlyingArrayTarget } from "./types"; |
||||||
|
import type { ContextUtil } from "./ContextUtil"; |
||||||
|
import type { LanguageOrdering } from "./language/languageTypes"; |
||||||
|
|
||||||
|
export interface ProxyContextOptions { |
||||||
|
dataset: Dataset; |
||||||
|
contextUtil: ContextUtil; |
||||||
|
writeGraphs: GraphType[]; |
||||||
|
languageOrdering: LanguageOrdering; |
||||||
|
prefilledArrayTargets?: ArrayProxyTarget[]; |
||||||
|
state?: Record<string, unknown>; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This file keeps track of the target objects used in the proxies. |
||||||
|
* The reason is so that JSON.stringify does not recurse inifinitely |
||||||
|
* when it encounters a circular object. |
||||||
|
*/ |
||||||
|
export class ProxyContext { |
||||||
|
private subjectMap: Map<string, SubjectProxy> = new Map(); |
||||||
|
private arrayMap: Map<string, ArrayProxy> = new Map(); |
||||||
|
|
||||||
|
readonly dataset: Dataset; |
||||||
|
readonly contextUtil: ContextUtil; |
||||||
|
readonly writeGraphs: GraphType[]; |
||||||
|
readonly languageOrdering: LanguageOrdering; |
||||||
|
public state: Record<string, unknown>; |
||||||
|
|
||||||
|
constructor(options: ProxyContextOptions) { |
||||||
|
this.dataset = options.dataset; |
||||||
|
this.contextUtil = options.contextUtil; |
||||||
|
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 { |
||||||
|
if (!this.subjectMap.has(node.value)) { |
||||||
|
const proxy = new Proxy( |
||||||
|
{ "@id": node }, |
||||||
|
this.createSubjectHandler(), |
||||||
|
) as unknown as SubjectProxy; |
||||||
|
this.subjectMap.set(node.value, proxy); |
||||||
|
} |
||||||
|
return this.subjectMap.get(node.value) as SubjectProxy; |
||||||
|
} |
||||||
|
|
||||||
|
protected createSubjectHandler() { |
||||||
|
return createSubjectHandler(this); |
||||||
|
} |
||||||
|
|
||||||
|
private getArrayKey(...quadMatch: QuadMatch) { |
||||||
|
return `${quadMatch[0]?.value || "undefined"}|${ |
||||||
|
quadMatch[1]?.value || "undefined" |
||||||
|
}|${quadMatch[2]?.value || "undefined"}|${ |
||||||
|
quadMatch[3]?.value || "undefined" |
||||||
|
}`;
|
||||||
|
} |
||||||
|
|
||||||
|
public createArrayProxy( |
||||||
|
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); |
||||||
|
} |
||||||
|
return this.arrayMap.get(key) as ArrayProxy; |
||||||
|
} |
||||||
|
|
||||||
|
protected createArrayHandler() { |
||||||
|
return createArrayHandler(this); |
||||||
|
} |
||||||
|
|
||||||
|
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, |
||||||
|
}; |
||||||
|
return new ProxyContext(fullOptions); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
import type { Dataset } from "@rdfjs/types"; |
||||||
|
import type { ArrayProxyTarget } from "./createArrayHandler"; |
||||||
|
import type { |
||||||
|
ObjectType, |
||||||
|
_getNodeAtIndex, |
||||||
|
_getUnderlyingArrayTarget, |
||||||
|
_getUnderlyingDataset, |
||||||
|
_getUnderlyingMatch, |
||||||
|
_proxyContext, |
||||||
|
} from "../types"; |
||||||
|
import { _getUnderlyingNode } from "../types"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
|
||||||
|
export type ArrayProxy = Array<unknown> & { |
||||||
|
readonly [_getUnderlyingDataset]: Dataset; |
||||||
|
readonly [_getUnderlyingMatch]: ArrayProxyTarget[0]; |
||||||
|
readonly [_getNodeAtIndex]: (index: number) => ObjectType | undefined; |
||||||
|
readonly [_getUnderlyingArrayTarget]: ArrayProxyTarget; |
||||||
|
[_proxyContext]: ProxyContext; |
||||||
|
}; |
@ -0,0 +1,216 @@ |
|||||||
|
import type { ArrayProxyTarget } from "./createArrayHandler"; |
||||||
|
import type { ObjectJsonRepresentation } from "../util/nodeToJsonldRepresentation"; |
||||||
|
import { nodeToJsonldRepresentation } from "../util/nodeToJsonldRepresentation"; |
||||||
|
import { modifyArray } from "./modifyArray"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
|
||||||
|
export type methodBuilder<Return> = ( |
||||||
|
target: ArrayProxyTarget, |
||||||
|
key: string, |
||||||
|
proxyContext: ProxyContext, |
||||||
|
) => Return; |
||||||
|
|
||||||
|
export interface ArrayMethodBuildersType { |
||||||
|
copyWithin: methodBuilder<Array<ObjectJsonRepresentation>["copyWithin"]>; |
||||||
|
fill: methodBuilder<Array<ObjectJsonRepresentation>["fill"]>; |
||||||
|
pop: methodBuilder<Array<ObjectJsonRepresentation>["pop"]>; |
||||||
|
push: methodBuilder<Array<ObjectJsonRepresentation>["push"]>; |
||||||
|
reverse: methodBuilder<Array<ObjectJsonRepresentation>["reverse"]>; |
||||||
|
shift: methodBuilder<Array<ObjectJsonRepresentation>["shift"]>; |
||||||
|
sort: methodBuilder<Array<ObjectJsonRepresentation>["sort"]>; |
||||||
|
splice: methodBuilder<Array<ObjectJsonRepresentation>["splice"]>; |
||||||
|
unshift: methodBuilder<Array<ObjectJsonRepresentation>["unshift"]>; |
||||||
|
} |
||||||
|
|
||||||
|
export const methodNames: Set<keyof ArrayMethodBuildersType> = new Set([ |
||||||
|
"copyWithin", |
||||||
|
"fill", |
||||||
|
"pop", |
||||||
|
"push", |
||||||
|
"reverse", |
||||||
|
"shift", |
||||||
|
"sort", |
||||||
|
"splice", |
||||||
|
"unshift", |
||||||
|
]); |
||||||
|
|
||||||
|
export const arrayMethodsBuilders: ArrayMethodBuildersType = { |
||||||
|
copyWithin: (target, key, proxyContext) => { |
||||||
|
return (targetIndex, start, end) => { |
||||||
|
return modifyArray( |
||||||
|
{ |
||||||
|
target, |
||||||
|
key, |
||||||
|
quadsToDelete: (quads) => { |
||||||
|
const realEnd = end || quads.length; |
||||||
|
return quads.slice(targetIndex, targetIndex + (realEnd - start)); |
||||||
|
}, |
||||||
|
modifyCoreArray: (coreArray) => { |
||||||
|
coreArray.copyWithin(targetIndex, start, end); |
||||||
|
return proxyContext.createArrayProxy( |
||||||
|
target[0], |
||||||
|
target[2], |
||||||
|
) as ObjectJsonRepresentation[]; |
||||||
|
}, |
||||||
|
}, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
}; |
||||||
|
}, |
||||||
|
fill: (target, key, proxyContext) => { |
||||||
|
return (value, start, end) => { |
||||||
|
return modifyArray( |
||||||
|
{ |
||||||
|
target, |
||||||
|
key, |
||||||
|
toAdd: [value], |
||||||
|
quadsToDelete: (quads) => { |
||||||
|
return quads.slice(start, end); |
||||||
|
}, |
||||||
|
modifyCoreArray: (coreArray, addedValues) => { |
||||||
|
coreArray.fill(addedValues[0], start, end); |
||||||
|
return proxyContext.createArrayProxy( |
||||||
|
target[0], |
||||||
|
target[2], |
||||||
|
) as ObjectJsonRepresentation[]; |
||||||
|
}, |
||||||
|
}, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
}; |
||||||
|
}, |
||||||
|
pop: (target, key, proxyContext) => { |
||||||
|
return () => { |
||||||
|
return modifyArray( |
||||||
|
{ |
||||||
|
target, |
||||||
|
key, |
||||||
|
quadsToDelete: (quads) => { |
||||||
|
return quads[quads.length - 1] ? [quads[quads.length - 1]] : []; |
||||||
|
}, |
||||||
|
modifyCoreArray: (coreArray) => { |
||||||
|
const popped = coreArray.pop(); |
||||||
|
return popped |
||||||
|
? nodeToJsonldRepresentation(popped, proxyContext) |
||||||
|
: undefined; |
||||||
|
}, |
||||||
|
}, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
}; |
||||||
|
}, |
||||||
|
push: (target, key, proxyContext) => { |
||||||
|
return (...args) => { |
||||||
|
return modifyArray( |
||||||
|
{ |
||||||
|
target, |
||||||
|
key, |
||||||
|
toAdd: args, |
||||||
|
modifyCoreArray: (coreArray, addedValues) => { |
||||||
|
coreArray.push(...addedValues); |
||||||
|
return proxyContext.createArrayProxy(target[0], target[2]).length; |
||||||
|
}, |
||||||
|
}, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
}; |
||||||
|
}, |
||||||
|
reverse: (target, _key, proxyContext) => { |
||||||
|
return () => { |
||||||
|
target[1].reverse(); |
||||||
|
return proxyContext.createArrayProxy( |
||||||
|
target[0], |
||||||
|
target[2], |
||||||
|
) as ObjectJsonRepresentation[]; |
||||||
|
}; |
||||||
|
}, |
||||||
|
shift: (target, key, proxyContext) => { |
||||||
|
return () => { |
||||||
|
return modifyArray( |
||||||
|
{ |
||||||
|
target, |
||||||
|
key, |
||||||
|
quadsToDelete: (quads) => { |
||||||
|
return quads[0] ? [quads[0]] : []; |
||||||
|
}, |
||||||
|
modifyCoreArray: (coreArray) => { |
||||||
|
const shifted = coreArray.shift(); |
||||||
|
return shifted |
||||||
|
? nodeToJsonldRepresentation(shifted, proxyContext) |
||||||
|
: undefined; |
||||||
|
}, |
||||||
|
}, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
}; |
||||||
|
}, |
||||||
|
sort: (target, _key, proxyContext) => { |
||||||
|
return (compareFunction) => { |
||||||
|
if (compareFunction) { |
||||||
|
target[1].sort((a, b) => { |
||||||
|
return compareFunction( |
||||||
|
nodeToJsonldRepresentation(a, proxyContext), |
||||||
|
nodeToJsonldRepresentation(b, proxyContext), |
||||||
|
); |
||||||
|
}); |
||||||
|
} else if (target) { |
||||||
|
target[1].sort((a, b) => { |
||||||
|
const aReal = nodeToJsonldRepresentation(a, proxyContext); |
||||||
|
const bReal = nodeToJsonldRepresentation(b, proxyContext); |
||||||
|
if (aReal > bReal) { |
||||||
|
return 1; |
||||||
|
} else if (bReal > aReal) { |
||||||
|
return -1; |
||||||
|
} else { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
return proxyContext.createArrayProxy( |
||||||
|
target[0], |
||||||
|
target[2], |
||||||
|
) as ObjectJsonRepresentation[]; |
||||||
|
}; |
||||||
|
}, |
||||||
|
splice: (target, key, proxyContext) => { |
||||||
|
return (start, deleteCount, ...items: ObjectJsonRepresentation[]) => { |
||||||
|
return modifyArray( |
||||||
|
{ |
||||||
|
target, |
||||||
|
key, |
||||||
|
toAdd: items, |
||||||
|
quadsToDelete: (quads) => { |
||||||
|
return quads.splice(start, deleteCount); |
||||||
|
}, |
||||||
|
modifyCoreArray: (coreArray, addedValues) => { |
||||||
|
const spliced = coreArray.splice( |
||||||
|
start, |
||||||
|
deleteCount || 0, |
||||||
|
...addedValues, |
||||||
|
); |
||||||
|
return spliced.map((node) => { |
||||||
|
return nodeToJsonldRepresentation(node, proxyContext); |
||||||
|
}); |
||||||
|
}, |
||||||
|
}, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
}; |
||||||
|
}, |
||||||
|
unshift: (target, key, proxyContext) => { |
||||||
|
return (...args) => { |
||||||
|
return modifyArray( |
||||||
|
{ |
||||||
|
target, |
||||||
|
key, |
||||||
|
toAdd: args, |
||||||
|
modifyCoreArray: (coreArray, addedValues) => { |
||||||
|
coreArray.unshift(...addedValues); |
||||||
|
return proxyContext.createArrayProxy(target[0], target[2]).length; |
||||||
|
}, |
||||||
|
}, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,177 @@ |
|||||||
|
import type { NamedNode } from "@rdfjs/types"; |
||||||
|
import type { ObjectJsonRepresentation } from "../util/nodeToJsonldRepresentation"; |
||||||
|
import { nodeToJsonldRepresentation } from "../util/nodeToJsonldRepresentation"; |
||||||
|
import { quad } from "@rdfjs/data-model"; |
||||||
|
import type { ArrayMethodBuildersType } from "./arrayMethods"; |
||||||
|
import { arrayMethodsBuilders, methodNames } from "./arrayMethods"; |
||||||
|
import type { ObjectType, QuadMatch, SubjectType } from "../types"; |
||||||
|
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: ObjectType[], |
||||||
|
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 SubjectType); |
||||||
|
} else { |
||||||
|
datasetObjects.add(quad.object as ObjectType); |
||||||
|
} |
||||||
|
}); |
||||||
|
const processedObjects: ObjectType[] = []; |
||||||
|
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): ObjectType | 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; |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
import { |
||||||
|
_getNodeAtIndex, |
||||||
|
_getUnderlyingArrayTarget, |
||||||
|
_getUnderlyingDataset, |
||||||
|
_getUnderlyingMatch, |
||||||
|
_getUnderlyingNode, |
||||||
|
_proxyContext, |
||||||
|
_writeGraphs, |
||||||
|
} from "../types"; |
||||||
|
import type { ArrayProxy } from "./ArrayProxy"; |
||||||
|
|
||||||
|
export function isArrayProxy(someObject?: unknown): someObject is ArrayProxy { |
||||||
|
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" |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,129 @@ |
|||||||
|
import { defaultGraph } from "@rdfjs/data-model"; |
||||||
|
import type { Quad } from "@rdfjs/types"; |
||||||
|
import { ProxyTransactionalDataset } from "o-dataset-pack"; |
||||||
|
import { createExtendedDatasetFactory } from "o-dataset-pack/dist/createExtendedDataset"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
import type { ObjectType } from "../types"; |
||||||
|
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 "./createArrayHandler"; |
||||||
|
|
||||||
|
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 ProxyTransactionalDataset( |
||||||
|
proxyContext.dataset, |
||||||
|
createExtendedDatasetFactory(), |
||||||
|
); |
||||||
|
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)]: added, |
||||||
|
} as RawObject, |
||||||
|
false, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
} |
||||||
|
const addedNodes = added |
||||||
|
? (added |
||||||
|
.map((addedValue) => { |
||||||
|
return getNodeFromRawValue(key, addedValue, proxyContext); |
||||||
|
}) |
||||||
|
.filter((val) => val != undefined) as ObjectType[]) |
||||||
|
: []; |
||||||
|
|
||||||
|
// Allow the base array to be modified
|
||||||
|
return modifyCoreArray(target[1], addedNodes); |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
import { namedNode } from "@rdfjs/data-model"; |
||||||
|
import { |
||||||
|
getSubjectProxyFromObject, |
||||||
|
isSubjectProxy, |
||||||
|
} from "./subjectProxy/isSubjectProxy"; |
||||||
|
import type { GraphType, ObjectLike, ObjectType } from "./types"; |
||||||
|
import { |
||||||
|
_getNodeAtIndex, |
||||||
|
_getUnderlyingDataset, |
||||||
|
_getUnderlyingMatch, |
||||||
|
_getUnderlyingNode, |
||||||
|
_proxyContext, |
||||||
|
} from "./types"; |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 |
||||||
|
* @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, |
||||||
|
): GraphType[] { |
||||||
|
const subjectProxy = getSubjectProxyFromObject(subject); |
||||||
|
const proxyContext = subjectProxy[_proxyContext]; |
||||||
|
const subjectNode = subjectProxy[_getUnderlyingNode]; |
||||||
|
const predicateNode = namedNode( |
||||||
|
proxyContext.contextUtil.keyToIri(predicate as string), |
||||||
|
); |
||||||
|
let objectNode: ObjectType | 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]; |
||||||
|
} |
||||||
|
const quads = subjectProxy[_getUnderlyingDataset].match( |
||||||
|
subjectNode, |
||||||
|
predicateNode, |
||||||
|
objectNode, |
||||||
|
); |
||||||
|
return quads.toArray().map((quad): GraphType => quad.graph as GraphType); |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
import { jsonldDatasetProxy } from "./jsonldDatasetProxy"; |
||||||
|
|
||||||
|
export default jsonldDatasetProxy; |
||||||
|
export * from "./types"; |
||||||
|
export * from "./ContextUtil"; |
||||||
|
export * from "./ProxyContext"; |
||||||
|
export * from "./JsonldDatasetProxyBuilder"; |
||||||
|
export * from "./jsonldDatasetProxy"; |
||||||
|
export * from "./write"; |
||||||
|
export * from "./graphOf"; |
||||||
|
export * from "./setLanguagePreferences"; |
||||||
|
|
||||||
|
export * from "./language/languagesOf"; |
||||||
|
export * from "./language/languageMapProxy"; |
||||||
|
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 "./subjectProxy/createSubjectHandler"; |
||||||
|
export * from "./subjectProxy/SubjectProxy"; |
||||||
|
export * from "./subjectProxy/getValueForKey"; |
||||||
|
export * from "./subjectProxy/deleteFromDataset"; |
||||||
|
export * from "./subjectProxy/isSubjectProxy"; |
||||||
|
|
||||||
|
export * from "./util/addObjectToDataset"; |
||||||
|
export * from "./util/nodeToJsonldRepresentation"; |
||||||
|
export * from "./util/RawObject"; |
||||||
|
export * from "./util/getNodeFromRaw"; |
||||||
|
export * from "./util/NodeSet"; |
||||||
|
export * from "./util/isProxy"; |
||||||
|
export * from "./util/createInteractOptions"; |
@ -0,0 +1,27 @@ |
|||||||
|
import { defaultGraph } from "@rdfjs/data-model"; |
||||||
|
import type { Dataset } from "@rdfjs/types"; |
||||||
|
import type { ContextDefinition } from "jsonld"; |
||||||
|
import { ContextUtil } from "./ContextUtil"; |
||||||
|
import { JsonldDatasetProxyBuilder } from "./JsonldDatasetProxyBuilder"; |
||||||
|
import { ProxyContext } from "./ProxyContext"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a JSON-LD Dataset Proxy |
||||||
|
* |
||||||
|
* @param inputDataset the source dataset |
||||||
|
* @param context JSON-LD Context |
||||||
|
* @returns a JSON-LD Dataset proxy |
||||||
|
*/ |
||||||
|
export function jsonldDatasetProxy( |
||||||
|
inputDataset: Dataset, |
||||||
|
context: ContextDefinition, |
||||||
|
): JsonldDatasetProxyBuilder { |
||||||
|
const contextUtil = new ContextUtil(context); |
||||||
|
const proxyContext = new ProxyContext({ |
||||||
|
dataset: inputDataset, |
||||||
|
contextUtil, |
||||||
|
writeGraphs: [defaultGraph()], |
||||||
|
languageOrdering: ["none", "en", "other"], |
||||||
|
}); |
||||||
|
return new JsonldDatasetProxyBuilder(proxyContext); |
||||||
|
} |
@ -0,0 +1,75 @@ |
|||||||
|
import { literal, quad } from "@rdfjs/data-model"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
import type { PredicateType, SubjectType } from "../types"; |
||||||
|
import { |
||||||
|
languageKeyToLiteralLanguage, |
||||||
|
quadsToLanguageQuadMap, |
||||||
|
languageDeleteMatch, |
||||||
|
} from "./languageUtils"; |
||||||
|
import type { LanguageMap, LanguageSetMap } from "./languagesOf"; |
||||||
|
import LanguageSet from "./languageSet"; |
||||||
|
|
||||||
|
export function createLanguageMapProxy< |
||||||
|
Target extends LanguageMap | LanguageSetMap, |
||||||
|
>( |
||||||
|
subject: SubjectType, |
||||||
|
predicate: PredicateType, |
||||||
|
proxyContext: ProxyContext, |
||||||
|
isArray: boolean, |
||||||
|
): Target { |
||||||
|
const target: Target = {} as Target; |
||||||
|
// Function to call to update the target to represent what's in the dataset
|
||||||
|
const targetSetter = (target: Target) => { |
||||||
|
// Clear the target
|
||||||
|
Object.keys(target).forEach((key) => delete target[key]); |
||||||
|
// Add current language map to target
|
||||||
|
const allQuads = proxyContext.dataset.match(subject, predicate); |
||||||
|
const languageQuadMap = quadsToLanguageQuadMap(allQuads); |
||||||
|
Object.entries(languageQuadMap).forEach(([language, quads]) => { |
||||||
|
const stringArray = quads.toArray().map((quad) => quad.object.value); |
||||||
|
if (isArray) { |
||||||
|
target[language] = new Set(stringArray); |
||||||
|
} else { |
||||||
|
target[language] = stringArray[0]; |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
targetSetter(target); |
||||||
|
|
||||||
|
return new Proxy<Target>(target, { |
||||||
|
get: (target, key) => { |
||||||
|
targetSetter(target); |
||||||
|
if (typeof key !== "string") { |
||||||
|
return Reflect.get(target, key); |
||||||
|
} |
||||||
|
if (isArray) { |
||||||
|
return new LanguageSet(subject, predicate, key, proxyContext); |
||||||
|
} |
||||||
|
return Reflect.get(target, key); |
||||||
|
}, |
||||||
|
set: (target, key, value) => { |
||||||
|
const language = languageKeyToLiteralLanguage(key); |
||||||
|
// Delete all quads with the language currently
|
||||||
|
if (!isArray) { |
||||||
|
languageDeleteMatch(proxyContext.dataset, subject, predicate, language); |
||||||
|
} |
||||||
|
// Add the new quad for the language
|
||||||
|
proxyContext.writeGraphs.forEach((writeGraph) => { |
||||||
|
proxyContext.dataset.add( |
||||||
|
quad(subject, predicate, literal(value, language), writeGraph), |
||||||
|
); |
||||||
|
}); |
||||||
|
return Reflect.set(target, key, value); |
||||||
|
}, |
||||||
|
deleteProperty: (target, key) => { |
||||||
|
languageDeleteMatch( |
||||||
|
proxyContext.dataset, |
||||||
|
subject, |
||||||
|
predicate, |
||||||
|
languageKeyToLiteralLanguage(key), |
||||||
|
); |
||||||
|
return Reflect.deleteProperty(target, key); |
||||||
|
}, |
||||||
|
}) as Target; |
||||||
|
} |
@ -0,0 +1,129 @@ |
|||||||
|
import type { Dataset, Literal } from "@rdfjs/types"; |
||||||
|
import type { PredicateType, SubjectType } from "../types"; |
||||||
|
import type { LanguageKey } from "./languageTypes"; |
||||||
|
import type { LiteralObjectQuad } from "./languageUtils"; |
||||||
|
import { languageDeleteMatch, languageMatch } from "./languageUtils"; |
||||||
|
import { literal, quad } from "@rdfjs/data-model"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
|
||||||
|
export default class LanguageSet implements Set<string> { |
||||||
|
private subject: SubjectType; |
||||||
|
private predicate: PredicateType; |
||||||
|
private languageKey: LanguageKey; |
||||||
|
private proxyContext: ProxyContext; |
||||||
|
|
||||||
|
constructor( |
||||||
|
subject: SubjectType, |
||||||
|
predicate: PredicateType, |
||||||
|
languageKey: LanguageKey, |
||||||
|
proxyContext: ProxyContext, |
||||||
|
) { |
||||||
|
this.subject = subject; |
||||||
|
this.predicate = predicate; |
||||||
|
this.languageKey = languageKey; |
||||||
|
this.proxyContext = proxyContext; |
||||||
|
} |
||||||
|
|
||||||
|
private matchThis(): Dataset<LiteralObjectQuad> { |
||||||
|
return languageMatch( |
||||||
|
this.proxyContext.dataset, |
||||||
|
this.subject, |
||||||
|
this.predicate, |
||||||
|
this.languageKey, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
private getLiteral(value: string): Literal { |
||||||
|
return this.languageKey === "@none" |
||||||
|
? literal(value) |
||||||
|
: literal(value, this.languageKey); |
||||||
|
} |
||||||
|
|
||||||
|
public get size(): number { |
||||||
|
return this.matchThis().size; |
||||||
|
} |
||||||
|
|
||||||
|
add(value: string): this { |
||||||
|
this.proxyContext.writeGraphs.forEach((graph) => { |
||||||
|
this.proxyContext.dataset.add( |
||||||
|
quad( |
||||||
|
this.subject, |
||||||
|
this.predicate, |
||||||
|
literal(value, this.languageKey), |
||||||
|
graph, |
||||||
|
), |
||||||
|
); |
||||||
|
}); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
clear(): void { |
||||||
|
languageDeleteMatch( |
||||||
|
this.proxyContext.dataset, |
||||||
|
this.subject, |
||||||
|
this.predicate, |
||||||
|
this.languageKey, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
delete(value: string): boolean { |
||||||
|
const hadValue = this.has(value); |
||||||
|
this.proxyContext.dataset.deleteMatches( |
||||||
|
this.subject, |
||||||
|
this.predicate, |
||||||
|
this.getLiteral(value), |
||||||
|
); |
||||||
|
return hadValue; |
||||||
|
} |
||||||
|
|
||||||
|
forEach( |
||||||
|
callbackfn: (value: string, value2: string, set: Set<string>) => void, |
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
thisArg?: any, |
||||||
|
): void { |
||||||
|
const quads = this.matchThis(); |
||||||
|
quads.forEach((curQuad) => { |
||||||
|
callbackfn(curQuad.object.value, curQuad.object.value, thisArg || this); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
has(item: string): boolean { |
||||||
|
return ( |
||||||
|
this.proxyContext.dataset.match( |
||||||
|
this.subject, |
||||||
|
this.predicate, |
||||||
|
this.getLiteral(item), |
||||||
|
).size > 0 |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
*entries(): IterableIterator<[string, string]> { |
||||||
|
const quads = this.matchThis(); |
||||||
|
for (const curQuad of quads) { |
||||||
|
yield [curQuad.object.value, curQuad.object.value]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
*keys(): IterableIterator<string> { |
||||||
|
const quads = this.matchThis(); |
||||||
|
for (const curQuad of quads) { |
||||||
|
yield curQuad.object.value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
*values(): IterableIterator<string> { |
||||||
|
const quads = this.matchThis(); |
||||||
|
for (const curQuad of quads) { |
||||||
|
yield curQuad.object.value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
*[Symbol.iterator](): IterableIterator<string> { |
||||||
|
const quads = this.matchThis(); |
||||||
|
for (const curQuad of quads) { |
||||||
|
yield curQuad.object.value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[Symbol.toStringTag] = "LanguageSet"; |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
export type LanguageOrdering = ("@none" | "@other" | string)[]; |
||||||
|
|
||||||
|
export type LanguageKey = "@none" | string; |
@ -0,0 +1,159 @@ |
|||||||
|
import type { Dataset, Literal, Quad, Quad_Object } from "@rdfjs/types"; |
||||||
|
import { createDataset } from "o-dataset-pack"; |
||||||
|
import type { PredicateType, SubjectType } from "../types"; |
||||||
|
import type { LanguageKey, LanguageOrdering } from "./languageTypes"; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @param dataset |
||||||
|
* @param subject |
||||||
|
* @param predicate |
||||||
|
* @param languageKey |
||||||
|
* @returns |
||||||
|
*/ |
||||||
|
export function languageMatch( |
||||||
|
dataset: Dataset, |
||||||
|
subject: SubjectType, |
||||||
|
predicate: PredicateType, |
||||||
|
languageKey: LanguageKey, |
||||||
|
): Dataset<LiteralObjectQuad> { |
||||||
|
const literalLanguage = languageKeyToLiteralLanguage(languageKey); |
||||||
|
return dataset.match(subject, predicate).filter((quad) => { |
||||||
|
return ( |
||||||
|
isLanguageLiteral(quad.object) && quad.object.language === literalLanguage |
||||||
|
); |
||||||
|
}) as Dataset<LiteralObjectQuad>; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @param dataset |
||||||
|
* @param subject |
||||||
|
* @param predicate |
||||||
|
* @param languageKey |
||||||
|
*/ |
||||||
|
export function languageDeleteMatch( |
||||||
|
dataset: Dataset, |
||||||
|
subject: SubjectType, |
||||||
|
predicate: PredicateType, |
||||||
|
languageKey: LanguageKey, |
||||||
|
): void { |
||||||
|
const quadsToDelete = languageMatch(dataset, subject, predicate, languageKey); |
||||||
|
quadsToDelete.forEach((quad) => { |
||||||
|
dataset.delete(quad); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Given a node, will return true if that node is a literal that could have a |
||||||
|
* language. This does not guarantee that it is a language literal. |
||||||
|
* @param node the node to test |
||||||
|
* @returns boolean |
||||||
|
*/ |
||||||
|
export function isLanguageLiteral(node: Quad_Object): node is Literal { |
||||||
|
return ( |
||||||
|
node.termType === "Literal" && |
||||||
|
(node.datatype.value === |
||||||
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString" || |
||||||
|
node.datatype.value === "http://www.w3.org/2001/XMLSchema#string") |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export interface LiteralObjectQuad extends Quad { |
||||||
|
object: Literal; |
||||||
|
} |
||||||
|
|
||||||
|
export function quadsToLanguageQuadMap( |
||||||
|
quads: Dataset, |
||||||
|
): Record<LanguageKey, Dataset<LiteralObjectQuad>> { |
||||||
|
const languageQuadMap: Record<LanguageKey, Dataset<LiteralObjectQuad>> = {}; |
||||||
|
quads.forEach((quad) => { |
||||||
|
const literal = quad.object; |
||||||
|
if (isLanguageLiteral(literal)) { |
||||||
|
const languageKey = literalLanguageToLanguageKey(literal.language); |
||||||
|
if (!languageQuadMap[languageKey]) { |
||||||
|
languageQuadMap[languageKey] = |
||||||
|
createDataset() as Dataset<LiteralObjectQuad>; |
||||||
|
} |
||||||
|
languageQuadMap[languageKey].add(quad as LiteralObjectQuad); |
||||||
|
} |
||||||
|
}); |
||||||
|
return languageQuadMap; |
||||||
|
} |
||||||
|
|
||||||
|
export function filterQuadsByLanguageOrdering( |
||||||
|
quads: Dataset, |
||||||
|
languageOrdering: LanguageOrdering, |
||||||
|
): Dataset { |
||||||
|
const languageQuadMap = quadsToLanguageQuadMap(quads); |
||||||
|
const validLanguages = new Set(languageOrdering); |
||||||
|
const presentLanguages = new Set(Object.keys(languageQuadMap)); |
||||||
|
for (const currentLanguageKey of languageOrdering) { |
||||||
|
if (presentLanguages.has(currentLanguageKey)) { |
||||||
|
return languageQuadMap[currentLanguageKey]; |
||||||
|
} |
||||||
|
if (currentLanguageKey === "@other") { |
||||||
|
for (const presentLang of presentLanguages) { |
||||||
|
if (!validLanguages.has(presentLang)) { |
||||||
|
return languageQuadMap[presentLang]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return createDataset(); |
||||||
|
} |
||||||
|
|
||||||
|
export function getLanguageKeyForWriteOperation( |
||||||
|
languageOrdering: LanguageOrdering, |
||||||
|
): LanguageKey | undefined { |
||||||
|
return languageOrdering.find((lang) => lang !== "@other"); |
||||||
|
} |
||||||
|
|
||||||
|
// function addToDatasetMap(
|
||||||
|
// key: string,
|
||||||
|
// value: Quad,
|
||||||
|
// map: Record<string, Dataset>
|
||||||
|
// ) {
|
||||||
|
// if (!map[key]) {
|
||||||
|
// map[key] = createDataset();
|
||||||
|
// }
|
||||||
|
// map[key].add(value);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function filterDatasetByLanguageOrdering(
|
||||||
|
// dataset: Dataset,
|
||||||
|
// proxyContext: ProxyContext
|
||||||
|
// ): Dataset {
|
||||||
|
// // TODO: This is an O(n) task that could be reduced to O(1) if we cached some
|
||||||
|
// // of the processing
|
||||||
|
// const validLangs = new Set(proxyContext.languageOrdering);
|
||||||
|
// const sortedLangs: Record<string, Dataset> = {};
|
||||||
|
// dataset.forEach((quad) => {
|
||||||
|
// const literal = quad.object;
|
||||||
|
// if (isLangStringNode(literal)) {
|
||||||
|
// if (literal.language === "") {
|
||||||
|
// addToDatasetMap("@none", quad, sortedLangs);
|
||||||
|
// } else if (validLangs.has(literal.language)) {
|
||||||
|
// addToDatasetMap(literal.language, quad, sortedLangs);
|
||||||
|
// } else {
|
||||||
|
// addToDatasetMap("@other", quad, sortedLangs);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// for (const language of proxyContext.languageOrdering) {
|
||||||
|
// if (sortedLangs[language]) {
|
||||||
|
// return sortedLangs[language];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return createDataset();
|
||||||
|
// }
|
||||||
|
|
||||||
|
export function languageKeyToLiteralLanguage( |
||||||
|
languageKey: string | symbol, |
||||||
|
): string { |
||||||
|
return (languageKey === "@none" ? "" : languageKey) as string; |
||||||
|
} |
||||||
|
|
||||||
|
export function literalLanguageToLanguageKey(literalLanguage: string): string { |
||||||
|
return literalLanguage === "" ? "@none" : literalLanguage; |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
import { namedNode } from "@rdfjs/data-model"; |
||||||
|
import { getSubjectProxyFromObject } from "../subjectProxy/isSubjectProxy"; |
||||||
|
import type { ObjectLike } from "../types"; |
||||||
|
import { _getUnderlyingNode, _proxyContext } from "../types"; |
||||||
|
import { createLanguageMapProxy } from "./languageMapProxy"; |
||||||
|
|
||||||
|
/** |
||||||
|
* ----------------------------------------------------------------------------- |
||||||
|
* Types |
||||||
|
* ----------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
|
||||||
|
export type LanguageMap = { |
||||||
|
"@none"?: string; |
||||||
|
[language: string]: string | undefined; |
||||||
|
}; |
||||||
|
|
||||||
|
export type LanguageSetMap = { |
||||||
|
"@none"?: LanguageSet; |
||||||
|
[language: string]: LanguageSet | undefined; |
||||||
|
}; |
||||||
|
|
||||||
|
export type LanguageSet = Set<string>; |
||||||
|
|
||||||
|
export type LanguageOfConditionalReturn< |
||||||
|
SubjectObject extends ObjectLike, |
||||||
|
Key extends keyof SubjectObject, |
||||||
|
> = NonNullable<SubjectObject[Key]> extends Array<unknown> |
||||||
|
? LanguageSetMap |
||||||
|
: LanguageMap; |
||||||
|
|
||||||
|
/** |
||||||
|
* ----------------------------------------------------------------------------- |
||||||
|
* Functions |
||||||
|
* ----------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @param subject |
||||||
|
* @param predicate |
||||||
|
* @returns |
||||||
|
*/ |
||||||
|
export function languagesOf< |
||||||
|
SubjectObject extends ObjectLike, |
||||||
|
Key extends keyof SubjectObject, |
||||||
|
>( |
||||||
|
subjectObject: SubjectObject, |
||||||
|
key: Key, |
||||||
|
): LanguageOfConditionalReturn<SubjectObject, Key> { |
||||||
|
const proxy = getSubjectProxyFromObject(subjectObject); |
||||||
|
const proxyContext = proxy[_proxyContext]; |
||||||
|
const subject = proxy[_getUnderlyingNode]; |
||||||
|
const predicate = namedNode(proxyContext.contextUtil.keyToIri(key as string)); |
||||||
|
return createLanguageMapProxy<LanguageMap>( |
||||||
|
subject, |
||||||
|
predicate, |
||||||
|
proxyContext, |
||||||
|
proxyContext.contextUtil.isArray(key as string), |
||||||
|
) as LanguageOfConditionalReturn<SubjectObject, Key>; |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
import type { LanguageOrdering } from "./language/languageTypes"; |
||||||
|
import type { InteractOptions } from "./util/createInteractOptions"; |
||||||
|
import { createInteractOptions } from "./util/createInteractOptions"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the default language pr |
||||||
|
* @param graphs The graphs that should be written to |
||||||
|
* @returns a write builder |
||||||
|
*/ |
||||||
|
export function setLanguagePreferences( |
||||||
|
...languageOrdering: LanguageOrdering |
||||||
|
): InteractOptions { |
||||||
|
return createInteractOptions("languageOrdering", languageOrdering); |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
import type { BlankNode, Dataset, NamedNode } from "@rdfjs/types"; |
||||||
|
import type { ContextDefinition } from "jsonld"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
import type { |
||||||
|
GraphType, |
||||||
|
_getUnderlyingDataset, |
||||||
|
_getUnderlyingNode, |
||||||
|
_proxyContext, |
||||||
|
_writeGraphs, |
||||||
|
} from "../types"; |
||||||
|
|
||||||
|
export type SubjectProxy = { |
||||||
|
"@id"?: string; |
||||||
|
"@context": ContextDefinition; |
||||||
|
readonly [key: string | number | symbol]: unknown; |
||||||
|
readonly [_getUnderlyingDataset]: Dataset; |
||||||
|
readonly [_getUnderlyingNode]: NamedNode | BlankNode; |
||||||
|
[_proxyContext]: ProxyContext; |
||||||
|
readonly [_writeGraphs]: GraphType[]; |
||||||
|
}; |
@ -0,0 +1,106 @@ |
|||||||
|
import type { BlankNode, NamedNode } from "@rdfjs/types"; |
||||||
|
import { namedNode, quad } from "@rdfjs/data-model"; |
||||||
|
import { addObjectToDataset } from "../util/addObjectToDataset"; |
||||||
|
import { deleteValueFromDataset } from "./deleteFromDataset"; |
||||||
|
import { |
||||||
|
_getUnderlyingDataset, |
||||||
|
_getUnderlyingNode, |
||||||
|
_proxyContext, |
||||||
|
_writeGraphs, |
||||||
|
} from "../types"; |
||||||
|
import { getValueForKey } from "./getValueForKey"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
|
||||||
|
export interface SubjectProxyTarget { |
||||||
|
"@id": NamedNode | BlankNode; |
||||||
|
} |
||||||
|
|
||||||
|
export function createSubjectHandler( |
||||||
|
initialProxyContext: ProxyContext, |
||||||
|
): ProxyHandler<SubjectProxyTarget> { |
||||||
|
let proxyContext = initialProxyContext; |
||||||
|
return { |
||||||
|
get(target: SubjectProxyTarget, key: string | symbol) { |
||||||
|
switch (key) { |
||||||
|
case _getUnderlyingDataset: |
||||||
|
return proxyContext.dataset; |
||||||
|
case _getUnderlyingNode: |
||||||
|
return target["@id"]; |
||||||
|
case _proxyContext: |
||||||
|
return proxyContext; |
||||||
|
case _writeGraphs: |
||||||
|
return proxyContext.writeGraphs; |
||||||
|
case "@context": |
||||||
|
return proxyContext.contextUtil.context; |
||||||
|
} |
||||||
|
return getValueForKey(target, key, proxyContext); |
||||||
|
}, |
||||||
|
getOwnPropertyDescriptor(target: SubjectProxyTarget, key: string) { |
||||||
|
return { |
||||||
|
value: getValueForKey(target, key, proxyContext), |
||||||
|
writable: true, |
||||||
|
enumerable: true, |
||||||
|
configurable: true, |
||||||
|
}; |
||||||
|
}, |
||||||
|
ownKeys(target) { |
||||||
|
const subject = target["@id"]; |
||||||
|
const tripleDataset = proxyContext.dataset.match(subject); |
||||||
|
const keys: Set<string> = new Set(["@id"]); |
||||||
|
tripleDataset.toArray().forEach((quad) => { |
||||||
|
keys.add(proxyContext.contextUtil.iriToKey(quad.predicate.value)); |
||||||
|
}); |
||||||
|
return Array.from(keys); |
||||||
|
}, |
||||||
|
set: (target: SubjectProxyTarget, key, value) => { |
||||||
|
if (key === _proxyContext) { |
||||||
|
proxyContext = value; |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (key === "@id" && typeof value === "string") { |
||||||
|
// Replace Subject Quads
|
||||||
|
const currentSubjectQuads = proxyContext.dataset |
||||||
|
.match(target["@id"]) |
||||||
|
.toArray(); |
||||||
|
const newSubjectQuads = currentSubjectQuads.map((curQuad) => |
||||||
|
quad( |
||||||
|
namedNode(value), |
||||||
|
curQuad.predicate, |
||||||
|
curQuad.object, |
||||||
|
curQuad.graph, |
||||||
|
), |
||||||
|
); |
||||||
|
currentSubjectQuads.forEach((curQuad) => |
||||||
|
proxyContext.dataset.delete(curQuad), |
||||||
|
); |
||||||
|
proxyContext.dataset.addAll(newSubjectQuads); |
||||||
|
// Replace Object Quads
|
||||||
|
const currentObjectQuads = proxyContext.dataset |
||||||
|
.match(undefined, undefined, target["@id"]) |
||||||
|
.toArray(); |
||||||
|
const newObjectQuads = currentObjectQuads.map((curQuad) => |
||||||
|
quad( |
||||||
|
curQuad.subject, |
||||||
|
curQuad.predicate, |
||||||
|
namedNode(value), |
||||||
|
curQuad.graph, |
||||||
|
), |
||||||
|
); |
||||||
|
currentObjectQuads.forEach((curQuad) => |
||||||
|
proxyContext.dataset.delete(curQuad), |
||||||
|
); |
||||||
|
proxyContext.dataset.addAll(newObjectQuads); |
||||||
|
target["@id"] = namedNode(value); |
||||||
|
} |
||||||
|
addObjectToDataset( |
||||||
|
{ "@id": target["@id"], [key]: value }, |
||||||
|
true, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
return true; |
||||||
|
}, |
||||||
|
deleteProperty(target, key) { |
||||||
|
return deleteValueFromDataset(target, key, proxyContext); |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
import type { Term } from "@rdfjs/types"; |
||||||
|
import { namedNode, quad } from "@rdfjs/data-model"; |
||||||
|
import type { SubjectProxyTarget } from "./createSubjectHandler"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
|
||||||
|
export function deleteValueFromDataset( |
||||||
|
target: SubjectProxyTarget, |
||||||
|
key: string | symbol, |
||||||
|
proxyContext: ProxyContext, |
||||||
|
) { |
||||||
|
const nodesToRemove: Term[] = []; |
||||||
|
if (key === "@context") { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (key === "toString" || key === Symbol.toStringTag) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (typeof key === "symbol") { |
||||||
|
return true; |
||||||
|
} |
||||||
|
const subject = target["@id"]; |
||||||
|
const predicate = namedNode(proxyContext.contextUtil.keyToIri(key)); |
||||||
|
if (key === "@id") { |
||||||
|
nodesToRemove.push(target["@id"]); |
||||||
|
} else { |
||||||
|
const objectDataset = proxyContext.dataset.match(subject, predicate); |
||||||
|
if (objectDataset.size === 0) { |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
nodesToRemove.push(...objectDataset.toArray().map((quad) => quad.object)); |
||||||
|
} |
||||||
|
} |
||||||
|
nodesToRemove.forEach((term) => { |
||||||
|
if (term.termType === "Literal") { |
||||||
|
proxyContext.dataset.delete(quad(subject, predicate, term)); |
||||||
|
return true; |
||||||
|
} else if (term.termType === "NamedNode") { |
||||||
|
proxyContext.dataset.deleteMatches(term, undefined, undefined); |
||||||
|
proxyContext.dataset.deleteMatches(undefined, undefined, term); |
||||||
|
return true; |
||||||
|
} |
||||||
|
}); |
||||||
|
return true; |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
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 "../arrayProxy/ArrayProxy"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
import { filterQuadsByLanguageOrdering } from "../language/languageUtils"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Given a subject target and a key return the correct value |
||||||
|
*/ |
||||||
|
export function getValueForKey( |
||||||
|
target: SubjectProxyTarget, |
||||||
|
key: string | symbol, |
||||||
|
proxyContext: ProxyContext, |
||||||
|
): SubjectProxy | ArrayProxy | string | number | boolean | undefined { |
||||||
|
const { contextUtil, dataset } = proxyContext; |
||||||
|
if (key === "@id") { |
||||||
|
if (target["@id"].termType === "BlankNode") { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
return contextUtil.iriToKey(target["@id"].value); |
||||||
|
} |
||||||
|
if (key === "toString" || key === Symbol.toStringTag) { |
||||||
|
// TODO: this toString method right now returns [object Object],
|
||||||
|
// which is correct, but it could be more descriptive, especially
|
||||||
|
// because console.log doesn't return anyting helpful due to the proxy.
|
||||||
|
return Reflect.get(target, "toString"); |
||||||
|
} |
||||||
|
if (typeof key === "symbol") { |
||||||
|
return; |
||||||
|
} |
||||||
|
const subject = target["@id"]; |
||||||
|
const predicate = namedNode(contextUtil.keyToIri(key)); |
||||||
|
if (contextUtil.isArray(key)) { |
||||||
|
const arrayProxy = proxyContext.createArrayProxy( |
||||||
|
[subject, predicate, null, null], |
||||||
|
false, |
||||||
|
undefined, |
||||||
|
contextUtil.isLangString(key), |
||||||
|
); |
||||||
|
return arrayProxy; |
||||||
|
} |
||||||
|
let objectDataset = dataset.match(subject, predicate); |
||||||
|
if (contextUtil.isLangString(key)) { |
||||||
|
objectDataset = filterQuadsByLanguageOrdering( |
||||||
|
objectDataset, |
||||||
|
proxyContext.languageOrdering, |
||||||
|
); |
||||||
|
} |
||||||
|
if (objectDataset.size === 0) { |
||||||
|
return undefined; |
||||||
|
} else if (objectDataset.size === 1) { |
||||||
|
const thing = nodeToJsonldRepresentation( |
||||||
|
objectDataset.toArray()[0].object, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
return thing; |
||||||
|
} else { |
||||||
|
return proxyContext.createArrayProxy([subject, predicate, null, null]); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
import type { ObjectLike } from "../types"; |
||||||
|
import { |
||||||
|
_getUnderlyingDataset, |
||||||
|
_getUnderlyingNode, |
||||||
|
_proxyContext, |
||||||
|
_writeGraphs, |
||||||
|
} from "../types"; |
||||||
|
import type { SubjectProxy } from "./SubjectProxy"; |
||||||
|
|
||||||
|
export function isSubjectProxy( |
||||||
|
someObject?: unknown, |
||||||
|
): someObject is SubjectProxy { |
||||||
|
if (!someObject) return false; |
||||||
|
if (typeof someObject !== "object") return false; |
||||||
|
const potentialSubjectProxy = someObject as SubjectProxy; |
||||||
|
return !( |
||||||
|
typeof potentialSubjectProxy[_writeGraphs] !== "object" || |
||||||
|
typeof potentialSubjectProxy[_getUnderlyingDataset] !== "object" || |
||||||
|
typeof potentialSubjectProxy[_getUnderlyingNode] !== "object" || |
||||||
|
typeof potentialSubjectProxy[_proxyContext] !== "object" |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export function getSubjectProxyFromObject(object: ObjectLike): SubjectProxy { |
||||||
|
const potentialSubjectProxy = object as SubjectProxy; |
||||||
|
if (!isSubjectProxy(potentialSubjectProxy)) { |
||||||
|
throw new Error(`${object} is not a Jsonld Dataset Proxy Subject`); |
||||||
|
} |
||||||
|
return potentialSubjectProxy; |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
import type { BlankNode, DefaultGraph, Literal, NamedNode } from "@rdfjs/types"; |
||||||
|
|
||||||
|
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"); |
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export type ObjectLike = Record<string | number | symbol, any>; |
||||||
|
|
||||||
|
export type SubjectType = NamedNode | BlankNode; |
||||||
|
export type PredicateType = NamedNode; |
||||||
|
export type ObjectType = NamedNode | BlankNode | Literal; |
||||||
|
export type GraphType = NamedNode | BlankNode | DefaultGraph; |
||||||
|
|
||||||
|
export type QuadMatch = [ |
||||||
|
SubjectType | undefined | null, |
||||||
|
PredicateType | undefined | null, |
||||||
|
ObjectType | undefined | null, |
||||||
|
GraphType | undefined | null, |
||||||
|
]; |
@ -0,0 +1,47 @@ |
|||||||
|
import type { BlankNode, DefaultGraph, Literal, NamedNode } from "@rdfjs/types"; |
||||||
|
import type { ObjectType } from "../types"; |
||||||
|
|
||||||
|
export function nodeToString( |
||||||
|
node: NamedNode | BlankNode | DefaultGraph | Literal | null | undefined, |
||||||
|
): string { |
||||||
|
if (node == null) { |
||||||
|
return "null"; |
||||||
|
} |
||||||
|
switch (node.termType) { |
||||||
|
case "NamedNode": |
||||||
|
return `namedNode(${node.value})`; |
||||||
|
case "BlankNode": |
||||||
|
return `blankNode(${node.value})`; |
||||||
|
case "Literal": |
||||||
|
return `literal(${node.value},${node.datatype.value})`; |
||||||
|
case "DefaultGraph": |
||||||
|
return "defaultGraph()"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export class NodeSet { |
||||||
|
private set: Set<string> = new Set(); |
||||||
|
private map: Record<string, ObjectType> = {}; |
||||||
|
|
||||||
|
add(node: ObjectType) { |
||||||
|
const key = nodeToString(node); |
||||||
|
this.set.add(key); |
||||||
|
this.map[key] = node; |
||||||
|
} |
||||||
|
|
||||||
|
has(node: ObjectType): boolean { |
||||||
|
return this.set.has(nodeToString(node)); |
||||||
|
} |
||||||
|
|
||||||
|
delete(node: ObjectType) { |
||||||
|
const key = nodeToString(node); |
||||||
|
delete this.map[key]; |
||||||
|
return this.set.delete(nodeToString(node)); |
||||||
|
} |
||||||
|
|
||||||
|
toArray() { |
||||||
|
return Array.from(this.set).map((stringVal) => { |
||||||
|
return this.map[stringVal]; |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
import type { BlankNode, NamedNode } from "@rdfjs/types"; |
||||||
|
import { _getUnderlyingNode } from "../types"; |
||||||
|
import type { SubjectProxy } from "../subjectProxy/SubjectProxy"; |
||||||
|
|
||||||
|
export type RawObject = |
||||||
|
| ({ |
||||||
|
"@id"?: string | NamedNode | BlankNode; |
||||||
|
} & { |
||||||
|
[key: string | symbol | number]: RawValue | RawValue[]; |
||||||
|
}) |
||||||
|
| SubjectProxy; |
||||||
|
|
||||||
|
export type RawValue = string | boolean | number | RawObject | undefined; |
@ -0,0 +1,140 @@ |
|||||||
|
import type { BlankNode, NamedNode } from "@rdfjs/types"; |
||||||
|
import { literal, namedNode, quad } from "@rdfjs/data-model"; |
||||||
|
import { _getUnderlyingNode } from "../types"; |
||||||
|
import type { SubjectProxy } from "../subjectProxy/SubjectProxy"; |
||||||
|
import { getNodeFromRawObject, getNodeFromRawValue } from "./getNodeFromRaw"; |
||||||
|
import type { RawObject, RawValue } from "./RawObject"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
import { isSubjectProxy } from "../subjectProxy/isSubjectProxy"; |
||||||
|
import { NodeSet } from "./NodeSet"; |
||||||
|
import { |
||||||
|
getLanguageKeyForWriteOperation, |
||||||
|
languageDeleteMatch, |
||||||
|
languageKeyToLiteralLanguage, |
||||||
|
} from "../language/languageUtils"; |
||||||
|
|
||||||
|
export function addRawValueToDatasetRecursive( |
||||||
|
subject: NamedNode | BlankNode, |
||||||
|
key: string, |
||||||
|
value: RawValue, |
||||||
|
visitedObjects: NodeSet, |
||||||
|
shouldDeleteOldTriples: boolean, |
||||||
|
proxyContext: ProxyContext, |
||||||
|
): void { |
||||||
|
const { dataset, contextUtil } = proxyContext; |
||||||
|
const predicate = namedNode(contextUtil.keyToIri(key)); |
||||||
|
// Get the Object Node
|
||||||
|
const object = getNodeFromRawValue(key, value, proxyContext); |
||||||
|
if (object == undefined) { |
||||||
|
dataset.deleteMatches(subject, predicate); |
||||||
|
} else if (object.termType === "Literal") { |
||||||
|
let languageAppliedObject = object; |
||||||
|
// Handle language use case
|
||||||
|
if (contextUtil.isLangString(key)) { |
||||||
|
const languageKey = getLanguageKeyForWriteOperation( |
||||||
|
proxyContext.languageOrdering, |
||||||
|
); |
||||||
|
if (!languageKey) return; |
||||||
|
languageAppliedObject = literal( |
||||||
|
object.value, |
||||||
|
languageKeyToLiteralLanguage(languageKey), |
||||||
|
); |
||||||
|
} |
||||||
|
proxyContext.writeGraphs.forEach((graph) => { |
||||||
|
proxyContext.dataset.add( |
||||||
|
quad(subject, predicate, languageAppliedObject, graph), |
||||||
|
); |
||||||
|
}); |
||||||
|
} else { |
||||||
|
// Delete any triples if the id is the same
|
||||||
|
if (!visitedObjects.has(object) && !isSubjectProxy(value)) { |
||||||
|
dataset.deleteMatches(object, undefined, undefined); |
||||||
|
} |
||||||
|
proxyContext.writeGraphs.forEach((graph) => { |
||||||
|
dataset.add(quad(subject, predicate, object, graph)); |
||||||
|
}); |
||||||
|
if (!isSubjectProxy(value)) { |
||||||
|
const updateData: RawObject = ( |
||||||
|
typeof value === "object" |
||||||
|
? { ...value, "@id": object } |
||||||
|
: { "@id": object } |
||||||
|
) as RawObject; |
||||||
|
addRawObjectToDatasetRecursive( |
||||||
|
updateData, |
||||||
|
visitedObjects, |
||||||
|
shouldDeleteOldTriples, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function addRawObjectToDatasetRecursive( |
||||||
|
item: RawObject, |
||||||
|
visitedObjects: NodeSet, |
||||||
|
shouldDeleteOldTriples: boolean, |
||||||
|
proxyContext: ProxyContext, |
||||||
|
): SubjectProxy { |
||||||
|
if (isSubjectProxy(item)) { |
||||||
|
return item as SubjectProxy; |
||||||
|
} |
||||||
|
const { dataset } = proxyContext; |
||||||
|
const subject = getNodeFromRawObject(item, proxyContext.contextUtil); |
||||||
|
if (visitedObjects.has(subject)) { |
||||||
|
return proxyContext.createSubjectProxy(subject); |
||||||
|
} |
||||||
|
visitedObjects.add(subject); |
||||||
|
Object.entries(item).forEach(([key, value]) => { |
||||||
|
if (key === "@id") { |
||||||
|
return; |
||||||
|
} |
||||||
|
const predicate = namedNode(proxyContext.contextUtil.keyToIri(key)); |
||||||
|
if (shouldDeleteOldTriples) { |
||||||
|
if (proxyContext.contextUtil.isLangString(key)) { |
||||||
|
const languageKey = getLanguageKeyForWriteOperation( |
||||||
|
proxyContext.languageOrdering, |
||||||
|
); |
||||||
|
if (languageKey) { |
||||||
|
languageDeleteMatch(dataset, subject, predicate, languageKey); |
||||||
|
} |
||||||
|
} else { |
||||||
|
dataset.deleteMatches(subject, predicate); |
||||||
|
} |
||||||
|
} |
||||||
|
if (Array.isArray(value)) { |
||||||
|
value.forEach((valueItem) => { |
||||||
|
addRawValueToDatasetRecursive( |
||||||
|
subject, |
||||||
|
key, |
||||||
|
valueItem, |
||||||
|
visitedObjects, |
||||||
|
true, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
}); |
||||||
|
} else { |
||||||
|
addRawValueToDatasetRecursive( |
||||||
|
subject, |
||||||
|
key, |
||||||
|
value as RawValue, |
||||||
|
visitedObjects, |
||||||
|
true, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
} |
||||||
|
}); |
||||||
|
return proxyContext.createSubjectProxy(subject); |
||||||
|
} |
||||||
|
|
||||||
|
export function addObjectToDataset( |
||||||
|
item: RawObject, |
||||||
|
shouldDeleteOldTriples: boolean, |
||||||
|
proxyContext: ProxyContext, |
||||||
|
): SubjectProxy { |
||||||
|
return addRawObjectToDatasetRecursive( |
||||||
|
item, |
||||||
|
new NodeSet(), |
||||||
|
shouldDeleteOldTriples, |
||||||
|
proxyContext, |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
import { getSubjectProxyFromObject } from "../subjectProxy/isSubjectProxy"; |
||||||
|
import type { ObjectLike } from "../types"; |
||||||
|
import { _getUnderlyingNode, _proxyContext } from "../types"; |
||||||
|
import { getProxyFromObject } from "./isProxy"; |
||||||
|
|
||||||
|
export interface InteractOptions { |
||||||
|
/** |
||||||
|
* Given a dataset proxy, this will set the action on that dataset proxy |
||||||
|
* @param objects Any number of dataset proxies |
||||||
|
* @returns An end function. Call this if to reset the interaction |
||||||
|
*/ |
||||||
|
using(...objects: ObjectLike[]): () => void; |
||||||
|
/** |
||||||
|
* Given a dataset proxy this will copy the dataset proxy and set the action |
||||||
|
* on the copy |
||||||
|
* @param objects Any number of dataset proxies |
||||||
|
* @returns cloned dataset proxies |
||||||
|
*/ |
||||||
|
usingCopy<T extends ObjectLike>(...objects: T[]): T[]; |
||||||
|
} |
||||||
|
|
||||||
|
export function createInteractOptions( |
||||||
|
paramKey: string, |
||||||
|
parameter: unknown, |
||||||
|
): InteractOptions { |
||||||
|
return { |
||||||
|
using(...objects: ObjectLike[]): () => void { |
||||||
|
const onEndFunctions: (() => void)[] = []; |
||||||
|
objects.forEach((object) => { |
||||||
|
const proxy = getProxyFromObject(object); |
||||||
|
const oldProxyContext = proxy[_proxyContext]; |
||||||
|
proxy[_proxyContext] = proxy[_proxyContext].duplicate({ |
||||||
|
[paramKey]: parameter, |
||||||
|
}); |
||||||
|
onEndFunctions.push(() => { |
||||||
|
proxy[_proxyContext] = oldProxyContext; |
||||||
|
}); |
||||||
|
}); |
||||||
|
return function endWrite() { |
||||||
|
onEndFunctions.forEach((func) => func()); |
||||||
|
}; |
||||||
|
}, |
||||||
|
usingCopy<T extends ObjectLike>(...objects: T[]): T[] { |
||||||
|
return objects.map((object) => { |
||||||
|
const proxy = getSubjectProxyFromObject(object); |
||||||
|
const newProxyContext = proxy[_proxyContext].duplicate({ |
||||||
|
[paramKey]: parameter, |
||||||
|
}); |
||||||
|
return newProxyContext.createSubjectProxy( |
||||||
|
proxy[_getUnderlyingNode], |
||||||
|
) as unknown as T; |
||||||
|
}); |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
import type { BlankNode, Literal, NamedNode } from "@rdfjs/types"; |
||||||
|
import { namedNode, literal, blankNode } from "@rdfjs/data-model"; |
||||||
|
import type { ContextUtil } from "../ContextUtil"; |
||||||
|
import { _getUnderlyingNode } from "../types"; |
||||||
|
import type { RawObject, RawValue } from "./RawObject"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
|
||||||
|
export function getNodeFromRawObject( |
||||||
|
item: RawObject, |
||||||
|
contextUtil: ContextUtil, |
||||||
|
): NamedNode | BlankNode { |
||||||
|
if (item[_getUnderlyingNode]) { |
||||||
|
return item[_getUnderlyingNode] as NamedNode | BlankNode; |
||||||
|
} else if (!item["@id"]) { |
||||||
|
return blankNode(); |
||||||
|
} else if (typeof item["@id"] === "string") { |
||||||
|
return namedNode(contextUtil.keyToIri(item["@id"])); |
||||||
|
} else { |
||||||
|
return item["@id"]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function getNodeFromRawValue( |
||||||
|
key: string, |
||||||
|
value: RawValue, |
||||||
|
proxyContext: ProxyContext, |
||||||
|
): BlankNode | NamedNode | Literal | undefined { |
||||||
|
// Get the Object Node
|
||||||
|
if (value == undefined) { |
||||||
|
return undefined; |
||||||
|
} else if ( |
||||||
|
typeof value === "string" || |
||||||
|
typeof value === "boolean" || |
||||||
|
typeof value === "number" |
||||||
|
) { |
||||||
|
const datatype = proxyContext.contextUtil.getType(key); |
||||||
|
if (datatype === "@id") { |
||||||
|
return namedNode(value.toString()); |
||||||
|
} else { |
||||||
|
return literal(value.toString(), datatype); |
||||||
|
} |
||||||
|
} else { |
||||||
|
return getNodeFromRawObject(value, proxyContext.contextUtil); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
import type { ArrayProxy } from "../arrayProxy/ArrayProxy"; |
||||||
|
import { isArrayProxy } from "../arrayProxy/isArrayProxy"; |
||||||
|
import { isSubjectProxy } from "../subjectProxy/isSubjectProxy"; |
||||||
|
import type { SubjectProxy } from "../subjectProxy/SubjectProxy"; |
||||||
|
import type { ObjectLike } from "../types"; |
||||||
|
|
||||||
|
export function isProxy( |
||||||
|
someObject?: unknown, |
||||||
|
): someObject is ArrayProxy | SubjectProxy { |
||||||
|
return isSubjectProxy(someObject) || isArrayProxy(someObject); |
||||||
|
} |
||||||
|
|
||||||
|
export function getProxyFromObject( |
||||||
|
object: ObjectLike | ObjectLike[], |
||||||
|
): SubjectProxy | ArrayProxy { |
||||||
|
if (!isProxy(object)) { |
||||||
|
throw new Error(`${object} is not a Jsonld Dataset Proxy`); |
||||||
|
} |
||||||
|
return object; |
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
import type { Literal, Quad_Object } from "@rdfjs/types"; |
||||||
|
import type { ProxyContext } from "../ProxyContext"; |
||||||
|
import type { SubjectProxy } from "../subjectProxy/SubjectProxy"; |
||||||
|
|
||||||
|
export type ObjectJsonRepresentation = string | number | boolean | SubjectProxy; |
||||||
|
|
||||||
|
export function literalToJsonldRepresentation(literal: Literal) { |
||||||
|
switch (literal.datatype.value) { |
||||||
|
case "http://www.w3.org/2001/XMLSchema#string": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#ENTITIES": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#ENTITY": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#ID": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#IDREF": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#IDREFS": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#language": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#Name": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#NCName": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#NMTOKEN": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#NMTOKENS": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#normalizedString": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#QName": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#token": |
||||||
|
return literal.value; |
||||||
|
case "http://www.w3.org/2001/XMLSchema#date": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#dateTime": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#duration": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#gDay": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#gMonth": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#gMonthDay": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#gYear": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#gYearMonth": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#time": |
||||||
|
return literal.value; |
||||||
|
case "http://www.w3.org/2001/XMLSchema#integer": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#byte": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#decimal": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#int": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#long": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#negativeInteger": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#nonNegativeInteger": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#nonPositiveInteger": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#positiveInteger": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#short": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#unsignedLong": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#unsignedInt": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#unsignedShort": |
||||||
|
case "http://www.w3.org/2001/XMLSchema#unsignedByte": |
||||||
|
return parseFloat(literal.value); |
||||||
|
case "http://www.w3.org/2001/XMLSchema#boolean": |
||||||
|
return literal.value === "true"; |
||||||
|
case "http://www.w3.org/2001/XMLSchema#hexBinary": |
||||||
|
return literal.value; |
||||||
|
case "http://www.w3.org/2001/XMLSchema#anyURI": |
||||||
|
return literal.value; |
||||||
|
case "http://www.w3.org/1999/02/22-rdf-syntax-ns#HTML": |
||||||
|
case "http://www.w3.org/1999/02/22-rdf-syntax-ns#PlainLiteral": |
||||||
|
case "http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral": |
||||||
|
case "http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON": |
||||||
|
return literal.value; |
||||||
|
default: |
||||||
|
return literal.value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function nodeToJsonldRepresentation( |
||||||
|
node: Quad_Object, |
||||||
|
proxyContext: ProxyContext, |
||||||
|
): string | number | boolean | SubjectProxy { |
||||||
|
if (node.termType === "Literal") { |
||||||
|
return literalToJsonldRepresentation(node); |
||||||
|
} else if (node.termType === "NamedNode" || node.termType === "BlankNode") { |
||||||
|
return proxyContext.createSubjectProxy(node); |
||||||
|
} else { |
||||||
|
throw new Error("Can only convert NamedNodes or Literals or BlankNodes"); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
import type { GraphType } from "./types"; |
||||||
|
import type { InteractOptions } from "./util/createInteractOptions"; |
||||||
|
import { createInteractOptions } from "./util/createInteractOptions"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the graphs that should be written to |
||||||
|
* @param graphs The graphs that should be written to |
||||||
|
* @returns a write builder |
||||||
|
*/ |
||||||
|
export function write(...graphs: GraphType[]): InteractOptions { |
||||||
|
return createInteractOptions("writeGraphs", graphs); |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
import { ContextUtil } from "../src/ContextUtil"; |
||||||
|
|
||||||
|
describe("ContextUtil", () => { |
||||||
|
describe("keyToIri and iriToKey", () => { |
||||||
|
it("handles a context that is simply a string map", () => { |
||||||
|
const fakeContext = { |
||||||
|
name: "http://hl7.org/fhir/name", |
||||||
|
}; |
||||||
|
const contextUtil = new ContextUtil(fakeContext); |
||||||
|
expect(contextUtil.keyToIri("name")).toBe("http://hl7.org/fhir/name"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns the given key if it is not in the context", () => { |
||||||
|
const contextUtil = new ContextUtil({}); |
||||||
|
expect(contextUtil.keyToIri("name")).toBe("name"); |
||||||
|
expect(contextUtil.iriToKey("http://hl7.org/fhir/name")).toBe( |
||||||
|
"http://hl7.org/fhir/name", |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("handles a context that existsm, but does not have an id", () => { |
||||||
|
const contextUtil = new ContextUtil({ |
||||||
|
name: { "@type": "http://www.w3.org/2001/XMLSchema#string" }, |
||||||
|
}); |
||||||
|
expect(contextUtil.keyToIri("name")).toBe("name"); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("getType", () => { |
||||||
|
it("returns xsd:string if no type is provided", () => { |
||||||
|
const contextUtil = new ContextUtil({ |
||||||
|
name: { "@id": "http://hl7.org/fhir/name" }, |
||||||
|
}); |
||||||
|
expect(contextUtil.getType("name")).toBe( |
||||||
|
"http://www.w3.org/2001/XMLSchema#string", |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,36 @@ |
|||||||
|
import { |
||||||
|
getProxyFromObject, |
||||||
|
getSubjectProxyFromObject, |
||||||
|
isArrayProxy, |
||||||
|
isSubjectProxy, |
||||||
|
} from "../src"; |
||||||
|
|
||||||
|
describe("isSubjectProxy", () => { |
||||||
|
it("returns false if undefined is passed as a parameter", () => { |
||||||
|
expect(isSubjectProxy(undefined)).toBe(false); |
||||||
|
}); |
||||||
|
|
||||||
|
it("throws an error if the given object isn't a subject proxy", () => { |
||||||
|
expect(() => getSubjectProxyFromObject({ cool: "bean" })).toThrowError( |
||||||
|
`[object Object] is not a Jsonld Dataset Proxy`, |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("isProxy", () => { |
||||||
|
it("throws an error if the given object isn't a proxy", () => { |
||||||
|
expect(() => getProxyFromObject({ cool: "bean" })).toThrowError( |
||||||
|
`[object Object] is not a Jsonld Dataset Proxy`, |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("isArrayProxy", () => { |
||||||
|
it("returns false if undefined is passed as a parameter", () => { |
||||||
|
expect(isArrayProxy(undefined)).toBe(false); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns false if string is passed as a parameter", () => { |
||||||
|
expect(isArrayProxy("hello")).toBe(false); |
||||||
|
}); |
||||||
|
}); |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,63 @@ |
|||||||
|
import { createDataset } from "o-dataset-pack"; |
||||||
|
import { ContextUtil } from "../src/ContextUtil"; |
||||||
|
import { nodeToJsonldRepresentation } from "../src/util/nodeToJsonldRepresentation"; |
||||||
|
import { literal, defaultGraph } from "@rdfjs/data-model"; |
||||||
|
import { ProxyContext } from "../src"; |
||||||
|
|
||||||
|
describe("objectToJsonRepresentation", () => { |
||||||
|
const extraParams: ProxyContext = new ProxyContext({ |
||||||
|
dataset: createDataset(), |
||||||
|
contextUtil: new ContextUtil({}), |
||||||
|
writeGraphs: [defaultGraph()], |
||||||
|
languageOrdering: ["@none", "@other"], |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns a string for hexBinary", () => { |
||||||
|
expect( |
||||||
|
nodeToJsonldRepresentation( |
||||||
|
literal("F03493", "http://www.w3.org/2001/XMLSchema#hexBinary"), |
||||||
|
extraParams, |
||||||
|
), |
||||||
|
).toBe("F03493"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns a string for HTML", () => { |
||||||
|
expect( |
||||||
|
nodeToJsonldRepresentation( |
||||||
|
literal( |
||||||
|
"<body></body>", |
||||||
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#HTML", |
||||||
|
), |
||||||
|
extraParams, |
||||||
|
), |
||||||
|
).toBe("<body></body>"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns a string for anyUri", () => { |
||||||
|
expect( |
||||||
|
nodeToJsonldRepresentation( |
||||||
|
literal( |
||||||
|
"http://example.com", |
||||||
|
"http://www.w3.org/2001/XMLSchema#anyURI", |
||||||
|
), |
||||||
|
extraParams, |
||||||
|
), |
||||||
|
).toBe("http://example.com"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns a string for an unrecognized datatype", () => { |
||||||
|
expect( |
||||||
|
nodeToJsonldRepresentation( |
||||||
|
literal("meh", "http://weirddatatype.com"), |
||||||
|
extraParams, |
||||||
|
), |
||||||
|
).toBe("meh"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("throws an error when it encoutners a quad that is not a Liter, NamedNode, or BlankNode", () => { |
||||||
|
expect(() => |
||||||
|
// @ts-expect-error defaultGraph is not allowed
|
||||||
|
nodeToJsonldRepresentation(defaultGraph(), extraParams), |
||||||
|
).toThrow("Can only convert NamedNodes or Literals or BlankNodes"); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,15 @@ |
|||||||
|
import { blankNode, defaultGraph, literal, namedNode } from "@rdfjs/data-model"; |
||||||
|
import { nodeToString } from "../src"; |
||||||
|
|
||||||
|
describe("nodeToString", () => { |
||||||
|
it("returns all the correct values for nodeToString", () => { |
||||||
|
expect(nodeToString(namedNode("http://example.com"))).toBe( |
||||||
|
"namedNode(http://example.com)", |
||||||
|
); |
||||||
|
expect(nodeToString(blankNode("_b1"))).toBe("blankNode(_b1)"); |
||||||
|
expect(nodeToString(literal("Hello"))).toBe( |
||||||
|
"literal(Hello,http://www.w3.org/2001/XMLSchema#string)", |
||||||
|
); |
||||||
|
expect(nodeToString(defaultGraph())).toBe("defaultGraph()"); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,196 @@ |
|||||||
|
import type { ContextDefinition } from "jsonld"; |
||||||
|
import type { Schema } from "shexj"; |
||||||
|
|
||||||
|
export interface ObservationShape { |
||||||
|
"@id"?: string; |
||||||
|
"@context"?: ContextDefinition; |
||||||
|
subject?: PatientShape; |
||||||
|
notes?: string; |
||||||
|
langNotes?: string; |
||||||
|
} |
||||||
|
|
||||||
|
export type PatientShape = { |
||||||
|
"@id"?: string; |
||||||
|
"@context"?: ContextDefinition; |
||||||
|
type?: { "@id": "Patient" }; |
||||||
|
name?: string[]; |
||||||
|
langName?: string[]; |
||||||
|
birthdate?: string; |
||||||
|
age?: number; |
||||||
|
isHappy?: boolean; |
||||||
|
roommate?: PatientShape[]; |
||||||
|
}; |
||||||
|
|
||||||
|
// No need to fully define the schema because this library doesn't use it
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
export const patientSchema: Schema = {}; |
||||||
|
|
||||||
|
export const patientContext: ContextDefinition = { |
||||||
|
type: { |
||||||
|
"@id": "@type", |
||||||
|
}, |
||||||
|
Patient: "http://hl7.org/fhir/Patient", |
||||||
|
subject: { "@id": "http://hl7.org/fhir/subject", "@type": "@id" }, |
||||||
|
name: { |
||||||
|
"@id": "http://hl7.org/fhir/name", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
"@container": "@set", |
||||||
|
}, |
||||||
|
langName: { |
||||||
|
"@id": "http://hl7.org/fhir/langName", |
||||||
|
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString", |
||||||
|
"@container": "@set", |
||||||
|
}, |
||||||
|
birthdate: { |
||||||
|
"@id": "http://hl7.org/fhir/birthdate", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#date", |
||||||
|
}, |
||||||
|
age: { |
||||||
|
"@id": "http://hl7.org/fhir/age", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#integer", |
||||||
|
}, |
||||||
|
isHappy: { |
||||||
|
"@id": "http://hl7.org/fhir/isHappy", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#boolean", |
||||||
|
}, |
||||||
|
roommate: { |
||||||
|
"@id": "http://hl7.org/fhir/roommate", |
||||||
|
"@type": "@id", |
||||||
|
"@container": "@set", |
||||||
|
}, |
||||||
|
notes: { |
||||||
|
"@id": "http://hl7.org/fhir/notes", |
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#string", |
||||||
|
}, |
||||||
|
langNotes: { |
||||||
|
"@id": "http://hl7.org/fhir/langNotes", |
||||||
|
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString", |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
export const patientData = ` |
||||||
|
@prefix example: <http://example.com/> . |
||||||
|
@prefix fhir: <http://hl7.org/fhir/> . |
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . |
||||||
|
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . |
||||||
|
|
||||||
|
example:Observation1 |
||||||
|
fhir:notes "Cool Notes"^^xsd:string ; |
||||||
|
fhir:subject example:Patient1 . |
||||||
|
|
||||||
|
example:Patient1 |
||||||
|
rdf:type fhir:Patient ;
|
||||||
|
fhir:name "Garrett"^^xsd:string, "Bobby"^^xsd:string, "Ferguson"^^xsd:string ; |
||||||
|
fhir:birthdate "1986-01-01"^^xsd:date ; |
||||||
|
fhir:age "35"^^xsd:integer ; |
||||||
|
fhir:isHappy "true"^^xsd:boolean ; |
||||||
|
fhir:roommate example:Patient2, example:Patient3 . |
||||||
|
|
||||||
|
example:Patient2 |
||||||
|
rdf:type fhir:Patient ;
|
||||||
|
fhir:name "Rob"^^xsd:string ; |
||||||
|
fhir:birthdate "1987-01-01"^^xsd:date ; |
||||||
|
fhir:age "34"^^xsd:integer ; |
||||||
|
fhir:isHappy "false"^^xsd:boolean ; |
||||||
|
fhir:roommate example:Patient1, example:Patient3 . |
||||||
|
|
||||||
|
example:Patient3 |
||||||
|
rdf:type fhir:Patient ;
|
||||||
|
fhir:name "Amy"^^xsd:string ; |
||||||
|
fhir:birthdate "1988-01-01"^^xsd:date ; |
||||||
|
fhir:age "33"^^xsd:integer ; |
||||||
|
fhir:isHappy "true"^^xsd:boolean . |
||||||
|
`;
|
||||||
|
|
||||||
|
export const patientDataWithBlankNodes = ` |
||||||
|
@prefix example: <http://example.com/> . |
||||||
|
@prefix fhir: <http://hl7.org/fhir/> . |
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . |
||||||
|
|
||||||
|
example:Observation1 |
||||||
|
fhir:notes "Cool Notes"^^xsd:string ; |
||||||
|
fhir:subject _:Patient1 . |
||||||
|
|
||||||
|
_:Patient1 |
||||||
|
fhir:name "Garrett"^^xsd:string, "Bobby"^^xsd:string, "Ferguson"^^xsd:string ; |
||||||
|
fhir:birthdate "1986-01-01"^^xsd:date ; |
||||||
|
fhir:age "35"^^xsd:integer ; |
||||||
|
fhir:isHappy "true"^^xsd:boolean ; |
||||||
|
fhir:roommate _:Patient2, _:Patient3 . |
||||||
|
|
||||||
|
_:Patient2 |
||||||
|
fhir:name "Rob"^^xsd:string ; |
||||||
|
fhir:birthdate "1987-01-01"^^xsd:date ; |
||||||
|
fhir:age "34"^^xsd:integer ; |
||||||
|
fhir:isHappy "false"^^xsd:boolean ; |
||||||
|
fhir:roommate _:Patient1, _:Patient3 . |
||||||
|
|
||||||
|
_:Patient3 |
||||||
|
fhir:name "Amy"^^xsd:string ; |
||||||
|
fhir:birthdate "1988-01-01"^^xsd:date ; |
||||||
|
fhir:age "33"^^xsd:integer ; |
||||||
|
fhir:isHappy "true"^^xsd:boolean . |
||||||
|
`;
|
||||||
|
|
||||||
|
export const tinyPatientData = ` |
||||||
|
@prefix example: <http://example.com/> . |
||||||
|
@prefix fhir: <http://hl7.org/fhir/> . |
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . |
||||||
|
|
||||||
|
example:Observation1 |
||||||
|
fhir:subject example:Patient1 . |
||||||
|
|
||||||
|
example:Patient1 |
||||||
|
fhir:name "Garrett"^^xsd:string ; |
||||||
|
fhir:roommate example:Patient2 . |
||||||
|
|
||||||
|
example:Patient2 |
||||||
|
fhir:name "Rob"^^xsd:string ; |
||||||
|
fhir:roommate example:Patient1 . |
||||||
|
`;
|
||||||
|
|
||||||
|
export const tinyArrayPatientData = ` |
||||||
|
@prefix example: <http://example.com/> . |
||||||
|
@prefix fhir: <http://hl7.org/fhir/> . |
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . |
||||||
|
|
||||||
|
example:Patient1 |
||||||
|
fhir:name "Garrett"^^xsd:string, "Bobby"^^xsd:string, "Ferguson"^^xsd:string . |
||||||
|
`;
|
||||||
|
|
||||||
|
export const tinyPatientDataWithBlankNodes = ` |
||||||
|
@prefix example: <http://example.com/> . |
||||||
|
@prefix fhir: <http://hl7.org/fhir/> . |
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . |
||||||
|
|
||||||
|
example:Observation1 |
||||||
|
fhir:subject _:Patient1 . |
||||||
|
|
||||||
|
_:Patient1 |
||||||
|
fhir:name "Garrett"^^xsd:string ; |
||||||
|
fhir:roommate _:Patient2 . |
||||||
|
|
||||||
|
_:Patient2 |
||||||
|
fhir:name "Rob"^^xsd:string ; |
||||||
|
fhir:roommate _:Patient1 . |
||||||
|
`;
|
||||||
|
|
||||||
|
export const tinyPatientDataWithLanguageTags = ` |
||||||
|
@prefix example: <http://example.com/> . |
||||||
|
@prefix fhir: <http://hl7.org/fhir/> . |
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . |
||||||
|
|
||||||
|
example:Observation1 |
||||||
|
fhir:subject example:Patient1 ; |
||||||
|
fhir:langNotes "Cool Notes" ; |
||||||
|
fhir:langNotes "Cooler Notes"@en ; |
||||||
|
fhir:langNotes "Notas Geniales"@es ; |
||||||
|
fhir:langNotes "Notes Sympas"@fr . |
||||||
|
|
||||||
|
example:Patient1 |
||||||
|
fhir:langName "Jon" ; |
||||||
|
fhir:langName "John"@en ; |
||||||
|
fhir:langName "Juan"@es ; |
||||||
|
fhir:langName "Jean"@fr . |
||||||
|
`;
|
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../tsconfig.base.json", |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "./dist" |
||||||
|
}, |
||||||
|
"include": ["./src"] |
||||||
|
} |
@ -1,5 +1,6 @@ |
|||||||
const sharedConfig = require('../../jest.config.js'); |
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const sharedConfig = require("../../jest.config.js"); |
||||||
module.exports = { |
module.exports = { |
||||||
...sharedConfig, |
...sharedConfig, |
||||||
'rootDir': './', |
rootDir: "./", |
||||||
} |
}; |
||||||
|
@ -1,5 +1,6 @@ |
|||||||
const sharedConfig = require('../../jest.config.js'); |
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const sharedConfig = require("../../jest.config.js"); |
||||||
module.exports = { |
module.exports = { |
||||||
...sharedConfig, |
...sharedConfig, |
||||||
'rootDir': './', |
rootDir: "./", |
||||||
} |
}; |
||||||
|
Loading…
Reference in new issue