Merge pull request #62 from o-development/feat/graph-type-traverser

Multi-caridinality between types
main
jaxoncreed 9 months ago committed by GitHub
commit 0a8e6be9b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 32178
      package-lock.json
  2. 153
      packages/jsonld-dataset-proxy/src/ContextUtil.ts
  3. 14
      packages/jsonld-dataset-proxy/src/LdoJsonldContext.ts
  4. 13
      packages/jsonld-dataset-proxy/src/ProxyContext.ts
  5. 12
      packages/jsonld-dataset-proxy/src/arrayProxy/modifyArray.ts
  6. 5
      packages/jsonld-dataset-proxy/src/graphOf.ts
  7. 1
      packages/jsonld-dataset-proxy/src/index.ts
  8. 3
      packages/jsonld-dataset-proxy/src/jsonldDatasetProxy.ts
  9. 7
      packages/jsonld-dataset-proxy/src/language/languagesOf.ts
  10. 7
      packages/jsonld-dataset-proxy/src/subjectProxy/createSubjectHandler.ts
  11. 4
      packages/jsonld-dataset-proxy/src/subjectProxy/deleteFromDataset.ts
  12. 13
      packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts
  13. 14
      packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts
  14. 8
      packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts
  15. 39
      packages/jsonld-dataset-proxy/test/ContextUtil.test.ts
  16. 188
      packages/jsonld-dataset-proxy/test/jsonldDatasetProxy.test.ts
  17. 93
      packages/jsonld-dataset-proxy/test/patientExampleData.ts
  18. 119
      packages/jsonld-dataset-proxy/test/scopedExampleData.ts
  19. 3
      packages/schema-converter-shex/package.json
  20. 76
      packages/schema-converter-shex/src/context/JsonLdContextBuilder.ts
  21. 91
      packages/schema-converter-shex/src/context/ShexJContextVisitor.ts
  22. 1
      packages/schema-converter-shex/src/context/shexjToContext.ts
  23. 80
      packages/schema-converter-shex/src/typing/ShexJTypingTransformer.ts
  24. 2
      packages/schema-converter-shex/src/typing/shexjToTyping.ts
  25. 44
      packages/schema-converter-shex/src/typing/util/dedupeObjectTypeMembers.ts
  26. 110
      packages/schema-converter-shex/src/util/getRdfTypesForTripleConstraint.ts
  27. 8839
      packages/schema-converter-shex/test/testData/activityPub.ts
  28. 21
      packages/schema-converter-shex/test/testData/circular.ts
  29. 38
      packages/schema-converter-shex/test/testData/extendsSimple.ts
  30. 46
      packages/schema-converter-shex/test/testData/oldExtends.ts
  31. 404
      packages/schema-converter-shex/test/testData/profile.ts
  32. 188
      packages/schema-converter-shex/test/testData/reducedProfile.ts
  33. 76
      packages/schema-converter-shex/test/testData/reusedPredicates.ts
  34. 6
      packages/schema-converter-shex/test/testData/simple.ts
  35. 8
      packages/schema-converter-shex/test/testData/testData.ts
  36. 35
      packages/schema-converter-shex/test/testData/testIfLegal.ts
  37. 5
      packages/solid-react/src/util/TrackingProxyContext.ts
  38. 1
      packages/traverser-shexj/package.json
  39. 13
      packages/traverser-shexj/src/ShexJTraverserDefinition.ts
  40. 4
      packages/traverser-shexj/src/ShexJTraverserTypes.ts
  41. 602
      packages/traverser-shexj/src/ShexJTypes.ts
  42. 10
      packages/type-traverser/README.md
  43. 174
      packages/type-traverser/example/example.ts
  44. 126
      packages/type-traverser/src/Visitors.ts
  45. 25
      packages/type-traverser/src/index.ts
  46. 47
      packages/type-traverser/src/instanceGraph/InstanceGraph.ts
  47. 82
      packages/type-traverser/src/instanceGraph/ReverseRelationshipTypes.ts
  48. 77
      packages/type-traverser/src/instanceGraph/nodes/InstanceNode.ts
  49. 100
      packages/type-traverser/src/instanceGraph/nodes/InterfaceInstanceNode.ts
  50. 25
      packages/type-traverser/src/instanceGraph/nodes/PrimitiveInstanceNode.ts
  51. 34
      packages/type-traverser/src/instanceGraph/nodes/UnionInstanceNode.ts
  52. 54
      packages/type-traverser/src/instanceGraph/nodes/createInstanceNodeFor.ts
  53. 47
      packages/type-traverser/src/transformer/Transformer.ts
  54. 2
      packages/type-traverser/src/transformer/TransformerReturnTypes.ts
  55. 2
      packages/type-traverser/src/transformer/TransformerReturnTypesDefaults.ts
  56. 90
      packages/type-traverser/src/transformer/Transformers.ts
  57. 22
      packages/type-traverser/src/transformer/transformerSubTraversers/TransformerInterfaceSubTraverser.ts
  58. 6
      packages/type-traverser/src/transformer/transformerSubTraversers/TransformerParentSubTraverser.ts
  59. 24
      packages/type-traverser/src/transformer/transformerSubTraversers/TransformerPrimitiveSubTraverser.ts
  60. 14
      packages/type-traverser/src/transformer/transformerSubTraversers/TransformerUnionSubTraverser.ts
  61. 2
      packages/type-traverser/src/transformer/transformerSubTraversers/util/CircularDependencyAwaiter.ts
  62. 4
      packages/type-traverser/src/transformer/transformerSubTraversers/util/MultiMap.ts
  63. 4
      packages/type-traverser/src/transformer/transformerSubTraversers/util/MultiSet.ts
  64. 0
      packages/type-traverser/src/transformer/transformerSubTraversers/util/SuperPromise.ts
  65. 0
      packages/type-traverser/src/transformer/transformerSubTraversers/util/timeout.ts
  66. 8
      packages/type-traverser/src/transformer/transformerSubTraversers/util/transformerSubTraverserTypes.ts
  67. 12
      packages/type-traverser/src/traverser/Traverser.ts
  68. 28
      packages/type-traverser/src/traverser/TraverserDefinition.ts
  69. 2
      packages/type-traverser/src/traverser/TraverserTypes.ts
  70. 0
      packages/type-traverser/src/traverser/traverserGraph/TraverserGraph.ts
  71. 54
      packages/type-traverser/src/visitor/Visitor.ts
  72. 157
      packages/type-traverser/src/visitor/Visitors.ts
  73. 24
      packages/type-traverser/src/visitor/visitorSubTraversers/VisitorInterfaceSubTraverser.ts
  74. 2
      packages/type-traverser/src/visitor/visitorSubTraversers/VisitorParentSubTraverser.ts
  75. 18
      packages/type-traverser/src/visitor/visitorSubTraversers/VisitorPrimitiveSubTraverser.ts
  76. 19
      packages/type-traverser/src/visitor/visitorSubTraversers/VisitorUnionSubTraverser.ts
  77. 10
      packages/type-traverser/src/visitor/visitorSubTraversers/util/visitorSubTraverserTypes.ts
  78. 156
      packages/type-traverser/test/integration/InstanceGraph.test.ts
  79. 4
      packages/type-traverser/test/integration/avatar/AvatarTraverserDefinition.ts
  80. 2
      packages/type-traverser/tsconfig.build.json
  81. 1
      tsconfig.base.json

32178
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,4 +1,9 @@
import type { ContextDefinition, ExpandedTermDefinition } from "jsonld";
import type {
LdoJsonldContext,
LdoJsonldContextExpandedTermDefinition,
} from "./LdoJsonldContext";
import type { NamedNode } from "@rdfjs/types";
// Create JSONLD Shorthands
const shorthandToIriMap: Record<string, string> = {
@ -10,40 +15,74 @@ const shorthandToIriMap: Record<string, string> = {
* Handles the JSON-LD context and allows conversion between IRIs and terms
*/
export class ContextUtil {
public readonly context: ContextDefinition;
public readonly context: ContextDefinition | LdoJsonldContext;
private iriToKeyMap: Record<string, string>;
private typeNameToIriToKeyMap: Record<string, Record<string, string>>;
constructor(context: ContextDefinition) {
constructor(context: ContextDefinition | LdoJsonldContext) {
this.context = context;
this.iriToKeyMap = {};
// Create the iriToKeyMap
this.iriToKeyMap = this.createIriToKeyMap(context);
this.typeNameToIriToKeyMap = {};
Object.entries(context).forEach(([contextKey, contextValue]) => {
if (
typeof contextValue === "object" &&
contextValue !== null &&
!!contextValue["@id"] &&
(contextValue as ExpandedTermDefinition)["@context"]
) {
this.typeNameToIriToKeyMap[contextKey] = this.createIriToKeyMap(
contextValue["@context"],
);
}
});
}
private createIriToKeyMap(
context: ContextDefinition,
): Record<string, string> {
const iriToKeyMap = {};
Object.entries(context).forEach(([contextKey, contextValue]) => {
if (typeof contextValue === "string") {
this.iriToKeyMap[this.keyIdToIri(contextValue)] = contextKey;
iriToKeyMap[this.keyIdToIri(contextValue)] = contextKey;
} else if (
typeof contextValue === "object" &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(contextValue as any)["@id"]
contextValue !== null &&
!!contextValue["@id"]
) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.iriToKeyMap[this.keyIdToIri((contextValue as any)["@id"])] =
contextKey;
const iri = this.keyIdToIri(contextValue["@id"]);
iriToKeyMap[iri] = contextKey;
}
});
return iriToKeyMap;
}
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"]);
/**
* Helper method that gets the relevant context to use if a typename is
* provided
*/
private getRelevantContext(
key: string,
typeNames: NamedNode[],
): ContextDefinition | LdoJsonldContext {
for (const typeNameNode of typeNames) {
const typeName = this.iriToKey((typeNameNode as NamedNode).value, []);
if (
typeof this.context[typeName] === "object" &&
this.context[typeName]?.["@context"] &&
this.context[typeName]?.["@context"][key]
) {
return this.context[typeName]?.["@context"];
}
}
return key;
return this.context;
}
/**
* Helper function that applies shorthands to keys
*/
private keyIdToIri(keyId: string) {
if (shorthandToIriMap[keyId]) {
return shorthandToIriMap[keyId];
@ -52,38 +91,82 @@ export class ContextUtil {
}
}
public iriToKey(iri: string): string {
if (this.iriToKeyMap[iri]) {
return this.iriToKeyMap[iri];
/**
* Converts a given JsonLd key into an RDF IRI
*/
public keyToIri(key: string, typeName: NamedNode[]): string {
const relevantContext = this.getRelevantContext(key, typeName);
if (!relevantContext[key]) {
return key;
} else if (typeof relevantContext[key] === "string") {
return this.keyIdToIri(relevantContext[key] as string);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} else if (relevantContext[key] && (relevantContext[key] as any)["@id"]) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return this.keyIdToIri((relevantContext[key] as any)["@id"]);
}
return key;
}
/**
* Converts a given RDF IRI into the JsonLd key
*/
public iriToKey(iri: string, typeNames: NamedNode[]): string {
let relevantMap = this.iriToKeyMap;
for (const typeNameNode of typeNames) {
const typeName = this.iriToKey((typeNameNode as NamedNode).value, []);
relevantMap = this.typeNameToIriToKeyMap[typeName]?.[iri]
? this.typeNameToIriToKeyMap[typeName]
: this.iriToKeyMap;
}
if (relevantMap[iri]) {
return relevantMap[iri];
}
return iri;
}
public getType(key: string): string {
/**
* Returns the IRI of a datatype of a specific object
*/
public getDataType(key: string, typeName: NamedNode[]): string {
const relevantContext = this.getRelevantContext(key, typeName);
if (
typeof this.context[key] === "object" &&
(this.context[key] as ExpandedTermDefinition)["@type"]
typeof relevantContext[key] === "object" &&
(relevantContext[key] as ExpandedTermDefinition)["@type"]
) {
return (this.context[key] as ExpandedTermDefinition)["@type"] as string;
return (relevantContext[key] as ExpandedTermDefinition)[
"@type"
] as string;
}
return "http://www.w3.org/2001/XMLSchema#string";
}
public isArray(key: string): boolean {
/**
* Returns true if the object is a collection
*/
public isArray(key: string, typeName: NamedNode[]): boolean {
const relevantContext = this.getRelevantContext(key, typeName);
return !!(
this.context[key] &&
typeof this.context[key] === "object" &&
(this.context[key] as ExpandedTermDefinition)["@container"] &&
(this.context[key] as ExpandedTermDefinition)["@container"] === "@set"
relevantContext[key] &&
typeof relevantContext[key] === "object" &&
((relevantContext[key] as ExpandedTermDefinition)["@container"] ===
"@set" ||
(relevantContext[key] as LdoJsonldContextExpandedTermDefinition)[
"@isCollection"
])
);
}
public isLangString(key: string): boolean {
/**
* Returns true if the object is a language string
*/
public isLangString(key: string, typeName: NamedNode[]): boolean {
const relevantContext = this.getRelevantContext(key, typeName);
return !!(
this.context[key] &&
typeof this.context[key] === "object" &&
(this.context[key] as ExpandedTermDefinition)["@type"] &&
(this.context[key] as ExpandedTermDefinition)["@type"] ===
relevantContext[key] &&
typeof relevantContext[key] === "object" &&
(relevantContext[key] as ExpandedTermDefinition)["@type"] &&
(relevantContext[key] as ExpandedTermDefinition)["@type"] ===
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString"
);
}

@ -0,0 +1,14 @@
import type { ContextDefinition, ExpandedTermDefinition } from "jsonld";
export interface LdoJsonldContext extends ContextDefinition {
[key: string]:
| null
| string
| LdoJsonldContextExpandedTermDefinition
| LdoJsonldContext[keyof LdoJsonldContext];
}
export type LdoJsonldContextExpandedTermDefinition = ExpandedTermDefinition & {
"@context"?: LdoJsonldContext | undefined;
"@isCollection"?: boolean | undefined;
};

@ -1,4 +1,4 @@
import type { GraphNode, QuadMatch } from "@ldo/rdf-utils";
import type { GraphNode, QuadMatch, SubjectNode } from "@ldo/rdf-utils";
import type { BlankNode, Dataset, NamedNode } from "@rdfjs/types";
import type { ArrayProxyTarget } from "./arrayProxy/createArrayHandler";
import { createArrayHandler } from "./arrayProxy/createArrayHandler";
@ -8,6 +8,7 @@ import type { ArrayProxy } from "./arrayProxy/ArrayProxy";
import { _getUnderlyingArrayTarget } from "./types";
import type { ContextUtil } from "./ContextUtil";
import type { LanguageOrdering } from "./language/languageTypes";
import { namedNode } from "@rdfjs/data-model";
export interface ProxyContextOptions {
dataset: Dataset;
@ -18,6 +19,8 @@ export interface ProxyContextOptions {
state?: Record<string, unknown>;
}
const rdfType = namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
/**
* This file keeps track of the target objects used in the proxies.
* The reason is so that JSON.stringify does not recurse inifinitely
@ -107,4 +110,12 @@ export class ProxyContext {
};
return new ProxyContext(fullOptions);
}
public getRdfType(subjectNode: SubjectNode): NamedNode[] {
return this.dataset
.match(subjectNode, rdfType)
.toArray()
.map((quad) => quad.object)
.filter((object): object is NamedNode => object.termType === "NamedNode");
}
}

@ -114,7 +114,10 @@ export function modifyArray<ReturnType>(
addObjectToDataset(
{
"@id": target[0][0],
[contextUtil.iriToKey(target[0][1].value)]: added,
[contextUtil.iriToKey(
target[0][1].value,
proxyContext.getRdfType(target[0][0]),
)]: added,
} as RawObject,
false,
proxyContext,
@ -123,7 +126,12 @@ export function modifyArray<ReturnType>(
const addedNodes = added
? (added
.map((addedValue) => {
return getNodeFromRawValue(key, addedValue, proxyContext);
return getNodeFromRawValue(
key,
addedValue,
target[0][0] ? proxyContext.getRdfType(target[0][0]) : [],
proxyContext,
);
})
.filter((val) => val != undefined) as ObjectNode[])
: [];

@ -31,7 +31,10 @@ export function graphOf<Subject extends ObjectLike, Key extends keyof Subject>(
const proxyContext = subjectProxy[_proxyContext];
const subjectNode = subjectProxy[_getUnderlyingNode];
const predicateNode = namedNode(
proxyContext.contextUtil.keyToIri(predicate as string),
proxyContext.contextUtil.keyToIri(
predicate as string,
proxyContext.getRdfType(subjectNode),
),
);
let objectNode: ObjectNode | null;
if (object == null) {

@ -9,6 +9,7 @@ export * from "./jsonldDatasetProxy";
export * from "./write";
export * from "./graphOf";
export * from "./setLanguagePreferences";
export * from "./LdoJsonldContext";
export * from "./language/languagesOf";
export * from "./language/languageMapProxy";

@ -4,6 +4,7 @@ import type { ContextDefinition } from "jsonld";
import { ContextUtil } from "./ContextUtil";
import { JsonldDatasetProxyBuilder } from "./JsonldDatasetProxyBuilder";
import { ProxyContext } from "./ProxyContext";
import type { LdoJsonldContext } from "./LdoJsonldContext";
/**
* Creates a JSON-LD Dataset Proxy
@ -14,7 +15,7 @@ import { ProxyContext } from "./ProxyContext";
*/
export function jsonldDatasetProxy(
inputDataset: Dataset,
context: ContextDefinition,
context: ContextDefinition | LdoJsonldContext,
): JsonldDatasetProxyBuilder {
const contextUtil = new ContextUtil(context);
const proxyContext = new ProxyContext({

@ -51,11 +51,14 @@ export function languagesOf<
const proxy = getSubjectProxyFromObject(subjectObject);
const proxyContext = proxy[_proxyContext];
const subject = proxy[_getUnderlyingNode];
const predicate = namedNode(proxyContext.contextUtil.keyToIri(key as string));
const rdfTypes = proxyContext.getRdfType(subject);
const predicate = namedNode(
proxyContext.contextUtil.keyToIri(key as string, rdfTypes),
);
return createLanguageMapProxy<LanguageMap>(
subject,
predicate,
proxyContext,
proxyContext.contextUtil.isArray(key as string),
proxyContext.contextUtil.isArray(key as string, rdfTypes),
) as LanguageOfConditionalReturn<SubjectObject, Key>;
}

@ -48,7 +48,12 @@ export function createSubjectHandler(
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));
keys.add(
proxyContext.contextUtil.iriToKey(
quad.predicate.value,
proxyContext.getRdfType(subject),
),
);
});
return Array.from(keys);
},

@ -19,7 +19,9 @@ export function deleteValueFromDataset(
return true;
}
const subject = target["@id"];
const predicate = namedNode(proxyContext.contextUtil.keyToIri(key));
const predicate = namedNode(
proxyContext.contextUtil.keyToIri(key, proxyContext.getRdfType(subject)),
);
if (key === "@id") {
nodesToRemove.push(target["@id"]);
} else {

@ -19,7 +19,9 @@ export function getValueForKey(
if (target["@id"].termType === "BlankNode") {
return undefined;
}
return contextUtil.iriToKey(target["@id"].value);
// Purposly don't provide a typeName because we don't want to use the nested
// context
return contextUtil.iriToKey(target["@id"].value, []);
}
if (key === "toString" || key === Symbol.toStringTag) {
// TODO: this toString method right now returns [object Object],
@ -31,18 +33,19 @@ export function getValueForKey(
return;
}
const subject = target["@id"];
const predicate = namedNode(contextUtil.keyToIri(key));
if (contextUtil.isArray(key)) {
const rdfType = proxyContext.getRdfType(subject);
const predicate = namedNode(contextUtil.keyToIri(key, rdfType));
if (contextUtil.isArray(key, rdfType)) {
const arrayProxy = proxyContext.createArrayProxy(
[subject, predicate, null, null],
false,
undefined,
contextUtil.isLangString(key),
contextUtil.isLangString(key, rdfType),
);
return arrayProxy;
}
let objectDataset = dataset.match(subject, predicate);
if (contextUtil.isLangString(key)) {
if (contextUtil.isLangString(key, rdfType)) {
objectDataset = filterQuadsByLanguageOrdering(
objectDataset,
proxyContext.languageOrdering,

@ -22,15 +22,16 @@ export function addRawValueToDatasetRecursive(
proxyContext: ProxyContext,
): void {
const { dataset, contextUtil } = proxyContext;
const predicate = namedNode(contextUtil.keyToIri(key));
const rdfType = proxyContext.getRdfType(subject);
const predicate = namedNode(contextUtil.keyToIri(key, rdfType));
// Get the Object Node
const object = getNodeFromRawValue(key, value, proxyContext);
const object = getNodeFromRawValue(key, value, rdfType, proxyContext);
if (object == undefined) {
dataset.deleteMatches(subject, predicate);
} else if (object.termType === "Literal") {
let languageAppliedObject = object;
// Handle language use case
if (contextUtil.isLangString(key)) {
if (contextUtil.isLangString(key, rdfType)) {
const languageKey = getLanguageKeyForWriteOperation(
proxyContext.languageOrdering,
);
@ -81,6 +82,7 @@ export function addRawObjectToDatasetRecursive(
}
const { dataset } = proxyContext;
const subject = getNodeFromRawObject(item, proxyContext.contextUtil);
const rdfType = proxyContext.getRdfType(subject);
if (visitedObjects.has(subject)) {
return proxyContext.createSubjectProxy(subject);
}
@ -89,9 +91,11 @@ export function addRawObjectToDatasetRecursive(
if (key === "@id") {
return;
}
const predicate = namedNode(proxyContext.contextUtil.keyToIri(key));
const predicate = namedNode(
proxyContext.contextUtil.keyToIri(key, rdfType),
);
if (shouldDeleteOldTriples) {
if (proxyContext.contextUtil.isLangString(key)) {
if (proxyContext.contextUtil.isLangString(key, rdfType)) {
const languageKey = getLanguageKeyForWriteOperation(
proxyContext.languageOrdering,
);

@ -14,7 +14,9 @@ export function getNodeFromRawObject(
} else if (!item["@id"]) {
return blankNode();
} else if (typeof item["@id"] === "string") {
return namedNode(contextUtil.keyToIri(item["@id"]));
// Purposly do not include typeName because we don't want to reference
// nested context
return namedNode(contextUtil.keyToIri(item["@id"], []));
} else {
return item["@id"];
}
@ -23,6 +25,7 @@ export function getNodeFromRawObject(
export function getNodeFromRawValue(
key: string,
value: RawValue,
rdfTypes: NamedNode[],
proxyContext: ProxyContext,
): BlankNode | NamedNode | Literal | undefined {
// Get the Object Node
@ -33,7 +36,8 @@ export function getNodeFromRawValue(
typeof value === "boolean" ||
typeof value === "number"
) {
const datatype = proxyContext.contextUtil.getType(key);
// PICKUP: figure out how to handle looking for the RDF Types of a raw value
const datatype = proxyContext.contextUtil.getDataType(key, rdfTypes);
if (datatype === "@id") {
return namedNode(value.toString());
} else {

@ -1,4 +1,6 @@
import { namedNode } from "@rdfjs/data-model";
import { ContextUtil } from "../src/ContextUtil";
import { scopedContext } from "./scopedExampleData";
describe("ContextUtil", () => {
describe("keyToIri and iriToKey", () => {
@ -7,22 +9,36 @@ describe("ContextUtil", () => {
name: "http://hl7.org/fhir/name",
};
const contextUtil = new ContextUtil(fakeContext);
expect(contextUtil.keyToIri("name")).toBe("http://hl7.org/fhir/name");
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(
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", () => {
it("handles a context that exists, 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");
expect(contextUtil.keyToIri("name", [])).toBe("name");
});
it("handles a nested context", () => {
const contextUtil = new ContextUtil(scopedContext);
expect(
contextUtil.keyToIri("element", [
namedNode("http://example.com/Bender"),
]),
).toBe("http://example.com/element");
expect(
contextUtil.iriToKey("http://example.com/element", [
namedNode("http://example.com/Bender"),
]),
).toBe("element");
});
});
@ -31,9 +47,20 @@ describe("ContextUtil", () => {
const contextUtil = new ContextUtil({
name: { "@id": "http://hl7.org/fhir/name" },
});
expect(contextUtil.getType("name")).toBe(
expect(contextUtil.getDataType("name", [])).toBe(
"http://www.w3.org/2001/XMLSchema#string",
);
});
});
describe("isArray", () => {
it("indicates that the special @isCollection field means array", () => {
const contextUtil = new ContextUtil(scopedContext);
expect(
contextUtil.isArray("element", [
namedNode("http://example.com/Avatar"),
]),
).toBe(true);
});
});
});

@ -18,18 +18,26 @@ import {
import type { ObservationShape, PatientShape } from "./patientExampleData";
import {
patientData,
patientContext,
tinyPatientData,
tinyArrayPatientData,
patientDataWithBlankNodes,
tinyPatientDataWithBlankNodes,
tinyPatientDataWithLanguageTags,
patientUnnestedContext,
patientNestedContext,
} from "./patientExampleData";
import { namedNode, quad, literal, defaultGraph } from "@rdfjs/data-model";
import type { Dataset, NamedNode } from "@rdfjs/types";
import type { ContextDefinition } from "jsonld";
import type { LdoJsonldContext } from "../src/LdoJsonldContext";
import {
scopedContext,
scopedData,
type Avatar,
type Bender,
} from "./scopedExampleData";
describe("jsonldDatasetProxy", () => {
const testJsonldDatasetProxy = (patientContext: LdoJsonldContext) => () => {
async function getLoadedDataset(): Promise<
[Dataset, ObservationShape, JsonldDatasetProxyBuilder]
> {
@ -163,6 +171,19 @@ describe("jsonldDatasetProxy", () => {
];
}
async function getScopedDataset(): Promise<
[Dataset, Bender, Avatar, JsonldDatasetProxyBuilder]
> {
const dataset = await serializedToDataset(scopedData);
const builder = await jsonldDatasetProxy(dataset, scopedContext);
return [
dataset,
builder.fromSubject(namedNode("http://example.com/Katara")),
builder.fromSubject(namedNode("http://example.com/Aang")),
builder,
];
}
describe("read", () => {
it("retreives a primitive", async () => {
const [, observation] = await getLoadedDataset();
@ -407,23 +428,32 @@ describe("jsonldDatasetProxy", () => {
const [, observation] = await getLoadedDataset();
expect(observation["@context"]).toEqual(patientContext);
});
it("reads an array for collections, but a var for non collections", async () => {
const [, bender, avatar] = await getScopedDataset();
expect(avatar.element[0]["@id"]).toBe("http://example.com/Air");
expect(avatar.element[1]["@id"]).toBe("http://example.com/Water");
expect(bender.element["@id"]).toBe("http://example.com/Water");
});
});
describe("write", () => {
it("sets a primitive value that doesn't exist yet", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
observation.type = { "@id": "Observation" };
observation.notes = "Cool Notes";
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/notes> "Cool Notes" .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/notes> "Cool Notes" .\n',
);
});
it("sets primitive number and boolean values", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.age = 35;
patient.isHappy = true;
expect(dataset.toString()).toBe(
'<http://example.com/Patient1> <http://hl7.org/fhir/age> "35"^^<http://www.w3.org/2001/XMLSchema#integer> .\n<http://example.com/Patient1> <http://hl7.org/fhir/isHappy> "true"^^<http://www.w3.org/2001/XMLSchema#boolean> .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/age> "35"^^<http://www.w3.org/2001/XMLSchema#integer> .\n<http://example.com/Patient1> <http://hl7.org/fhir/isHappy> "true"^^<http://www.w3.org/2001/XMLSchema#boolean> .\n',
);
});
@ -437,16 +467,21 @@ describe("jsonldDatasetProxy", () => {
it("replaces a primitive value that currently exists", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
dataset.add(
dataset.addAll([
quad(
namedNode("http://example.com/Observation1"),
namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
namedNode("http://hl7.org/fhir/Observation"),
),
quad(
namedNode("http://example.com/Observation1"),
namedNode("http://hl7.org/fhir/notes"),
literal("Cool Notes"),
),
);
]);
observation.notes = "Lame Notes";
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/notes> "Lame Notes" .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/notes> "Lame Notes" .\n',
);
});
@ -454,11 +489,13 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getEmptyObservationDataset();
const patient: PatientShape = {
"@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
birthdate: "2001-01-01",
};
observation.type = { "@id": "Observation" };
observation.subject = patient;
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://hl7.org/fhir/birthdate> "2001-01-01"^^<http://www.w3.org/2001/XMLSchema#date> .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/birthdate> "2001-01-01"^^<http://www.w3.org/2001/XMLSchema#date> .\n',
);
});
@ -477,13 +514,14 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getTinyLoadedDataset();
observation.subject = undefined;
expect(dataset.toString()).toBe(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
);
});
it("Creates a blank node if the id is blank during set", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
observation.subject = { name: ["Joe"] };
observation.type = { "@id": "Observation" };
observation.subject = { type: { "@id": "Patient" }, name: ["Joe"] };
expect(observation.subject?.["@id"]).toBeUndefined();
expect(observation.subject.name).toEqual(["Joe"]);
expect(
@ -500,12 +538,14 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getEmptyObservationDataset();
const patient: PatientShape = {
"@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
birthdate: "2001-01-01",
name: ["Jon", "Bon", "Jovi"],
};
observation.type = { "@id": "Observation" };
observation.subject = patient;
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://hl7.org/fhir/birthdate> "2001-01-01"^^<http://www.w3.org/2001/XMLSchema#date> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Jon" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bon" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Jovi" .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/birthdate> "2001-01-01"^^<http://www.w3.org/2001/XMLSchema#date> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Jon" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bon" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Jovi" .\n',
);
});
@ -513,41 +553,47 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getEmptyObservationDataset();
const patient1: PatientShape = {
"@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
name: ["jon"],
};
const patient2: PatientShape = {
"@id": "http://example.com/patient2",
type: { "@id": "Patient" },
name: ["jane"],
roommate: [patient1],
};
patient1.roommate = [patient2];
observation.type = { "@id": "Observation" };
observation.subject = patient1;
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "jon" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/patient2> .\n<http://example.com/patient2> <http://hl7.org/fhir/name> "jane" .\n<http://example.com/patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "jon" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/patient2> .\n<http://example.com/patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/patient2> <http://hl7.org/fhir/name> "jane" .\n<http://example.com/patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
);
});
it("adds a proxy object to the array", async () => {
const [, , builder] = await getTinyLoadedDataset();
const patient3 = builder.fromSubject(
const patient3: PatientShape = builder.fromSubject(
namedNode("http://example.com/Patient3"),
);
const patient1 = builder.fromSubject(
patient3.type = { "@id": "Patient" };
const patient1: PatientShape = builder.fromSubject(
namedNode("http://example.com/Patient1"),
);
patient3.roommate.push(patient1);
patient3.roommate?.push(patient1);
});
it("sets a primitive on an array", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
(patient.name as string[])[0] = "jon";
expect(dataset.toString()).toBe(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "jon" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "jon" .\n',
);
});
it("sets a primitive on an array and overwrites one that already is there", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
dataset.add(
quad(
namedNode("http://example.com/Patient1"),
@ -557,15 +603,16 @@ describe("jsonldDatasetProxy", () => {
);
(patient.name as string[])[0] = "not jon";
expect(dataset.toString()).toBe(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "not jon" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "not jon" .\n',
);
});
it("sets an array", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = ["Joe", "Mama"];
expect(dataset.toString()).toBe(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Joe" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Mama" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Joe" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Mama" .\n',
);
});
@ -573,19 +620,23 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getTinyLoadedDataset();
const replacementPatient: PatientShape = {
"@id": "http://example.com/ReplacementPatient",
type: { "@id": "Patient" },
name: ["Jackson"],
};
observation.subject = replacementPatient;
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/ReplacementPatient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n<http://example.com/ReplacementPatient> <http://hl7.org/fhir/name> "Jackson" .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/ReplacementPatient> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n<http://example.com/ReplacementPatient> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/ReplacementPatient> <http://hl7.org/fhir/name> "Jackson" .\n',
);
});
it("Does not overwrite the full object when a partial object is provided", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
observation.subject = { "@id": "http://example.com/Patient2" };
observation.subject = {
"@id": "http://example.com/Patient2",
type: { "@id": "Patient" },
};
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient2> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient2> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
);
});
@ -593,12 +644,13 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getTinyLoadedDataset();
const replacementPatient: PatientShape = {
"@id": "http://example.com/ReplacementPatient",
type: { "@id": "Patient" },
name: ["Jackson"],
};
const roommateArr = observation?.subject?.roommate as PatientShape[];
roommateArr[0] = replacementPatient;
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/ReplacementPatient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n<http://example.com/ReplacementPatient> <http://hl7.org/fhir/name> "Jackson" .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/ReplacementPatient> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n<http://example.com/ReplacementPatient> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/ReplacementPatient> <http://hl7.org/fhir/name> "Jackson" .\n',
);
});
@ -607,6 +659,7 @@ describe("jsonldDatasetProxy", () => {
const roommateArr = observation.subject?.roommate as PatientShape[];
roommateArr[0] = {
"@id": "http://example.com/ReplacementPatient",
type: { "@id": "Patient" },
name: ["Jackson"],
};
expect(roommateArr.length).toBe(2);
@ -619,7 +672,7 @@ describe("jsonldDatasetProxy", () => {
patient["@id"] = "http://example.com/RenamedPatient";
expect(patient["@id"]).toBe("http://example.com/RenamedPatient");
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/RenamedPatient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/RenamedPatient> .\n<http://example.com/RenamedPatient> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/RenamedPatient> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/RenamedPatient> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/RenamedPatient> .\n<http://example.com/RenamedPatient> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/RenamedPatient> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/RenamedPatient> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n',
);
});
@ -627,7 +680,7 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject;
expect(dataset.toString()).toBe(
'<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n',
);
});
@ -635,7 +688,7 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject?.name;
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
);
});
@ -643,23 +696,23 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject?.roommate?.[0];
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n',
);
});
it("Removes all adjoining triples when garbage collection is indicated via the delete operator on an array with blank nodes", async () => {
const [dataset, observation] = await getTinyLoadedDatasetWithBlankNodes();
const deletedBlankNode =
observation.subject?.roommate?.[0][_getUnderlyingNode];
delete observation.subject?.roommate?.[0];
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> _:b26_Patient1 .\n_:b26_Patient1 <http://hl7.org/fhir/name> "Garrett" .\n',
);
expect(dataset.match(deletedBlankNode).size).toBe(0);
});
it("Removes a literal in an array when using the delete operator", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject?.name?.[0];
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
);
});
@ -670,7 +723,7 @@ describe("jsonldDatasetProxy", () => {
delete observation["@id"];
expect(observation).toEqual({ "@id": "http://example.com/Observation1" });
expect(dataset.toString()).toBe(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
);
});
@ -711,27 +764,29 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getTinyLoadedDataset();
const replacementPatient: PatientShape = {
"@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
name: ["Mister Sneaky"],
};
observation.subject = replacementPatient;
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Mister Sneaky" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Mister Sneaky" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
);
});
it("handles Object.assign", async () => {
const [dataset, observation] = await getTinyLoadedDataset();
Object.assign(observation, {
Object.assign(observation.subject!, {
age: 35,
isHappy: true,
});
expect(dataset.toString()).toBe(
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Observation1> <http://hl7.org/fhir/age> "35"^^<http://www.w3.org/2001/XMLSchema#integer> .\n<http://example.com/Observation1> <http://hl7.org/fhir/isHappy> "true"^^<http://www.w3.org/2001/XMLSchema#boolean> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
'<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/roommate> <http://example.com/Patient2> .\n<http://example.com/Patient1> <http://hl7.org/fhir/age> "35"^^<http://www.w3.org/2001/XMLSchema#integer> .\n<http://example.com/Patient1> <http://hl7.org/fhir/isHappy> "true"^^<http://www.w3.org/2001/XMLSchema#boolean> .\n<http://example.com/Patient2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient2> <http://hl7.org/fhir/name> "Rob" .\n<http://example.com/Patient2> <http://hl7.org/fhir/roommate> <http://example.com/Patient1> .\n',
);
});
it("Adds elements to the array even if they were modified by the datastore", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = ["Joe", "Blow"];
dataset.add(
quad(
@ -745,6 +800,7 @@ describe("jsonldDatasetProxy", () => {
it("Removes elements from the array even if they were modified by the datastore", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = ["Joe", "Blow"];
dataset.delete(
quad(
@ -758,6 +814,7 @@ describe("jsonldDatasetProxy", () => {
it("Removes and adds from the array even if they were modified by the datastore", async () => {
const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = ["Joe", "Blow"];
dataset.delete(
quad(
@ -793,7 +850,10 @@ describe("jsonldDatasetProxy", () => {
it("Prevents duplicates for Objects", async () => {
const [, observation] = await getLoadedDataset();
const roommates = observation.subject?.roommate as PatientShape[];
roommates[0] = { "@id": "http://example.com/Patient3" };
roommates[0] = {
"@id": "http://example.com/Patient3",
type: { "@id": "Patient" },
};
expect(roommates.length).toBe(1);
expect(roommates[0].name?.[0]).toBe("Amy");
});
@ -824,12 +884,13 @@ describe("jsonldDatasetProxy", () => {
delete arr[5];
expect(arr).toEqual(["Garrett", "Bobby", "Ferguson"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
);
});
it("Can set a triple object named node with just a string", async () => {
const [dataset, observation] = await getEmptyObservationDataset();
observation.type = { "@id": "Observation" };
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
observation.subject = "http://example.com/Patient1";
@ -837,7 +898,7 @@ describe("jsonldDatasetProxy", () => {
"@id": "http://example.com/Patient1",
});
expect(dataset.toString()).toBe(
"<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n",
"<http://example.com/Observation1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Observation> .\n<http://example.com/Observation1> <http://hl7.org/fhir/subject> <http://example.com/Patient1> .\n",
);
});
@ -848,7 +909,7 @@ describe("jsonldDatasetProxy", () => {
arr.copyWithin(0, 2, 3);
expect(arr).toEqual(["Ferguson", "Bobby"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
);
});
@ -858,7 +919,7 @@ describe("jsonldDatasetProxy", () => {
arr.copyWithin(0, 2);
expect(arr).toEqual(["Ferguson", "Bobby"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
);
});
@ -870,7 +931,7 @@ describe("jsonldDatasetProxy", () => {
expect(() => arr.copyWithin(0, undefined, 2)).not.toThrowError();
expect(arr).toEqual(["Garrett", "Bobby", "Ferguson"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
);
});
@ -880,7 +941,7 @@ describe("jsonldDatasetProxy", () => {
arr.fill("Beepy", 2, 5);
expect(arr).toEqual(["Garrett", "Bobby", "Beepy"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
);
});
@ -890,7 +951,7 @@ describe("jsonldDatasetProxy", () => {
expect(arr.pop()).toBe("Ferguson");
expect(arr).toEqual(["Garrett", "Bobby"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n',
);
});
@ -906,7 +967,7 @@ describe("jsonldDatasetProxy", () => {
arr.push("Beepy");
expect(arr).toEqual(["Garrett", "Bobby", "Ferguson", "Beepy"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
);
});
@ -915,7 +976,7 @@ describe("jsonldDatasetProxy", () => {
patient.name?.reverse();
expect(patient.name).toEqual(["Ferguson", "Bobby", "Garrett"]);
expect(dataset.toString()).toBe(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
);
});
@ -925,7 +986,7 @@ describe("jsonldDatasetProxy", () => {
expect(arr.shift()).toEqual("Garrett");
expect(arr).toEqual(["Bobby", "Ferguson"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
);
});
@ -942,7 +1003,7 @@ describe("jsonldDatasetProxy", () => {
});
expect(patient.name).toEqual(["Bobby", "Garrett", "Ferguson"]);
expect(dataset.toString()).toBe(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
);
});
@ -974,7 +1035,7 @@ describe("jsonldDatasetProxy", () => {
arr.splice(1, 0, "Beepy");
expect(arr).toEqual(["Garrett", "Beepy", "Bobby", "Ferguson"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
);
});
@ -1008,7 +1069,7 @@ describe("jsonldDatasetProxy", () => {
arr.splice(1, 1);
expect(arr).toEqual(["Garrett", "Ferguson"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n',
);
});
@ -1018,7 +1079,7 @@ describe("jsonldDatasetProxy", () => {
arr.unshift("Beepy");
expect(arr).toEqual(["Beepy", "Garrett", "Bobby", "Ferguson"]);
expect(dataset.toString()).toEqual(
'<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
'<http://example.com/Patient1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Garrett" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Bobby" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Ferguson" .\n<http://example.com/Patient1> <http://hl7.org/fhir/name> "Beepy" .\n',
);
});
});
@ -1132,6 +1193,8 @@ describe("jsonldDatasetProxy", () => {
it("errors if an object is added without the correct parameters", async () => {
expect(() =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
patients.push({
"@id": "http://example.com/Patient4",
name: ["Dippy"],
@ -1241,7 +1304,11 @@ describe("jsonldDatasetProxy", () => {
it("cannot write to a collection that matches the null, predicate, null pattern", () => {
expect(
() => (patients[1] = { "@id": "http://example.com/Patient4" }),
() =>
(patients[1] = {
"@id": "http://example.com/Patient4",
type: { "@id": "Patient" },
}),
).toThrow(
"A collection that does not specify a match for both a subject or predicate cannot be modified directly.",
);
@ -1267,11 +1334,13 @@ describe("jsonldDatasetProxy", () => {
it("initializes a patient using the fromJSON method", async () => {
const [, , builder] = await getEmptyPatientDataset();
const patient = builder.fromJson<PatientShape>({
type: { "@id": "Patient" },
name: ["Jack", "Horner"],
birthdate: "1725/11/03",
age: 298,
roommate: [
{
type: { "@id": "Patient" },
name: ["Ethical", "Bug"],
},
],
@ -1288,11 +1357,13 @@ describe("jsonldDatasetProxy", () => {
const [, , builder] = await getEmptyPatientDataset();
const patient = builder.fromJson<PatientShape>({
"@id": "http://example.com/Patient13",
type: { "@id": "Patient" },
name: ["Jack", "Horner"],
birthdate: "1725/11/03",
age: 298,
roommate: [
{
type: { "@id": "Patient" },
name: ["Ethical", "Bug"],
},
],
@ -1314,9 +1385,10 @@ describe("jsonldDatasetProxy", () => {
const patient4 = builder
.write(namedNode("http://example.com/Patient4Doc"))
.fromSubject<PatientShape>(namedNode("https://example.com/Patient4"));
patient4.type = { "@id": "Patient" };
patient4.name = ["Jackson"];
expect(dataset.toString()).toBe(
'<https://example.com/Patient4> <http://hl7.org/fhir/name> "Jackson" <http://example.com/Patient4Doc> .\n',
'<https://example.com/Patient4> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://hl7.org/fhir/Patient> <http://example.com/Patient4Doc> .\n<https://example.com/Patient4> <http://hl7.org/fhir/name> "Jackson" <http://example.com/Patient4Doc> .\n',
);
});
});
@ -1399,6 +1471,7 @@ describe("jsonldDatasetProxy", () => {
const doc3 = namedNode("http://example.com/Doc3");
const [, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name?.push("default");
const end1 = write(doc1).using(patient);
patient.name?.push("1");
@ -1426,6 +1499,7 @@ describe("jsonldDatasetProxy", () => {
const doc1 = namedNode("http://example.com/Doc1");
const [, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name?.push("Default");
const [patientOnDoc1] = write(doc1).usingCopy(patient);
patientOnDoc1.name?.push("Doc1");
@ -1676,4 +1750,14 @@ describe("jsonldDatasetProxy", () => {
expect(enSet.size).toBe(0);
});
});
});
};
describe(
"jsonldDatasetProxy - unnested context",
testJsonldDatasetProxy(patientUnnestedContext),
);
describe(
"jsonldDatasetProxy - nested context",
testJsonldDatasetProxy(patientNestedContext),
);

@ -1,9 +1,11 @@
import type { ContextDefinition } from "jsonld";
import type { Schema } from "shexj";
import type { LdoJsonldContext } from "../src/LdoJsonldContext";
export interface ObservationShape {
"@id"?: string;
"@context"?: ContextDefinition;
type: { "@id": "Observation" };
subject?: PatientShape;
notes?: string;
langNotes?: string;
@ -12,7 +14,7 @@ export interface ObservationShape {
export type PatientShape = {
"@id"?: string;
"@context"?: ContextDefinition;
type?: { "@id": "Patient" };
type: { "@id": "Patient" };
name?: string[];
langName?: string[];
birthdate?: string;
@ -26,11 +28,12 @@ export type PatientShape = {
// @ts-ignore
export const patientSchema: Schema = {};
export const patientContext: ContextDefinition = {
export const patientUnnestedContext: ContextDefinition = {
type: {
"@id": "@type",
},
Patient: "http://hl7.org/fhir/Patient",
Observation: "http://hl7.org/fhir/Observation",
subject: { "@id": "http://hl7.org/fhir/subject", "@type": "@id" },
name: {
"@id": "http://hl7.org/fhir/name",
@ -69,17 +72,69 @@ export const patientContext: ContextDefinition = {
},
};
export const patientNestedContext: LdoJsonldContext = {
type: {
"@id": "@type",
},
Observation: {
"@id": "http://hl7.org/fhir/Observation",
"@context": {
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",
},
subject: { "@id": "http://hl7.org/fhir/subject", "@type": "@id" },
},
},
Patient: {
"@id": "http://hl7.org/fhir/Patient",
"@context": {
name: {
"@id": "http://hl7.org/fhir/name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
"@isCollection": true,
},
langName: {
"@id": "http://hl7.org/fhir/langName",
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString",
"@isCollection": true,
},
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",
"@isCollection": true,
},
},
},
};
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
example:Observation1 a fhir:Observation ;
fhir:notes "Cool Notes"^^xsd:string ;
fhir:subject example:Patient1 .
example:Patient1
example:Patient1 a fhir:Patient ;
rdf:type fhir:Patient ;
fhir:name "Garrett"^^xsd:string, "Bobby"^^xsd:string, "Ferguson"^^xsd:string ;
fhir:birthdate "1986-01-01"^^xsd:date ;
@ -87,7 +142,7 @@ example:Patient1
fhir:isHappy "true"^^xsd:boolean ;
fhir:roommate example:Patient2, example:Patient3 .
example:Patient2
example:Patient2 a fhir:Patient ;
rdf:type fhir:Patient ;
fhir:name "Rob"^^xsd:string ;
fhir:birthdate "1987-01-01"^^xsd:date ;
@ -95,7 +150,7 @@ example:Patient2
fhir:isHappy "false"^^xsd:boolean ;
fhir:roommate example:Patient1, example:Patient3 .
example:Patient3
example:Patient3 a fhir:Patient ;
rdf:type fhir:Patient ;
fhir:name "Amy"^^xsd:string ;
fhir:birthdate "1988-01-01"^^xsd:date ;
@ -108,25 +163,25 @@ export const patientDataWithBlankNodes = `
@prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
example:Observation1
example:Observation1 a fhir:Observation ;
fhir:notes "Cool Notes"^^xsd:string ;
fhir:subject _:Patient1 .
_:Patient1
_:Patient1 a 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 _:Patient2, _:Patient3 .
_:Patient2
_:Patient2 a 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 _:Patient1, _:Patient3 .
_:Patient3
_:Patient3 a fhir:Patient ;
fhir:name "Amy"^^xsd:string ;
fhir:birthdate "1988-01-01"^^xsd:date ;
fhir:age "33"^^xsd:integer ;
@ -138,14 +193,14 @@ export const tinyPatientData = `
@prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
example:Observation1
example:Observation1 a fhir:Observation ;
fhir:subject example:Patient1 .
example:Patient1
example:Patient1 a fhir:Patient ;
fhir:name "Garrett"^^xsd:string ;
fhir:roommate example:Patient2 .
example:Patient2
example:Patient2 a fhir:Patient ;
fhir:name "Rob"^^xsd:string ;
fhir:roommate example:Patient1 .
`;
@ -155,7 +210,7 @@ export const tinyArrayPatientData = `
@prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
example:Patient1
example:Patient1 a fhir:Patient ;
fhir:name "Garrett"^^xsd:string, "Bobby"^^xsd:string, "Ferguson"^^xsd:string .
`;
@ -164,14 +219,14 @@ export const tinyPatientDataWithBlankNodes = `
@prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
example:Observation1
example:Observation1 a fhir:Observation ;
fhir:subject _:Patient1 .
_:Patient1
_:Patient1 a fhir:Patient ;
fhir:name "Garrett"^^xsd:string ;
fhir:roommate _:Patient2 .
_:Patient2
_:Patient2 a fhir:Patient ;
fhir:name "Rob"^^xsd:string ;
fhir:roommate _:Patient1 .
`;
@ -181,14 +236,14 @@ export const tinyPatientDataWithLanguageTags = `
@prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
example:Observation1
example:Observation1 a fhir:Observation ;
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
example:Patient1 a fhir:Patient ;
fhir:langName "Jon" ;
fhir:langName "John"@en ;
fhir:langName "Juan"@es ;

@ -0,0 +1,119 @@
import type { ContextDefinition } from "jsonld";
import type { LdoJsonldContext } from "../src/LdoJsonldContext";
export interface Bender {
"@id"?: string;
"@context"?: ContextDefinition;
type: "Bender";
name: string;
element: Element;
friend: (Bender | NonBender | Avatar)[];
}
export interface Avatar {
"@id"?: string;
"@context"?: ContextDefinition;
type: "Avatar";
name: string;
element: Element[];
friend: (Bender | NonBender | Avatar)[];
}
export interface NonBender {
"@id"?: string;
"@context"?: ContextDefinition;
type: "Avatar";
name: string;
friend: (Bender | NonBender | Avatar)[];
}
export type Element =
| {
"@id": "VideoObject";
}
| {
"@id": "ImageObject";
}
| {
"@id": "MediaObject";
}
| {
"@id": "CreativeWork";
};
export const scopedContext: LdoJsonldContext = {
Bender: {
"@id": "http://example.com/Bender",
"@context": {
type: {
"@id": "@type",
},
name: {
"@id": "http://example.com/name",
},
element: {
"@id": "http://example.com/element",
},
friend: {
"@id": "http://example.com/friend",
"@isCollection": true,
},
},
},
Avatar: {
"@id": "http://example.com/Avatar",
"@context": {
type: {
"@id": "@type",
},
name: {
"@id": "http://example.com/name",
},
element: {
"@id": "http://example.com/element",
"@isCollection": true,
},
friend: {
"@id": "http://example.com/friend",
"@isCollection": true,
},
},
},
NonBender: {
"@id": "http://example.com/NonBender",
"@context": {
type: {
"@id": "@type",
},
name: {
"@id": "http://example.com/name",
},
friend: {
"@id": "http://example.com/friend",
"@isCollection": true,
},
},
},
};
export const scopedData = `
@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:Aang a example:Avatar ;
example:name "Aang" ;
example:element example:Air, example:Water ;
example:friend example:Sokka, example:Katara .
example:Katara a example:Bender ;
example:name "Katara" ;
example:element example:Water ;
example:friend example:Sokka, example:Aang .
example:Sokka a example:NonBender ;
example:name "Sokka" ;
example:element example:Water ;
example:friend example:Sokka, example:Aang .
`;

@ -20,6 +20,7 @@
},
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/schema-converter-shex#readme",
"devDependencies": {
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.24",
"@shexjs/parser": "^1.0.0-alpha.24",
"@types/jest": "^27.0.3",
"@types/jsonld": "^1.5.6",
@ -34,7 +35,7 @@
],
"dependencies": {
"@ldo/traverser-shexj": "^0.0.1-alpha.24",
"dts-dom": "^3.6.0",
"dts-dom": "~3.6.0",
"jsonld2graphobject": "^0.0.5"
},
"publishConfig": {

@ -47,20 +47,41 @@ export function toCamelCase(text: string) {
});
}
export function isJsonLdContextBuilder(
item: ExpandedTermDefinition | JsonLdContextBuilder,
): item is JsonLdContextBuilder {
return !!(typeof item === "object" && item instanceof JsonLdContextBuilder);
}
/**
* JsonLd Context Builder
*/
export class JsonLdContextBuilder {
private iriAnnotations: Record<string, Annotation[]> = {};
private iriTypes: Record<string, ExpandedTermDefinition> = {};
private generatedNames: Record<string, string> | undefined;
protected iriAnnotations: Record<string, Annotation[]> = {};
protected iriTypes: Record<
string,
ExpandedTermDefinition | JsonLdContextBuilder
> = {};
protected generatedNames: Record<string, string> | undefined;
private getRelevantBuilder(rdfType?: string): JsonLdContextBuilder {
if (!rdfType) return this;
if (
!this.iriTypes[rdfType] ||
!isJsonLdContextBuilder(this.iriTypes[rdfType])
) {
this.iriTypes[rdfType] = new JsonLdContextBuilder();
}
return this.iriTypes[rdfType] as JsonLdContextBuilder;
}
addSubject(iri: string, annotations?: Annotation[]) {
if (!this.iriAnnotations[iri]) {
this.iriAnnotations[iri] = [];
addSubject(iri: string, rdfType?: string, annotations?: Annotation[]) {
const relevantBuilder = this.getRelevantBuilder(rdfType);
if (!relevantBuilder.iriAnnotations[iri]) {
relevantBuilder.iriAnnotations[iri] = [];
}
if (annotations && annotations.length > 0) {
this.iriAnnotations[iri].push(...annotations);
relevantBuilder.iriAnnotations[iri].push(...annotations);
}
}
@ -68,22 +89,24 @@ export class JsonLdContextBuilder {
iri: string,
expandedTermDefinition: ExpandedTermDefinition,
isContainer: boolean,
rdfType?: string,
annotations?: Annotation[],
) {
this.addSubject(iri, annotations);
if (!this.iriTypes[iri]) {
this.iriTypes[iri] = expandedTermDefinition;
const relevantBuilder = this.getRelevantBuilder(rdfType);
relevantBuilder.addSubject(iri, undefined, annotations);
if (!relevantBuilder.iriTypes[iri]) {
relevantBuilder.iriTypes[iri] = expandedTermDefinition;
if (isContainer) {
this.iriTypes[iri]["@container"] = "@set";
relevantBuilder.iriTypes[iri]["@isCollection"] = true;
}
} else {
const curDef = this.iriTypes[iri];
const curDef = relevantBuilder.iriTypes[iri];
const newDef = expandedTermDefinition;
// TODO: if you reuse the same predicate with a different cardinality,
// it will overwrite the past cardinality. Perhapse we might want to
// split contexts in the various shapes.
if (isContainer) {
curDef["@container"] = "@set";
curDef["@isCollection"] = true;
}
// If the old and new versions both have types
if (curDef["@type"] && newDef["@type"]) {
@ -149,12 +172,13 @@ export class JsonLdContextBuilder {
return generatedNames;
}
getNameFromIri(iri: string) {
if (!this.generatedNames) {
this.generatedNames = this.generateNames();
getNameFromIri(iri: string, rdfType?: string) {
const relevantBuilder = this.getRelevantBuilder(rdfType);
if (!relevantBuilder.generatedNames) {
relevantBuilder.generatedNames = relevantBuilder.generateNames();
}
if (this.generatedNames[iri]) {
return this.generatedNames[iri];
if (relevantBuilder.generatedNames[iri]) {
return relevantBuilder.generatedNames[iri];
} else {
return iri;
}
@ -165,13 +189,25 @@ export class JsonLdContextBuilder {
const namesMap = this.generateNames();
Object.entries(namesMap).forEach(([iri, name]) => {
if (this.iriTypes[iri]) {
contextDefnition[name] = {
let subContext: ExpandedTermDefinition = {
"@id":
iri === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
? "@type"
: iri,
...this.iriTypes[iri],
};
if (isJsonLdContextBuilder(this.iriTypes[iri])) {
subContext["@context"] = (
this.iriTypes[iri] as JsonLdContextBuilder
).generateJsonldContext();
} else {
subContext = {
...subContext,
...this.iriTypes[iri],
};
}
contextDefnition[name] = subContext;
} else {
contextDefnition[name] = iri;
}

@ -1,5 +1,6 @@
import ShexJTraverser from "@ldo/traverser-shexj";
import type { JsonLdContextBuilder } from "./JsonLdContextBuilder";
import { getRdfTypesForTripleConstraint } from "../util/getRdfTypesForTripleConstraint";
/**
* Visitor
@ -10,60 +11,72 @@ export const ShexJNameVisitor =
visitor: async (_shape, _context) => {},
},
TripleConstraint: {
visitor: async (tripleConstraint, context) => {
if (tripleConstraint.valueExpr) {
const isContainer =
tripleConstraint.max !== undefined && tripleConstraint.max !== 1;
if (typeof tripleConstraint.valueExpr === "string") {
// TOOD handle string value expr
} else if (tripleConstraint.valueExpr.type === "NodeConstraint") {
if (tripleConstraint.valueExpr.datatype) {
visitor: async (tripleConstraint, node, context) => {
// Check that there's a triple constraint that is a type at the
// same level if there is, use that as an rdfType
const rdfTypes = getRdfTypesForTripleConstraint(node);
// For Each RDF Type, add it
rdfTypes.forEach((rdfType) => {
if (tripleConstraint.valueExpr) {
const isContainer =
tripleConstraint.max !== undefined && tripleConstraint.max !== 1;
if (typeof tripleConstraint.valueExpr === "string") {
// TOOD handle string value expr
} else if (tripleConstraint.valueExpr.type === "NodeConstraint") {
if (tripleConstraint.valueExpr.datatype) {
context.addPredicate(
tripleConstraint.predicate,
{
"@type": tripleConstraint.valueExpr.datatype,
},
isContainer,
rdfType,
tripleConstraint.annotations,
);
} else if (
tripleConstraint.valueExpr.nodeKind &&
tripleConstraint.valueExpr.nodeKind !== "literal"
) {
context.addPredicate(
tripleConstraint.predicate,
{ "@type": "@id" },
isContainer,
rdfType,
tripleConstraint.annotations,
);
} else {
context.addPredicate(
tripleConstraint.predicate,
{},
isContainer,
rdfType,
tripleConstraint.annotations,
);
}
} else {
context.addPredicate(
tripleConstraint.predicate,
{
"@type": tripleConstraint.valueExpr.datatype,
"@type": "@id",
},
isContainer,
tripleConstraint.annotations,
);
} else if (
tripleConstraint.valueExpr.nodeKind &&
tripleConstraint.valueExpr.nodeKind !== "literal"
) {
context.addPredicate(
tripleConstraint.predicate,
{ "@type": "@id" },
isContainer,
tripleConstraint.annotations,
);
} else {
context.addPredicate(
tripleConstraint.predicate,
{},
isContainer,
rdfType,
tripleConstraint.annotations,
);
}
} else {
context.addPredicate(
context.addSubject(
tripleConstraint.predicate,
{
"@type": "@id",
},
isContainer,
rdfType,
tripleConstraint.annotations,
);
}
} else {
context.addSubject(
tripleConstraint.predicate,
tripleConstraint.annotations,
);
}
});
},
},
NodeConstraint: {
visitor: async (nodeConstraint, context) => {
visitor: async (nodeConstraint, node, context) => {
if (nodeConstraint.values) {
nodeConstraint.values.forEach((value) => {
if (typeof value === "string") {
@ -74,7 +87,7 @@ export const ShexJNameVisitor =
},
},
IriStem: {
visitor: async (iriStem, context) => {
visitor: async (iriStem, node, context) => {
context.addSubject(iriStem.stem);
},
},

@ -14,6 +14,7 @@ export async function shexjToContext(
"@context": "http://www.w3.org/ns/shex.jsonld",
},
"SCHEMA",
{ excludeContext: true },
)) as unknown as Schema;
const jsonLdContextBuilder = new JsonLdContextBuilder();
await ShexJNameVisitor.visit(processedShexj, "Schema", jsonLdContextBuilder);

@ -3,9 +3,11 @@ import * as dom from "dts-dom";
import type { Annotation } from "shexj";
import { nameFromObject } from "../context/JsonLdContextBuilder";
import type { ShapeInterfaceDeclaration } from "./ShapeInterfaceDeclaration";
import { getRdfTypesForTripleConstraint } from "../util/getRdfTypesForTripleConstraint";
import { dedupeObjectTypeMembers } from "./util/dedupeObjectTypeMembers";
export interface ShexJTypeTransformerContext {
getNameFromIri: (iri: string) => string;
getNameFromIri: (iri: string, rdfType?: string) => string;
}
export function commentFromAnnotations(
@ -125,12 +127,19 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
}
// Use EXTENDS
if (transformedChildren.extends) {
newInterface.baseTypes = [];
transformedChildren.extends.forEach((extendsItem) => {
if ((extendsItem as dom.InterfaceDeclaration).kind === "interface") {
newInterface.baseTypes?.push(
extendsItem as dom.InterfaceDeclaration,
);
const extendsInterface = extendsItem as dom.InterfaceDeclaration;
if (extendsInterface.kind === "interface") {
// NOTE: This is how you would use the actual typescript "extends"
// feature, but as ShEx extend doesn't work the same way as ts
// extends, we just dedupe and push members.
// newInterface.baseTypes?.push(
// extendsItem as dom.InterfaceDeclaration,
// );
newInterface.members = dedupeObjectTypeMembers([
...extendsInterface.members,
...newInterface.members,
]);
}
});
}
@ -144,7 +153,6 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
const objectType = name
? dom.create.interface(name)
: dom.create.objectType([]);
const eachOfComment = commentFromAnnotations(eachOf.annotations);
setReturnPointer(objectType);
// Get Input property expressions
const inputPropertyExpressions: dom.PropertyDeclaration[] = [];
@ -178,49 +186,9 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
}
},
);
// Merge property expressions
const properties: Record<string, dom.PropertyDeclaration> = {};
inputPropertyExpressions.forEach((expression) => {
const propertyDeclaration = expression as dom.PropertyDeclaration;
// Combine properties if they're duplicates
if (properties[propertyDeclaration.name]) {
const oldPropertyDeclaration = properties[propertyDeclaration.name];
const oldPropertyTypeAsArray =
oldPropertyDeclaration.type as dom.ArrayTypeReference;
const oldProeprtyType =
oldPropertyTypeAsArray.kind === "array"
? oldPropertyTypeAsArray.type
: oldPropertyDeclaration.type;
const propertyTypeAsArray =
propertyDeclaration.type as dom.ArrayTypeReference;
const propertyType =
propertyTypeAsArray.kind === "array"
? propertyTypeAsArray.type
: propertyDeclaration.type;
const isOptional =
propertyDeclaration.flags === dom.DeclarationFlags.Optional ||
oldPropertyDeclaration.flags === dom.DeclarationFlags.Optional;
properties[propertyDeclaration.name] = dom.create.property(
propertyDeclaration.name,
dom.type.array(dom.create.union([oldProeprtyType, propertyType])),
isOptional
? dom.DeclarationFlags.Optional
: dom.DeclarationFlags.None,
);
// Set JS Comment
properties[propertyDeclaration.name].jsDocComment =
oldPropertyDeclaration.jsDocComment &&
propertyDeclaration.jsDocComment
? `${oldPropertyDeclaration.jsDocComment} | ${propertyDeclaration.jsDocComment}`
: oldPropertyDeclaration.jsDocComment ||
propertyDeclaration.jsDocComment ||
eachOfComment;
} else {
properties[propertyDeclaration.name] = propertyDeclaration;
}
});
objectType.members.push(...Object.values(properties));
objectType.members.push(
...dedupeObjectTypeMembers(inputPropertyExpressions),
);
return objectType;
},
},
@ -229,10 +197,19 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
tripleConstraint,
getTransformedChildren,
setReturnPointer,
node,
context,
) => {
const transformedChildren = await getTransformedChildren();
const propertyName = context.getNameFromIri(tripleConstraint.predicate);
const rdfTypes = getRdfTypesForTripleConstraint(node);
// HACK: Selecting only one RDFType might cause edge cases children in the
// heirarchy that have different predicate names.
const propertyName = context.getNameFromIri(
tripleConstraint.predicate,
rdfTypes[0],
);
const isArray =
tripleConstraint.max !== undefined && tripleConstraint.max !== 1;
const isOptional = tripleConstraint.min === 0;
@ -258,6 +235,7 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
nodeConstraint,
_getTransformedChildren,
setReturnPointer,
node,
context,
) => {
if (nodeConstraint.datatype) {

@ -42,7 +42,7 @@ export async function shexjToTyping(
.emit(declaration, {
rootFlags: dom.ContextFlags.InAmbientNamespace,
})
.replace("\r\n", "\n"),
.replace(/\r\n/g, "\n"),
dts: declaration,
};
});

@ -0,0 +1,44 @@
import type { ObjectTypeMember } from "dts-dom";
import * as dom from "dts-dom";
export function dedupeObjectTypeMembers(
memberList: ObjectTypeMember[],
): ObjectTypeMember[] {
const properties: Record<string, dom.PropertyDeclaration> = {};
memberList.forEach((expression) => {
const propertyDeclaration = expression as dom.PropertyDeclaration;
// Combine properties if they're duplicates
if (properties[propertyDeclaration.name]) {
const oldPropertyDeclaration = properties[propertyDeclaration.name];
const oldPropertyTypeAsArray =
oldPropertyDeclaration.type as dom.ArrayTypeReference;
const oldProeprtyType =
oldPropertyTypeAsArray.kind === "array"
? oldPropertyTypeAsArray.type
: oldPropertyDeclaration.type;
const propertyTypeAsArray =
propertyDeclaration.type as dom.ArrayTypeReference;
const propertyType =
propertyTypeAsArray.kind === "array"
? propertyTypeAsArray.type
: propertyDeclaration.type;
const isOptional =
propertyDeclaration.flags === dom.DeclarationFlags.Optional ||
oldPropertyDeclaration.flags === dom.DeclarationFlags.Optional;
properties[propertyDeclaration.name] = dom.create.property(
propertyDeclaration.name,
dom.type.array(dom.create.union([oldProeprtyType, propertyType])),
isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None,
);
// Set JS Comment
properties[propertyDeclaration.name].jsDocComment =
oldPropertyDeclaration.jsDocComment && propertyDeclaration.jsDocComment
? `${oldPropertyDeclaration.jsDocComment} | ${propertyDeclaration.jsDocComment}`
: oldPropertyDeclaration.jsDocComment ||
propertyDeclaration.jsDocComment;
} else {
properties[propertyDeclaration.name] = propertyDeclaration;
}
});
return Object.values(properties);
}

@ -0,0 +1,110 @@
import type { ShexJTraverserTypes } from "@ldo/traverser-shexj";
import type { InterfaceInstanceNode } from "@ldo/type-traverser";
function recursivelyGatherTypesFromShapeNodes(
shapeNode: InterfaceInstanceNode<
ShexJTraverserTypes,
"Shape",
ShexJTraverserTypes["Shape"]
>,
rdfTypeSet: Set<string>,
): void {
shapeNode.parent("shapeExpr").forEach((parentShapeExpr) => {
parentShapeExpr
.parent("ShapeDecl", "shapeExpr")
.forEach((parentShapeDecl) => {
parentShapeDecl
.parent("shapeDeclRef")
.forEach((parentShapeDeclOrRef) => {
parentShapeDeclOrRef
.parent("shapeExprOrRef")
.forEach((parentShapeExprOrRef) => {
parentShapeExprOrRef
.parent("Shape", "extends")
.forEach((parentShape) => {
recursivelyGatherTypesFromShapeNodes(
parentShape,
rdfTypeSet,
);
const childExpressionNode = parentShape.child("expression");
if (!childExpressionNode) return;
const childEachOf = childExpressionNode.child().child();
if (childEachOf.typeName === "EachOf") {
recursivelyGatherTypesFromEachOfNodes(
childEachOf,
rdfTypeSet,
);
}
});
});
});
});
});
}
function recursivelyGatherTypesFromEachOfNodes(
eachOfNode: InterfaceInstanceNode<
ShexJTraverserTypes,
"EachOf",
ShexJTraverserTypes["EachOf"]
>,
rdfTypeSet: Set<string>,
): void {
const tripleExprs = eachOfNode.instance.expressions;
tripleExprs.forEach((tripleExpr) => {
if (
typeof tripleExpr === "object" &&
tripleExpr.type === "TripleConstraint" &&
tripleExpr.predicate ===
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type" &&
typeof tripleExpr.valueExpr === "object" &&
tripleExpr.valueExpr.type === "NodeConstraint" &&
tripleExpr.valueExpr.values
) {
tripleExpr.valueExpr.values.forEach((val) => {
if (typeof val === "string") rdfTypeSet.add(val);
// TODO handle other edge cases like IRIStem
});
}
});
eachOfNode.parent("tripleExpr").forEach((tripleExprNode) => {
const tripleExprOrRefNodes = tripleExprNode.parent("tripleExprOrRef");
tripleExprOrRefNodes.forEach((tripleExprOrRdfNode) => {
const parentEachOfs = tripleExprOrRdfNode.parent("EachOf", "expressions");
parentEachOfs.forEach((parentEachOf) => {
recursivelyGatherTypesFromEachOfNodes(parentEachOf, rdfTypeSet);
});
// Deal with shape extends
const parentShapes = tripleExprOrRdfNode.parent("Shape", "expression");
parentShapes.forEach((parentShape) =>
recursivelyGatherTypesFromShapeNodes(parentShape, rdfTypeSet),
);
});
});
}
export function getRdfTypesForTripleConstraint(
tripleConstraintNode: InterfaceInstanceNode<
ShexJTraverserTypes,
"TripleConstraint",
ShexJTraverserTypes["TripleConstraint"]
>,
): string[] | undefined[] {
// Check that there's a triple constraint that is a type at the
// same level if there is, use that as an rdfType
const rdfTypeSet = new Set<string>();
tripleConstraintNode.parent("tripleExpr").forEach((tripleExprParents) => {
tripleExprParents
.parent("tripleExprOrRef")
.forEach((tripleExprOrRefParent) => {
tripleExprOrRefParent
.parent("EachOf", "expressions")
.forEach((eachOfParent) => {
recursivelyGatherTypesFromEachOfNodes(eachOfParent, rdfTypeSet);
});
});
});
const rdfTypes = rdfTypeSet.size > 0 ? Array.from(rdfTypeSet) : [undefined];
return rdfTypes;
}

File diff suppressed because one or more lines are too long

@ -31,12 +31,21 @@ export const circular: TestData = {
`,
baseNode: "http://example.com/SampleParent",
successfulContext: {
type: { "@id": "@type" },
Parent: "http://example.com/Parent",
hasChild: { "@id": "http://example.com/hasChild", "@type": "@id" },
Child: "http://example.com/Child",
hasParent: { "@id": "http://example.com/hasParent", "@type": "@id" },
Parent: {
"@id": "http://example.com/Parent",
"@context": {
type: { "@id": "@type" },
hasChild: { "@id": "http://example.com/hasChild", "@type": "@id" },
},
},
Child: {
"@id": "http://example.com/Child",
"@context": {
type: { "@id": "@type" },
hasParent: { "@id": "http://example.com/hasParent", "@type": "@id" },
},
},
},
successfulTypings:
'import {ContextDefinition} from "jsonld"\n\nexport interface ParentShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n type?: {\r\n "@id": "Parent";\r\n };\r\n hasChild: ChildShape;\r\n}\r\n\r\nexport interface ChildShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n type?: {\r\n "@id": "Child";\r\n };\r\n hasParent: ParentShape;\r\n}\r\n\r\n',
'import {ContextDefinition} from "jsonld"\n\nexport interface ParentShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n type?: {\n "@id": "Parent";\n };\n hasChild: ChildShape;\n}\n\nexport interface ChildShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n type?: {\n "@id": "Child";\n };\n hasParent: ParentShape;\n}\n\n',
};

@ -10,14 +10,17 @@ export const extendsSimple: TestData = {
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
ex:EntityShape {
a [ ex:Entity ] ;
ex:entityId .
}
ex:PersonShape EXTENDS @ex:EntityShape {
a [ ex:Person ] ;
foaf:name .
}
ex:EmployeeShape EXTENDS @ex:PersonShape {
a [ ex:Employee ] ;
ex:employeeNumber .
}
`,
@ -34,10 +37,37 @@ export const extendsSimple: TestData = {
`,
baseNode: "http://example.com/SampleParent",
successfulContext: {
entityId: "https://example.com/entityId",
name: "http://xmlns.com/foaf/0.1/name",
employeeNumber: "https://example.com/employeeNumber",
Entity: {
"@id": "https://example.com/Entity",
"@context": {
type: {
"@id": "@type",
},
entityId: "https://example.com/entityId",
},
},
Person: {
"@id": "https://example.com/Person",
"@context": {
type: {
"@id": "@type",
},
entityId: "https://example.com/entityId",
name: "http://xmlns.com/foaf/0.1/name",
},
},
Employee: {
"@id": "https://example.com/Employee",
"@context": {
type: {
"@id": "@type",
},
entityId: "https://example.com/entityId",
name: "http://xmlns.com/foaf/0.1/name",
employeeNumber: "https://example.com/employeeNumber",
},
},
},
successfulTypings:
'import {ContextDefinition} from "jsonld"\n\nexport interface EntityShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n entityId: any;\r\n}\r\n\r\nexport interface PersonShapeextends EntityShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n name: any;\r\n}\r\n\r\nexport interface EmployeeShapeextends PersonShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n employeeNumber: any;\r\n}\r\n\r\n',
'import {ContextDefinition} from "jsonld"\n\nexport interface EntityShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n type: {\n "@id": "Entity";\n };\n entityId: any;\n}\n\nexport interface PersonShape {\n "@id"?: (string | string)[];\n "@context"?: (ContextDefinition | ContextDefinition)[];\n type: ({\n "@id": "Entity";\n } | {\n "@id": "Person";\n })[];\n entityId: any;\n name: any;\n}\n\nexport interface EmployeeShape {\n "@id"?: (string | string | string)[];\n "@context"?: (ContextDefinition | ContextDefinition | ContextDefinition)[];\n type: ({\n "@id": "Entity";\n } | {\n "@id": "Person";\n } | {\n "@id": "Employee";\n })[];\n entityId: any;\n name: any;\n employeeNumber: any;\n}\n\n',
};

@ -1,7 +1,7 @@
import type { TestData } from "./testData";
/**
* Circular
* Old Extends
*/
export const oldExtends: TestData = {
name: "old extends",
@ -9,21 +9,24 @@ export const oldExtends: TestData = {
PREFIX ex: <https://example.com/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
ex:EntityShape {
ex:EntityShape EXTRA a {
$ex:EntityRef (
a [ ex:Entity ] ;
ex:entityId .
)
}
ex:PersonShape {
ex:PersonShape EXTRA a {
$ex:PersonRef (
&ex:EntityRef ;
a [ ex:Person ] ;
foaf:name .
)
}
ex:EmployeeShape EXTENDS @ex:PersonShape {
ex:EmployeeShape EXTRA a {
&ex:PersonRef ;
a [ ex:Employee ] ;
ex:employeeNumber .
}
`,
@ -39,10 +42,37 @@ export const oldExtends: TestData = {
`,
baseNode: "http://example.com/SampleParent",
successfulContext: {
entityId: "https://example.com/entityId",
name: "http://xmlns.com/foaf/0.1/name",
employeeNumber: "https://example.com/employeeNumber",
Entity: {
"@id": "https://example.com/Entity",
"@context": {
type: {
"@id": "@type",
},
entityId: "https://example.com/entityId",
},
},
Person: {
"@id": "https://example.com/Person",
"@context": {
type: {
"@id": "@type",
},
entityId: "https://example.com/entityId",
name: "http://xmlns.com/foaf/0.1/name",
},
},
Employee: {
"@id": "https://example.com/Employee",
"@context": {
type: {
"@id": "@type",
},
entityId: "https://example.com/entityId",
name: "http://xmlns.com/foaf/0.1/name",
employeeNumber: "https://example.com/employeeNumber",
},
},
},
successfulTypings:
'import {ContextDefinition} from "jsonld"\n\nexport interface EntityShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n entityId: any;\r\n}\r\n\r\nexport interface PersonShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n entityId: any;\r\n name: any;\r\n}\r\n\r\nexport interface EmployeeShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n entityId: any;\r\n name: any;\r\n employeeNumber: any;\r\n}\r\n\r\n',
'import {ContextDefinition} from "jsonld"\n\nexport interface EntityShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n type: {\n "@id": "Entity";\n };\n entityId: any;\n}\n\nexport interface PersonShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n type: ({\n "@id": "Entity";\n } | {\n "@id": "Person";\n })[];\n entityId: any;\n name: any;\n}\n\nexport interface EmployeeShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n type: ({\n "@id": "Entity";\n } | {\n "@id": "Person";\n } | {\n "@id": "Employee";\n })[];\n entityId: any;\n name: any;\n employeeNumber: any;\n}\n\n',
};

File diff suppressed because one or more lines are too long

@ -51,31 +51,173 @@ srs:EmailShape EXTRA a {
sampleTurtle: ``,
baseNode: "",
successfulContext: {
type: { "@id": "@type" },
Person: "http://schema.org/Person",
Person2: "http://xmlns.com/foaf/0.1/Person",
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "@id",
"@container": "@set",
Person: {
"@id": "http://schema.org/Person",
"@context": {
type: {
"@id": "@type",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "@id",
"@isCollection": true,
},
name: {
"@id": "http://xmlns.com/foaf/0.1/name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
},
},
Dom: "http://www.w3.org/2006/vcard/ns#Dom",
Home: "http://www.w3.org/2006/vcard/ns#Home",
ISDN: "http://www.w3.org/2006/vcard/ns#ISDN",
Internet: "http://www.w3.org/2006/vcard/ns#Internet",
Intl: "http://www.w3.org/2006/vcard/ns#Intl",
Label: "http://www.w3.org/2006/vcard/ns#Label",
Parcel: "http://www.w3.org/2006/vcard/ns#Parcel",
Postal: "http://www.w3.org/2006/vcard/ns#Postal",
Pref: "http://www.w3.org/2006/vcard/ns#Pref",
Work: "http://www.w3.org/2006/vcard/ns#Work",
X400: "http://www.w3.org/2006/vcard/ns#X400",
value: { "@id": "http://www.w3.org/2006/vcard/ns#value", "@type": "@id" },
name: {
"@id": "http://xmlns.com/foaf/0.1/name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
Person2: {
"@id": "http://xmlns.com/foaf/0.1/Person",
"@context": {
type: {
"@id": "@type",
},
hasEmail: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
"@type": "@id",
"@isCollection": true,
},
name: {
"@id": "http://xmlns.com/foaf/0.1/name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
},
},
Dom: {
"@id": "http://www.w3.org/2006/vcard/ns#Dom",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Home: {
"@id": "http://www.w3.org/2006/vcard/ns#Home",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
ISDN: {
"@id": "http://www.w3.org/2006/vcard/ns#ISDN",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Internet: {
"@id": "http://www.w3.org/2006/vcard/ns#Internet",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Intl: {
"@id": "http://www.w3.org/2006/vcard/ns#Intl",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Label: {
"@id": "http://www.w3.org/2006/vcard/ns#Label",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Parcel: {
"@id": "http://www.w3.org/2006/vcard/ns#Parcel",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Postal: {
"@id": "http://www.w3.org/2006/vcard/ns#Postal",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Pref: {
"@id": "http://www.w3.org/2006/vcard/ns#Pref",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
Work: {
"@id": "http://www.w3.org/2006/vcard/ns#Work",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
X400: {
"@id": "http://www.w3.org/2006/vcard/ns#X400",
"@context": {
type: {
"@id": "@type",
},
value: {
"@id": "http://www.w3.org/2006/vcard/ns#value",
"@type": "@id",
},
},
},
},
successfulTypings:
'import {ContextDefinition} from "jsonld"\n\nexport interface SolidProfileShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n /**\r\n * Defines the node as a Person | Defines the node as a Person\r\n */\r\n type: ({\r\n "@id": "Person";\r\n } | {\r\n "@id": "Person2";\r\n })[];\r\n /**\r\n * The person\'s email.\r\n */\r\n hasEmail?: (EmailShape)[];\r\n /**\r\n * An alternate way to define a person\'s name\r\n */\r\n name?: string;\r\n}\r\n\r\nexport interface EmailShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n /**\r\n * The type of email.\r\n */\r\n type?: {\r\n "@id": "Dom";\r\n } | {\r\n "@id": "Home";\r\n } | {\r\n "@id": "ISDN";\r\n } | {\r\n "@id": "Internet";\r\n } | {\r\n "@id": "Intl";\r\n } | {\r\n "@id": "Label";\r\n } | {\r\n "@id": "Parcel";\r\n } | {\r\n "@id": "Postal";\r\n } | {\r\n "@id": "Pref";\r\n } | {\r\n "@id": "Work";\r\n } | {\r\n "@id": "X400";\r\n };\r\n /**\r\n * The value of an email as a mailto link (Example <mailto:jane@example.com>)\r\n */\r\n value: {\r\n "@id": string;\r\n };\r\n}\r\n\r\n',
'import {ContextDefinition} from "jsonld"\n\nexport interface SolidProfileShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n /**\n * Defines the node as a Person | Defines the node as a Person\n */\n type: ({\n "@id": "Person";\n } | {\n "@id": "Person2";\n })[];\n /**\n * The person\'s email.\n */\n hasEmail?: (EmailShape)[];\n /**\n * An alternate way to define a person\'s name\n */\n name?: string;\n}\n\nexport interface EmailShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n /**\n * The type of email.\n */\n type?: {\n "@id": "Dom";\n } | {\n "@id": "Home";\n } | {\n "@id": "ISDN";\n } | {\n "@id": "Internet";\n } | {\n "@id": "Intl";\n } | {\n "@id": "Label";\n } | {\n "@id": "Parcel";\n } | {\n "@id": "Postal";\n } | {\n "@id": "Pref";\n } | {\n "@id": "Work";\n } | {\n "@id": "X400";\n };\n /**\n * The value of an email as a mailto link (Example <mailto:jane@example.com>)\n */\n value: {\n "@id": string;\n };\n}\n\n',
};

@ -18,43 +18,73 @@ export const reusedPredicates: TestData = {
app:LawShape {
rdf:type [ app:Law ] ;
app:name xsd:string ;
app:name xsd:string *;
app:path IRI ;
}
app:VocabularyShape {
rdf:type [ app:Vocabulary ] ;
app:name xsd:string ;
app:path IRI ;
app:path IRI *;
}
`,
sampleTurtle: ``,
baseNode: "http://example.com/SampleParent",
successfulContext: {
type: { "@id": "@type" },
Document: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Document",
vocabulary: {
"@id":
"https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#vocabulary",
"@type": "@id",
"@container": "@set",
Document: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Document",
"@context": {
type: {
"@id": "@type",
},
vocabulary: {
"@id":
"https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#vocabulary",
"@type": "@id",
"@isCollection": true,
},
law: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#law",
"@type": "@id",
},
},
},
Vocabulary:
"https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Vocabulary",
name: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
path: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path",
"@type": "@id",
Vocabulary: {
"@id":
"https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Vocabulary",
"@context": {
type: {
"@id": "@type",
},
name: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
},
path: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path",
"@type": "@id",
"@isCollection": true,
},
},
},
law: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#law",
"@type": "@id",
Law: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Law",
"@context": {
type: {
"@id": "@type",
},
name: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name",
"@type": "http://www.w3.org/2001/XMLSchema#string",
"@isCollection": true,
},
path: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path",
"@type": "@id",
},
},
},
Law: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Law",
},
successfulTypings:
'import {ContextDefinition} from "jsonld"\n\nexport interface DocumentShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n type: {\r\n "@id": "Document";\r\n };\r\n vocabulary?: (VocabularyShape)[];\r\n law: LawShape;\r\n}\r\n\r\nexport interface LawShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n type: {\r\n "@id": "Law";\r\n };\r\n name: string;\r\n path: {\r\n "@id": string;\r\n };\r\n}\r\n\r\nexport interface VocabularyShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n type: {\r\n "@id": "Vocabulary";\r\n };\r\n name: string;\r\n path: {\r\n "@id": string;\r\n };\r\n}\r\n\r\n',
'import {ContextDefinition} from "jsonld"\n\nexport interface DocumentShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n type: {\n "@id": "Document";\n };\n vocabulary?: (VocabularyShape)[];\n law: LawShape;\n}\n\nexport interface LawShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n type: {\n "@id": "Law";\n };\n name?: string[];\n path: {\n "@id": string;\n };\n}\n\nexport interface VocabularyShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n type: {\n "@id": "Vocabulary";\n };\n name: string;\n path?: {\n "@id": string;\n }[];\n}\n\n',
};

@ -32,7 +32,7 @@ export const simple: TestData = {
givenName: {
"@id": "http://xmlns.com/foaf/0.1/givenName",
"@type": "http://www.w3.org/2001/XMLSchema#string",
"@container": "@set",
"@isCollection": true,
},
familyName: {
"@id": "http://xmlns.com/foaf/0.1/familyName",
@ -41,10 +41,10 @@ export const simple: TestData = {
phone: {
"@id": "http://xmlns.com/foaf/0.1/phone",
"@type": "@id",
"@container": "@set",
"@isCollection": true,
},
mbox: { "@id": "http://xmlns.com/foaf/0.1/mbox", "@type": "@id" },
},
successfulTypings:
'import {ContextDefinition} from "jsonld"\n\nexport interface EmployeeShape {\n "@id"?: string;\r\n "@context"?: ContextDefinition;\r\n givenName: string[];\r\n familyName: string;\r\n phone?: {\r\n "@id": string;\r\n }[];\r\n mbox: {\r\n "@id": string;\r\n };\r\n}\r\n\r\n',
'import {ContextDefinition} from "jsonld"\n\nexport interface EmployeeShape {\n "@id"?: string;\n "@context"?: ContextDefinition;\n givenName: string[];\n familyName: string;\n phone?: {\n "@id": string;\n }[];\n mbox: {\n "@id": string;\n };\n}\n\n',
};

@ -1,4 +1,4 @@
import type { ContextDefinition } from "jsonld";
import type { LdoJsonldContext } from "@ldo/jsonld-dataset-proxy";
import { activityPub } from "./activityPub";
import { circular } from "./circular";
import { profile } from "./profile";
@ -6,14 +6,14 @@ import { reducedProfile } from "./reducedProfile";
import { simple } from "./simple";
import { extendsSimple } from "./extendsSimple";
import { reusedPredicates } from "./reusedPredicates";
// import { oldExtends } from "./oldExtends";
import { oldExtends } from "./oldExtends";
export interface TestData {
name: string;
shexc: string;
sampleTurtle: string;
baseNode: string;
successfulContext: ContextDefinition;
successfulContext: LdoJsonldContext;
successfulTypings: string;
}
@ -24,6 +24,6 @@ export const testData: TestData[] = [
reducedProfile,
activityPub,
extendsSimple,
// oldExtends,
oldExtends,
reusedPredicates,
];

@ -0,0 +1,35 @@
import type { ContextDefinition } from "jsonld";
export interface DocumentShape {
"@id"?: string;
"@context"?: ContextDefinition;
type: {
"@id": "Document";
};
vocabulary?: VocabularyShape[];
law: LawShape;
}
export interface LawShape {
"@id"?: string;
"@context"?: ContextDefinition;
type: {
"@id": "Law";
};
name?: string[];
path: {
"@id": string;
};
}
export interface VocabularyShape {
"@id"?: string;
"@context"?: ContextDefinition;
type: {
"@id": "Vocabulary";
};
name: string;
path?: {
"@id": string;
}[];
}

@ -50,12 +50,13 @@ export class TrackingProxyContext extends ProxyContext {
receiver,
) => {
const subject = target["@id"];
const rdfTypes = this.getRdfType(subject);
if (typeof key === "symbol") {
// Do Nothing
} else if (key === "@id") {
this.addListener([subject, null, null, null]);
} else if (!this.contextUtil.isArray(key)) {
const predicate = namedNode(this.contextUtil.keyToIri(key));
} else if (!this.contextUtil.isArray(key, rdfTypes)) {
const predicate = namedNode(this.contextUtil.keyToIri(key, rdfTypes));
this.addListener([subject, predicate, null, null]);
}
return oldGetFunction && oldGetFunction(target, key, receiver);

@ -21,7 +21,6 @@
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/traverser-shexj#readme",
"devDependencies": {
"@types/jest": "^27.0.3",
"@types/shexj": "^2.1.3",
"jest": "^27.4.5",
"ts-jest": "^27.1.2"
},

@ -1,8 +1,8 @@
import type { ShexJTraverserTypes } from ".";
import type { TraverserDefinition } from "@ldo/type-traverser";
import type { shapeExpr, valueSetValue } from "shexj";
import type { TraverserDefinitions } from "@ldo/type-traverser";
import type { shapeExpr, valueSetValue } from "./ShexJTypes";
export const ShexJTraverserDefinition: TraverserDefinition<ShexJTraverserTypes> =
export const ShexJTraverserDefinition: TraverserDefinitions<ShexJTraverserTypes> =
{
Schema: {
kind: "interface",
@ -29,7 +29,9 @@ export const ShexJTraverserDefinition: TraverserDefinition<ShexJTraverserTypes>
shapeExprOrRef: {
kind: "union",
selector: (item) =>
typeof item === "string" ? "shapeDeclRef" : "shapeExpr",
typeof item === "string" || item.type === "ShapeDecl"
? "shapeDeclRef"
: "shapeExpr",
},
ShapeOr: {
kind: "interface",
@ -55,7 +57,8 @@ export const ShexJTraverserDefinition: TraverserDefinition<ShexJTraverserTypes>
},
shapeDeclRef: {
kind: "union",
selector: () => "shapeDeclLabel",
selector: (value) =>
typeof value === "string" ? "shapeDeclLabel" : "ShapeDecl",
},
shapeDeclLabel: {
kind: "union",

@ -41,7 +41,7 @@ import type {
tripleExprRef,
valueSetValue,
Wildcard,
} from "shexj";
} from "./ShexJTypes";
import type { ValidateTraverserTypes } from "@ldo/type-traverser";
export type ShexJTraverserTypes = ValidateTraverserTypes<{
@ -110,7 +110,7 @@ export type ShexJTraverserTypes = ValidateTraverserTypes<{
shapeDeclRef: {
kind: "union";
type: shapeDeclRef;
typeNames: "shapeDeclLabel";
typeNames: "shapeDeclLabel" | "ShapeDecl";
};
shapeDeclLabel: {
kind: "union";

@ -0,0 +1,602 @@
// These type definitions are slightly modified to make up for the fact that the "extends" clause can loop back on itself
export {}; // only export specified symbols (strict-export-declare-modifiers)
/**
* Structure for expressing a Shape Expression schema.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapes-schema">ShEx Schema definition</a>
*/
export interface Schema {
/**
* Mandatory type "Schema".
*/
type: "Schema";
/**
* JSON-LD <a href="https://www.w3.org/TR/json-ld11/#the-context">@context</a> for ShEx.
*/
"@context"?: "http://www.w3.org/ns/shex.jsonld" | undefined;
/**
* List of semantic actions to be executed when evaluating conformance.
*/
startActs?: SemAct[] | undefined; // +
/**
* Identifies default starting shape expression.
*/
start?: shapeExprOrRef | undefined;
/**
* List of ShEx schemas to <a href="http://shex.io/shex-semantics/#import">import</a> when processing this schema.
*/
imports?: IRIREF[] | undefined; // +
/**
* The list of {@link ShapeDecl}s defined in this schema. Each MUST include and {@link ShapeOr#id}.
*/
shapes?: ShapeDecl[] | undefined; // +
}
export interface semactsAndAnnotations {
/**
* List of semantic actions to be executed when evaluating conformance.
*/
semActs?: SemAct[] | undefined; // +;
/**
* List of {@link SemAct#predicate}/{@link SemAct#object} annotations.
*/
annotations?: Annotation[] | undefined; // +
}
/**
* A declaration for a shapeExpr with added inheritance constraints.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapedecl">ShEx ShapeDecl definition</a>
*/
export interface ShapeDecl {
/**
* Mandatory type "ShapeDecl".
*/
type: "ShapeDecl";
/**
* The identifier is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
id: shapeDeclLabel;
/**
* Whether this ShapeDecl participates in <a href="http://shex.io/shex-semantics/#dfn-inheritanceSubstitution">inheritance substitution</a>.
*/
abstract?: BOOL | undefined;
/**
* The list of {@link shapeExprOrRef}s that a neighborhood MUST conform to in order to conform to this ShapeDecl.
*/
restricts?: shapeExprOrRef[] | undefined; // +
/**
* The {@link shapeExpr} to which this neighborhood MUST also conform.
*/
shapeExpr: shapeExpr;
}
/**
* Union of shape expression types.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapeexpr">ShEx shapeExpr definition</a>
*/
export type shapeExpr =
| ShapeOr
| ShapeAnd
| ShapeNot
| NodeConstraint
| Shape
| ShapeExternal;
/**
* Union of shapeExpr and shapeDeclRef.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapeexpr">ShEx shapeExpr definition</a>
*/
export type shapeExprOrRef = shapeExpr | shapeDeclRef;
/**
* A non-exclusive choice of shape expressions; considered conformant if any of {@link #shapeExprs} conforms.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapeor">ShEx shapeExpr definition</a>
*/
export interface ShapeOr {
/**
* Mandatory type "ShapeOr".
*/
type: "ShapeOr";
/**
* List of two or more {@link shapeExprOrRef}s in this disjunction.
*/
shapeExprs: shapeExprOrRef[]; // {2,}
}
/**
* A conjunction of shape expressions; considered conformant if each conjunct conforms.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapeor">ShEx shapeExpr definition</a>
*/
export interface ShapeAnd {
/**
* Mandatory type "ShapeAnd".
*/
type: "ShapeAnd";
/**
* List of two or more {@link shapeExprOrRef}s in this conjunction.
*/
shapeExprs: shapeExprOrRef[]; // {2,}
}
/**
* A negated shape expressions; considered conformant if {@link #shapeExpr} is not conformant.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapenot">ShEx shapeExpr definition</a>
*/
export interface ShapeNot {
/**
* Mandatory type "ShapeNot".
*/
type: "ShapeNot";
/**
* The {@link shapeExprOrRef} that must be non-conformant for this shape expression to be conformant.
*/
shapeExpr: shapeExprOrRef;
}
/**
* A shape expression not defined in this schema or in any imported schema. The definition of this shape expression is NOT defined by ShEx.
* @see <a href="http://shex.io/shex-semantics/#dfn-shapeexternal">ShEx shapeExpr definition</a>
*/
export interface ShapeExternal {
/**
* Mandatory type "ShapeExternal".
*/
type: "ShapeExternal";
}
/**
* A reference a shape expression.
* The reference is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
* This is modified to also include the possibility of ShapeDecl
*/
export type shapeDeclRef = shapeDeclLabel | ShapeDecl;
/**
* An identifier for a shape expression.
* The identifier is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
export type shapeDeclLabel = IRIREF | BNODE;
export type nodeKind = "iri" | "bnode" | "nonliteral" | "literal";
/**
* A collection of constraints on <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-node">RDF Term</a>s expected for conformance.
* The identifier is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
export interface NodeConstraint extends xsFacets, semactsAndAnnotations {
/**
* Mandatory type "NodeConstraint".
*/
type: "NodeConstraint";
/**
* Type of <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-node">RDF Term</a> expected for a conformant RDF node.
* @see <a href="http://shex.io/shex-semantics/#nodeKind">ShEx nodeKind definition</a>
*/
nodeKind?: nodeKind | undefined;
/**
* The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri">RDF Literal datatype IRITerm</a> expected for a conformant RDF node.
* @see <a href="http://shex.io/shex-semantics/#datatype">ShEx datatype definition</a>
*/
datatype?: IRIREF | undefined;
/**
* The set of permissible values.
* @see <a href="http://shex.io/shex-semantics/#values">ShEx values definition</a>
*/
values?: valueSetValue[] | undefined;
}
/**
* The set of XML Schema Facets supported in ShEx; defers to {@link stringFacets} and {@link numericFacets}.
* @see <a href="http://shex.io/shex-semantics/#xs-string">ShEx String Facet Constraints</a> and <a href="http://shex.io/shex-semantics/#xs-numeric">ShEx Numeric Facet Constraints</a>.
*/
export interface xsFacets extends stringFacets, numericFacets {}
/**
* The set of <a href="https://www.w3.org/TR/xmlschema-2/#facets">XML Schema Facets</a> applying to <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form">lexical forms of RDF terms</a>.
* @see <a href="http://shex.io/shex-semantics/#xs-string">ShEx String Facet Constraints</a>.
*/
export interface stringFacets {
/**
* Expected length of the lexical form of an RDF Term.
*/
length?: INTEGER | undefined;
/**
* Expected minimum length of the lexical form of an RDF Term.
*/
minlength?: INTEGER | undefined;
/**
* Expected maximum length of the lexical form of an RDF Term.
*/
maxlength?: INTEGER | undefined;
/**
* Regular expression which the lexical forn of an RDF Term must match.
*/
pattern?: STRING | undefined;
/**
* Optional flags for the regular expression in {@link pattern}.
*/
flags?: STRING | undefined;
}
/**
* The set of <a href="https://www.w3.org/TR/xmlschema-2/#facets">XML Schema Facets</a> applying to <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-value-space">numeric values of RDF terms</a>.
* @see <a href="http://shex.io/shex-semantics/#xs-numeric">ShEx Numeric Facet Constraints</a>.
*/
export interface numericFacets {
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value <= {@link mininclusive}.
*/
mininclusive?: numericLiteral | undefined;
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value < {@link minexclusive}.
*/
minexclusive?: numericLiteral | undefined;
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value > {@link maxinclusive}.
*/
maxinclusive?: numericLiteral | undefined;
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value >= {@link maxexclusive}.
*/
maxexclusive?: numericLiteral | undefined;
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value whose canonical form has {@link totaldigits} digits.
* @see <a href="http://shex.io/shex-semantics/#nodeSatisfies-totaldigits">ShEx totalDigits definition</a>
*/
totaldigits?: INTEGER | undefined;
/**
* Conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> has as a numeric value whose canonical form has {@link fractiondigits} digits.
* @see <a href="http://shex.io/shex-semantics/#nodeSatisfies-fractiondigits">ShEx fractionDigits definition</a>
*/
fractiondigits?: INTEGER | undefined;
}
/**
* Union of numeric types in ShEx used in {@link numericFacets}s.
*/
export type numericLiteral = INTEGER | DECIMAL | DOUBLE;
/**
* Union of numeric types that may appear in a value set.
* @see {@link NodeConstraint#values}.
*/
export type valueSetValue =
| objectValue
| IriStem
| IriStemRange
| LiteralStem
| LiteralStemRange
| Language
| LanguageStem
| LanguageStemRange;
/**
* JSON-LD representation of a URL or a Literal.
*/
export type objectValue = IRIREF | ObjectLiteral;
/**
* A <a href="https://www.w3.org/TR/json-ld11/#value-objects">JSON-LD Value Object</a> used to express an <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a>.
*/
export interface ObjectLiteral {
/**
* The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form">lexical form</a> of an RDF Literal.
*/
value: STRING;
/**
* The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag">language tag</a> of an RDF Literal.
*/
language?: STRING | undefined;
/**
* The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-datatype">datatype</a> of an RDF Literal.
*/
type?: STRING | undefined;
}
/**
* Matchs an <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-iri">RDF IRI</a> starting with the character sequence in {@link stem}.
*/
export interface IriStem {
/**
* Mandatory type "IriStem".
*/
type: "IriStem";
/**
* substring of IRI to be matched.
*/
stem: IRIREF;
}
export type iriRangeStem = IRIREF | Wildcard;
export type iriRangeExclusion = IRIREF | IriStem;
/**
* Filters a matching <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-iri">RDF IRI</a>s through a list of exclusions.
* The initial match is made on an IRI stem per {@link IriStem} or a {@link Wildcard} to accept any IRI.
* The {@link exclusion}s are either specific IRIs or {@link IRIStem}s.
*/
export interface IriStemRange {
/**
* Mandatory type "IriStemRange".
*/
type: "IriStemRange";
/**
* substring of IRI to be matched or a {@link Wildcard} matching any IRI.
*/
stem: iriRangeStem;
/**
* IRIs or {@link IRIStem}s to exclude.
*/
exclusions: iriRangeExclusion[]; // +
}
/**
* Matchs an <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a> starting with the character sequence in {@link stem}.
*/
export interface LiteralStem {
/**
* Mandatory type "LiteralStem".
*/
type: "LiteralStem";
/**
* substring of Literal to be matched.
*/
stem: STRING;
}
export type literalRangeStem = string | Wildcard;
export type literalRangeExclusion = string | LiteralStem;
/**
* Filters a matching <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-literal">RDF Literal</a>s through a list of exclusions.
* The initial match is made on an Literal stem per {@link LiteralStem} or a {@link Wildcard} to accept any Literal.
* The {@link exclusion}s are either specific Literals or {@link LiteralStem}s.
*/
export interface LiteralStemRange {
/**
* Mandatory type "LiteralStemRange".
*/
type: "LiteralStemRange";
/**
* substring of Literal to be matched or a {@link Wildcard} matching any Literal.
*/
stem: literalRangeStem;
/**
* Literals or {@link LiteralStem}s to exclude.
*/
exclusions: literalRangeExclusion[]; // +
}
/**
* An <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag">RDF Language Tag</a>.
*/
export interface Language {
/**
* Mandatory type "Language".
*/
type: "Language";
/**
* The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form">lexical representation</a> of an RDF Language Tag.
*/
languageTag: LANGTAG;
}
/**
* Matchs an <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag">RDF Language Tag</a> starting with the character sequence in {@link stem}.
*/
export interface LanguageStem {
/**
* Mandatory type "LanguageStem".
*/
type: "LanguageStem";
/**
* substring of Language Tag to be matched.
*/
stem: LANGTAG;
}
export type languageRangeStem = string | Wildcard;
export type languageRangeExclusion = string | LanguageStem;
/**
* Filters a matching <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-langugae-tag">RDF Language Tag</a>s through a list of exclusions.
* The initial match is made on an Language Tag stem per {@link Language TagStem} or a {@link Wildcard} to accept any Language Tag.
* The {@link exclusion}s are either specific Language Tags or {@link Language TagStem}s.
*/
export interface LanguageStemRange {
/**
* Mandatory type "LanguageStemRange".
*/
type: "LanguageStemRange";
/**
* substring of Language-Tag to be matched or a {@link Wildcard} matching any Language Tag.
*/
stem: languageRangeStem;
/**
* Language Tags or {@link LanguageStem}s to exclude.
*/
exclusions: languageRangeExclusion[]; // +
}
/**
* An empty object signifying than any item may be matched.
* This is used in {@link IriStemRange}, {@link LiteralStemRange} and {@link LanguageStemRange}.
*/
export interface Wildcard {
/**
* Mandatory type "Wildcard".
*/
type: "Wildcard";
}
/**
* A collection of {@link tripleExpr}s which must be matched by <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-triple">RDF Triple</a>s in conformance data.
*/
export interface Shape extends semactsAndAnnotations {
/**
* Mandatory type "Shape".
*/
type: "Shape";
/**
* Only the predicates mentioned in the {@link expression} may appear in conformant data.
*/
closed?: BOOL | undefined;
/**
* Permit extra triples with these predicates to appear in triples which don't match any {@link TripleConstraint}s mentioned in the {@link expression}.
*/
extra?: IRIREF[] | undefined;
/**
* List of one or more {@link shapeExprOrRef}s that a neighborhood must satisfy in order to conform to this shape.
*/
extends?: shapeExprOrRef[];
/**
* A tree of {@link tripleExpr}s specifying a set triples into or out of conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-node">RDF Nodes</a>.
*/
expression?: tripleExprOrRef | undefined;
}
/**
* Union of triple expression types.
* @see <a href="http://shex.io/shex-semantics/#dfn-tripleexpr">ShEx tripleExpr definition</a>
*/
export type tripleExpr = EachOf | OneOf | TripleConstraint;
/**
* A tripleExpr or a label to one.
* @see <a href="http://shex.io/shex-semantics/#dfn-tripleexpr">ShEx tripleExpr definition</a>
*/
export type tripleExprOrRef = tripleExpr | tripleExprRef;
/**
* Common attributes appearing in every form of {@link tripleExpr}.
*/
export interface tripleExprBase extends semactsAndAnnotations {
/**
* Optional identifier for {@link tripleExpr}s for reference by {@link tripleExprRef}.
* The identifier is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
id?: tripleExprLabel | undefined;
/**
* Minimum number of times matching triples must appear in conformant data.
*/
min?: INTEGER | undefined;
/**
* Maximum number of times matching triples must appear in conformant data.
*/
max?: INTEGER | undefined;
}
/**
* A list of of triple expressions; considered conformant if there is some conforming mapping of the examined triples to the {@link #tripleExprs}.
* @see <a href="http://shex.io/shex-semantics/#dfn-eachof">ShEx EachOf definition</a>
*/
export interface EachOf extends tripleExprBase {
/**
* Mandatory type "EachOf".
*/
type: "EachOf";
expressions: tripleExprOrRef[]; // {2,}
}
/**
* An exclusive choice of triple expressions; considered conformant if exactly one of {@link #shapeExprs} conforms.
* @see <a href="http://shex.io/shex-semantics/#dfn-oneof">ShEx OneOf definition</a>
*/
export interface OneOf extends tripleExprBase {
/**
* Mandatory type "OneOf".
*/
type: "OneOf";
expressions: tripleExprOrRef[]; // {2,}
}
/**
* A template matching a number of triples attached to the node being validated.
*/
export interface TripleConstraint extends tripleExprBase {
/**
* Mandatory type "TripleConstraint".
*/
type: "TripleConstraint";
/**
* If false, the TripleConstraint matches the a triple composed of a focus node, the {@link predicate} and an object matching the (optional) {@link shapeExpr}.
* If true, the TripleConstraint matches the a triple composed of a subject matching the (optional) {@link shapeExpr}, the {@link predicate} and focus node.
*/
inverse?: BOOL | undefined;
/**
* The predicate expected in a matching <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-triple">RDF Triple</a>.
*/
predicate: IRIREF;
/**
* A {@link shapeExpr} matching a conformant <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-triple">RDF Triple</a>s subject or object, depending on the value of {@link inverse}.
*/
valueExpr?: shapeExprOrRef | undefined;
}
/**
* A reference a triple expression.
* The reference is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
export type tripleExprRef = tripleExprLabel;
/**
* An identifier for a triple expression.
* The identifier is an <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">IRI</a> or a <a href="https://www.w3.org/TR/json-ld11/#identifying-blank-nodes">BlankNode</a>
* as expressed in <a href="https://www.w3.org/TR/json-ld11/">JSON-LD 1.1</a>.
*/
export type tripleExprLabel = IRIREF | BNODE;
/**
* An extension point for Shape Expressions allowing external code to be invoked during validation.
*/
export interface SemAct {
/**
* Mandatory type "SemAct".
*/
type: "SemAct";
/*
* Identifier of the language for this semantic action.
*/
name: IRIREF;
/*
* The actual code to be interpreted/executed.
* This may be kept separate from the ShEx containing the schema by including only {@link name}s in the schema.
*/
code?: STRING | undefined;
}
/**
* An assertion about some part of a ShEx schema which has no affect on conformance checking.
* These can be useful for documentation, provenance tracking, form generation, etch.
*/
export interface Annotation {
/**
* Mandatory type "Annotation".
*/
type: "Annotation";
/**
* The <a href="https://www.w3.org/TR/json-ld11/#node-identifiers">RDF Predicate</a> of the annotation.
*/
predicate: IRI;
/**
* A value for the above {@link predicate}.
*/
object: objectValue;
}
export type IRIREF = string;
export type BNODE = string;
export type INTEGER = number;
export type STRING = string;
export type DECIMAL = number;
export type DOUBLE = number;
export type LANGTAG = string;
export type BOOL = boolean;
export type IRI = string;

@ -159,14 +159,14 @@ There are two fields for the primitive sub-traverser:
- `type`: The typescript type corresponding to this primitive.
### Creating a traverser definition
Typescript typings aren't available at runtime, so the next step is to translate the `TraverserTypes` that we made into a standard JSON object called a "TraverserDefinition". But, don't worry! This will be easy. If you define a variable as a `TraverserDefinition<TraverserType>`, your IDE's IntelliSense will be able to direct you through exactly what to fill out, as seen below.
Typescript typings aren't available at runtime, so the next step is to translate the `TraverserTypes` that we made into a standard JSON object called a "TraverserDefinitions". But, don't worry! This will be easy. If you define a variable as a `TraverserDefinitions<TraverserType>`, your IDE's IntelliSense will be able to direct you through exactly what to fill out, as seen below.
![Traverse Definition IntelliSense](/tutorialImages/TraveserDefinitionIntellisense.png)
In our example, the TraverserDefinition looks like:
In our example, the TraverserDefinitions looks like:
```typescript
const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> = {
const avatarTraverserDefinitions: TraverserDefinitions<AvatarTraverserTypes> = {
Element: {
kind: "primitive",
},
@ -193,7 +193,7 @@ const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> = {
```
#### Defining a Union Selector
The only part of the TraverserDefinition that isn't just blindly following IntelliSense is the `selector` on a Union sub-traverser. A `selector` is given the item and should return the TypeName corresponding to the item.
The only part of the TraverserDefinitions that isn't just blindly following IntelliSense is the `selector` on a Union sub-traverser. A `selector` is given the item and should return the TypeName corresponding to the item.
In the above example, `"Bender"` is returned if the given item has a `"element"` property because a `"NonBender"` does not include an `"element"` property.
@ -204,7 +204,7 @@ In our example, this is how we instantiate the traverser
```typescript
const avatarTraverser = new Traverser<AvatarTraverserTypes>(
avatarTraverserDefinition
avatarTraverserDefinitions
);
```

@ -1,5 +1,10 @@
import type { TraverserDefinition, ValidateTraverserTypes } from "../src";
import { Traverser } from "../src";
import type {
ValidateTraverserTypes,
ItemNamed,
TraverserDefinitions,
} from "../src";
import { InstanceGraph } from "../src/instanceGraph/instanceGraph";
import type { ParentIdentifiers } from "../src/ReverseRelationshipTypes";
async function run() {
/**
@ -56,7 +61,7 @@ async function run() {
};
NonBender: {
kind: "interface";
type: Bender;
type: NonBender;
properties: {
friends: "Person";
};
@ -68,33 +73,42 @@ async function run() {
};
}>;
type PersonParentIdentifiers = ParentIdentifiers<
AvatarTraverserTypes,
"Person"
>;
const sample: PersonParentIdentifiers = ["Bender", "friends"];
console.log(sample);
/**
* Create the traverser definition
*/
const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> = {
Element: {
kind: "primitive",
},
Bender: {
kind: "interface",
properties: {
element: "Element",
friends: "Person",
const avatarTraverserDefinition: TraverserDefinitions<AvatarTraverserTypes> =
{
Element: {
kind: "primitive",
},
},
NonBender: {
kind: "interface",
properties: {
friends: "Person",
Bender: {
kind: "interface",
properties: {
element: "Element",
friends: "Person",
},
},
},
Person: {
kind: "union",
selector: (item) => {
return (item as Bender).element ? "Bender" : "NonBender";
NonBender: {
kind: "interface",
properties: {
friends: "Person",
},
},
},
};
Person: {
kind: "union",
selector: (item) => {
return (item as Bender).element ? "Bender" : "NonBender";
},
},
};
/**
* Instantiate the Traverser
@ -103,65 +117,65 @@ async function run() {
avatarTraverserDefinition,
);
/**
* Create a visitor
*/
const avatarVisitor = avatarTraverser.createVisitor<undefined>({
Element: async (item) => {
console.log(`Element: ${item}`);
},
Bender: {
visitor: async (item) => {
console.log(`Bender: ${item.name}`);
},
properties: {
element: async (item) => {
console.log(`Bender.element: ${item}`);
},
},
},
NonBender: {
visitor: async (item) => {
console.log(`NonBender: ${item.name}`);
},
},
Person: async (item) => {
console.log(`Person: ${item.name}`);
},
});
// /**
// * Create a visitor
// */
// const avatarVisitor = avatarTraverser.createVisitor<undefined>({
// Element: async (item) => {
// console.log(`Element: ${item}`);
// },
// Bender: {
// visitor: async (item) => {
// console.log(`Bender: ${item.name}`);
// },
// properties: {
// element: async (item) => {
// console.log(`Bender.element: ${item}`);
// },
// },
// },
// NonBender: {
// visitor: async (item) => {
// console.log(`NonBender: ${item.name}`);
// },
// },
// Person: async (item) => {
// console.log(`Person: ${item.name}`);
// },
// });
/**
* Run the visitor on data
*/
console.log(
"############################## Visitor Logs ##############################",
);
await avatarVisitor.visit(aang, "Bender", undefined);
// /**
// * Run the visitor on data
// */
// console.log(
// "############################## Visitor Logs ##############################",
// );
// await avatarVisitor.visit(aang, "Bender", undefined);
/**
* Create a visitor that uses context
*/
interface AvatarCountingVisitorContext {
numberOfBenders: number;
}
const avatarCountingVisitor =
avatarTraverser.createVisitor<AvatarCountingVisitorContext>({
Bender: {
visitor: async (item, context) => {
context.numberOfBenders++;
},
},
});
// /**
// * Create a visitor that uses context
// */
// interface AvatarCountingVisitorContext {
// numberOfBenders: number;
// }
// const avatarCountingVisitor =
// avatarTraverser.createVisitor<AvatarCountingVisitorContext>({
// Bender: {
// visitor: async (item, context) => {
// context.numberOfBenders++;
// },
// },
// });
/**
* Run the counting visitor
*/
console.log(
"############################## Found Number of Benders Using Visitor ##############################",
);
const countContext: AvatarCountingVisitorContext = { numberOfBenders: 0 };
await avatarCountingVisitor.visit(aang, "Bender", countContext);
console.log(countContext.numberOfBenders);
// /**
// * Run the counting visitor
// */
// console.log(
// "############################## Found Number of Benders Using Visitor ##############################",
// );
// const countContext: AvatarCountingVisitorContext = { numberOfBenders: 0 };
// await avatarCountingVisitor.visit(aang, "Bender", countContext);
// console.log(countContext.numberOfBenders);
/**
* Set up a transformer

@ -1,126 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {
InterfaceType,
PrimitiveType,
TraverserTypes,
UnionType,
} from ".";
export type InterfaceVisitorFunction<
Types extends TraverserTypes<any>,
Type extends InterfaceType<keyof Types>,
Context,
> = (originalData: Type["type"], context: Context) => Promise<void>;
export type InterfaceVisitorPropertyFunction<
Types extends TraverserTypes<any>,
Type extends InterfaceType<keyof Types>,
PropertyName extends keyof Type["properties"],
Context,
> = (
originalData: Types[Type["properties"][PropertyName]]["type"],
context: Context,
) => Promise<void>;
export type InterfaceVisitorDefinition<
Types extends TraverserTypes<any>,
Type extends InterfaceType<keyof Types>,
Context,
> = {
visitor: InterfaceVisitorFunction<Types, Type, Context>;
properties: {
[PropertyName in keyof Type["properties"]]: InterfaceVisitorPropertyFunction<
Types,
Type,
PropertyName,
Context
>;
};
};
export type UnionVisitorFunction<
Types extends TraverserTypes<any>,
Type extends UnionType<keyof Types>,
Context,
> = (originalData: Type["type"], context: Context) => Promise<void>;
export type UnionVisitorDefinition<
Types extends TraverserTypes<any>,
Type extends UnionType<keyof Types>,
Context,
> = UnionVisitorFunction<Types, Type, Context>;
export type PrimitiveVisitorFunction<Type extends PrimitiveType, Context> = (
originalData: Type["type"],
context: Context,
) => Promise<void>;
export type PrimitiveVisitorDefinition<
Type extends PrimitiveType,
Context,
> = PrimitiveVisitorFunction<Type, Context>;
export type VisitorDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Context,
> = Types[TypeName] extends InterfaceType<keyof Types>
? InterfaceVisitorDefinition<Types, Types[TypeName], Context>
: Types[TypeName] extends UnionType<keyof Types>
? UnionVisitorDefinition<Types, Types[TypeName], Context>
: Types[TypeName] extends PrimitiveType
? PrimitiveVisitorDefinition<Types[TypeName], Context>
: never;
export type Visitors<Types extends TraverserTypes<any>, Context> = {
[TypeName in keyof Types]: VisitorDefinition<Types, TypeName, Context>;
};
/**
* Input
*/
export type InterfaceVisitorInputDefinition<
Types extends TraverserTypes<any>,
Type extends InterfaceType<keyof Types>,
Context,
> = {
visitor: InterfaceVisitorFunction<Types, Type, Context>;
properties?: Partial<{
[PropertyName in keyof Type["properties"]]: InterfaceVisitorPropertyFunction<
Types,
Type,
PropertyName,
Context
>;
}>;
};
export type UnionVisitorInputDefinition<
Types extends TraverserTypes<any>,
Type extends UnionType<keyof Types>,
Context,
> = UnionVisitorFunction<Types, Type, Context>;
export type PrimitiveVisitorInputDefinition<
Type extends PrimitiveType,
Context,
> = PrimitiveVisitorFunction<Type, Context>;
export type VisitorInputDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Context,
> = Types[TypeName] extends InterfaceType<keyof Types>
? InterfaceVisitorInputDefinition<Types, Types[TypeName], Context>
: Types[TypeName] extends UnionType<keyof Types>
? UnionVisitorInputDefinition<Types, Types[TypeName], Context>
: Types[TypeName] extends PrimitiveType
? PrimitiveVisitorInputDefinition<Types[TypeName], Context>
: never;
export type VisitorsInput<
Types extends TraverserTypes<any>,
Context,
> = Partial<{
[TypeName in keyof Types]: VisitorInputDefinition<Types, TypeName, Context>;
}>;

@ -1,9 +1,18 @@
export * from "./TraverserTypes";
export * from "./traverser/TraverserTypes";
export * from "./UtilTypes";
export * from "./TraverserDefinition";
export * from "./TransformerReturnTypes";
export * from "./TransformerReturnTypesDefaults";
export * from "./Traverser";
export * from "./Transformer";
export * from "./Visitor";
export * from "./Visitors";
export * from "./traverser/TraverserDefinition";
export * from "./transformer/TransformerReturnTypes";
export * from "./transformer/TransformerReturnTypesDefaults";
export * from "./traverser/Traverser";
export * from "./transformer/Transformer";
export * from "./visitor/Visitor";
export * from "./visitor/Visitors";
export * from "./instanceGraph/InstanceGraph";
export * from "./instanceGraph/ReverseRelationshipTypes";
export * from "./instanceGraph/nodes/InstanceNode";
export * from "./instanceGraph/nodes/InterfaceInstanceNode";
export * from "./instanceGraph/nodes/PrimitiveInstanceNode";
export * from "./instanceGraph/nodes/UnionInstanceNode";

@ -0,0 +1,47 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { MultiMap } from "../transformer/transformerSubTraversers/util/MultiMap";
import type { TraverserTypes } from "../traverser/TraverserTypes";
import {
createInstanceNodeFor,
type InstanceNodeFor,
} from "./nodes/createInstanceNodeFor";
import type { TraverserDefinitions } from "../traverser/TraverserDefinition";
export class InstanceGraph<Types extends TraverserTypes<any>> {
protected objectMap: MultiMap<
object,
keyof Types,
InstanceNodeFor<Types, keyof Types>
> = new MultiMap();
public readonly traverserDefinitions: TraverserDefinitions<Types>;
constructor(traverserDefinitions: TraverserDefinitions<Types>) {
this.traverserDefinitions = traverserDefinitions;
}
getNodeFor<TypeName extends keyof Types>(
instance: unknown,
typeName: TypeName,
): InstanceNodeFor<Types, TypeName> {
let potentialNode;
// Skip the cache for Primitive Nodes
const isCachable =
this.traverserDefinitions[typeName].kind !== "primitive" &&
typeof instance === "object" &&
instance != null;
if (isCachable) {
potentialNode = this.objectMap.get(instance, typeName);
}
if (potentialNode) return potentialNode as InstanceNodeFor<Types, TypeName>;
const newNode = createInstanceNodeFor(instance, typeName, this);
if (isCachable) {
// TODO: Figure out why this is a ts error
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.objectMap.set(instance, typeName, newNode);
}
newNode._recursivelyBuildChildren();
return newNode;
}
}

@ -0,0 +1,82 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {
InterfaceType,
PrimitiveType,
TraverserTypes,
UnionType,
} from "../traverser/TraverserTypes";
export type InterfaceReverseRelationshipIndentifier<
Types extends TraverserTypes<any>,
ChildName extends keyof Types,
PotentialParentName extends keyof Types,
PotentialParentType extends InterfaceType<keyof Types>,
> = {
[PropertyField in keyof PotentialParentType["properties"]]: ChildName extends PotentialParentType["properties"][PropertyField]
? [PotentialParentName, PropertyField]
: never;
}[keyof PotentialParentType["properties"]];
export type UnionReverseRelationshipIndentifier<
Types extends TraverserTypes<any>,
ChildName extends keyof Types,
PotentialParentName extends keyof Types,
PotentialParentType extends UnionType<keyof Types>,
> = ChildName extends PotentialParentType["typeNames"]
? [PotentialParentName]
: never;
export type PrimitiveReverseRelationshipIndentifier<
Types extends TraverserTypes<any>,
_ChildName extends keyof Types,
_PotentialParentName extends keyof Types,
_PotentialParentType extends PrimitiveType,
> = never;
export type BaseReverseRelationshipIndentifier<
Types extends TraverserTypes<any>,
ChildName extends keyof Types,
PotentialParentName extends keyof Types,
> = Types[PotentialParentName] extends InterfaceType<keyof Types>
? InterfaceReverseRelationshipIndentifier<
Types,
ChildName,
PotentialParentName,
Types[PotentialParentName]
>
: Types[PotentialParentName] extends UnionType<keyof Types>
? UnionReverseRelationshipIndentifier<
Types,
ChildName,
PotentialParentName,
Types[PotentialParentName]
>
: Types[PotentialParentName] extends PrimitiveType
? PrimitiveReverseRelationshipIndentifier<
Types,
ChildName,
PotentialParentName,
Types[PotentialParentName]
>
: never;
export type BaseReverseRelationshipIndentifiers<
Types extends TraverserTypes<any>,
ChildName extends keyof Types,
> = {
[ParentName in keyof Types]: BaseReverseRelationshipIndentifier<
Types,
ChildName,
ParentName
>;
};
export type ParentIdentifiers<
Types extends TraverserTypes<any>,
ChildName extends keyof Types,
> = {
[CN in ChildName]: BaseReverseRelationshipIndentifiers<
Types,
CN
>[keyof Types];
}[ChildName];

@ -0,0 +1,77 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { TraverserDefinition } from "../..";
import type { ParentIdentifiers } from "../../instanceGraph/ReverseRelationshipTypes";
import type { TraverserTypes } from "../../traverser/TraverserTypes";
import type { InstanceGraph } from "../InstanceGraph";
import type { InstanceNodeFor } from "./createInstanceNodeFor";
export abstract class InstanceNode<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends Types[TypeName],
> {
readonly graph: InstanceGraph<Types>;
readonly instance: Type["type"];
readonly typeName: TypeName;
protected readonly parents: Record<
string,
Set<InstanceNodeFor<Types, ParentIdentifiers<Types, TypeName>[0]>>
> = {};
constructor(
graph: InstanceGraph<Types>,
instance: Type["type"],
typeName: TypeName,
) {
this.graph = graph;
this.instance = instance;
this.typeName = typeName;
}
private getParentKey(
identifiers: ParentIdentifiers<Types, TypeName>,
): string {
return identifiers.join("|");
}
public _setParent<Identifiers extends ParentIdentifiers<Types, TypeName>>(
identifiers: Identifiers,
parentNode: InstanceNodeFor<Types, Identifiers[0]>,
) {
const parentKey = this.getParentKey(identifiers);
if (!this.parents[parentKey]) this.parents[parentKey] = new Set();
this.parents[parentKey].add(parentNode);
}
public parent<Identifiers extends ParentIdentifiers<Types, TypeName>>(
...identifiers: Identifiers
): InstanceNodeFor<Types, Identifiers[0]>[] {
return Array.from(this.parents[this.getParentKey(identifiers)] ?? []);
}
public allParents(): InstanceNodeFor<
Types,
ParentIdentifiers<Types, TypeName>[0]
>[] {
return Object.values(this.parents)
.map((parentSet) => Array.from(parentSet))
.flat();
}
public abstract _setChild(...props: any[]): void;
public abstract child(...props: any[]): any;
/**
* Returns all nodes that are children of this node reguardless of their edge
*/
public abstract allChildren(): InstanceNodeFor<Types, any>[];
public get traverserDefinition(): TraverserDefinition<Types, TypeName, Type> {
return this.graph.traverserDefinitions[
this.typeName
] as unknown as TraverserDefinition<Types, TypeName, Type>;
}
public abstract _recursivelyBuildChildren(): void;
}

@ -0,0 +1,100 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { ApplyArrayAndUndefined } from "../../transformer/TransformerReturnTypesDefaults";
import type {
InterfaceType,
TraverserTypes,
} from "../../traverser/TraverserTypes";
import type { InstanceGraph } from "../InstanceGraph";
import type { InstanceNodeFor } from "./createInstanceNodeFor";
import { InstanceNode } from "./InstanceNode";
/**
* Helper Function
*/
export type InterfacePropertyNode<
Types extends TraverserTypes<any>,
Type extends InterfaceType<keyof Types>,
PropertyName extends keyof Type["properties"],
> = ApplyArrayAndUndefined<
Type["type"][PropertyName],
InstanceNodeFor<Types, Type["properties"][PropertyName]>
>;
/**
* Class
*/
export class InterfaceInstanceNode<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
> extends InstanceNode<Types, TypeName, Type> {
private children: {
[PropertyName in keyof Type["properties"]]: InterfacePropertyNode<
Types,
Type,
PropertyName
>;
};
constructor(
graph: InstanceGraph<Types>,
instance: Type["type"],
typeName: TypeName,
) {
super(graph, instance, typeName);
// This will eventually be filled out by the recursivelyBuildChildren method
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.children = {};
}
public _setChild<PropertyName extends keyof Type["properties"]>(
propertyName: PropertyName,
child: InterfacePropertyNode<Types, Type, PropertyName>,
): void {
this.children[propertyName] = child;
}
public child<PropertyName extends keyof Type["properties"]>(
propertyName: PropertyName,
): InterfacePropertyNode<Types, Type, PropertyName> {
return this.children[propertyName];
}
public allChildren(): InstanceNodeFor<
Types,
Type["properties"][keyof Type["properties"]]
>[] {
return Object.values(this.children).flat();
}
public _recursivelyBuildChildren() {
Object.entries(this.instance).forEach(
([propertyName, value]: [keyof Type["properties"], unknown]) => {
const propertyTypeName =
// Fancy typescript doesn't work until you actually give it a type
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.traverserDefinition.properties[propertyName];
if (!propertyTypeName) return;
const initChildNode = (val: unknown) => {
const node = this.graph.getNodeFor(val, propertyTypeName);
// Fancy typescript doesn't work until you actually give it a type
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
node._setParent([this.typeName, propertyName], this);
return node;
};
const childNode = (Array.isArray(value)
? value.map((val) => initChildNode(val))
: initChildNode(value)) as unknown as InterfacePropertyNode<
Types,
Type,
keyof Type["properties"]
>;
this._setChild(propertyName, childNode);
},
);
}
}

@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {
PrimitiveType,
TraverserTypes,
} from "../../traverser/TraverserTypes";
import { InstanceNode } from "./InstanceNode";
export class PrimitiveInstanceNode<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends PrimitiveType & Types[TypeName],
> extends InstanceNode<Types, TypeName, Type> {
public _setChild(): void {
return;
}
public child() {
return undefined;
}
public allChildren(): [] {
return [];
}
public _recursivelyBuildChildren(): void {
return;
}
}

@ -0,0 +1,34 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { TraverserTypes, UnionType } from "../../traverser/TraverserTypes";
import type { InstanceNodeFor } from "./createInstanceNodeFor";
import { InstanceNode } from "./InstanceNode";
export class UnionInstanceNode<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends UnionType<keyof Types> & Types[TypeName],
> extends InstanceNode<Types, TypeName, Type> {
private childNode: InstanceNodeFor<Types, Type["typeNames"]> | undefined;
public _setChild(child: InstanceNodeFor<Types, Type["typeNames"]>): void {
this.childNode = child;
}
public child(): InstanceNodeFor<Types, Type["typeNames"]> {
if (!this.childNode) throw new Error("Child node not yet set");
return this.childNode;
}
public allChildren(): InstanceNodeFor<Types, Type["typeNames"]>[] {
return this.childNode ? [this.childNode] : [];
}
public _recursivelyBuildChildren(): void {
const childType = this.traverserDefinition.selector(this.instance);
const childNode = this.graph.getNodeFor(this.instance, childType);
this._setChild(childNode);
// Fancy typescript only works once the type is provided
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
childNode._setParent([this.typeName], this);
}
}

@ -0,0 +1,54 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {
InterfaceType,
PrimitiveType,
TraverserTypes,
UnionType,
} from "../../traverser/TraverserTypes";
import type { InstanceGraph } from "../instanceGraph";
import { InterfaceInstanceNode } from "./InterfaceInstanceNode";
import { PrimitiveInstanceNode } from "./PrimitiveInstanceNode";
import { UnionInstanceNode } from "./UnionInstanceNode";
export type InstanceNodeFor<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
> = {
[TN in TypeName]: Types[TN] extends InterfaceType<keyof Types>
? InterfaceInstanceNode<Types, TN, Types[TN]>
: Types[TN] extends UnionType<keyof Types>
? UnionInstanceNode<Types, TN, Types[TN]>
: Types[TN] extends PrimitiveType
? PrimitiveInstanceNode<Types, TypeName, Types[TN]>
: never;
}[TypeName];
export function createInstanceNodeFor<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
>(
instance: unknown,
typeName: TypeName,
graph: InstanceGraph<Types>,
): InstanceNodeFor<Types, TypeName> {
switch (graph.traverserDefinitions[typeName].kind) {
case "interface":
return new InterfaceInstanceNode(
graph,
instance,
typeName,
) as InstanceNodeFor<Types, TypeName>;
case "union":
return new UnionInstanceNode(
graph,
instance,
typeName,
) as InstanceNodeFor<Types, TypeName>;
case "primitive":
return new PrimitiveInstanceNode(
graph,
instance,
typeName,
) as InstanceNodeFor<Types, TypeName>;
}
}

@ -8,11 +8,11 @@ import type {
PrimitiveReturnType,
PrimitiveType,
TransformerInputReturnTypes,
TraverserDefinition,
TraverserDefinitions,
TraverserTypes,
UnionReturnType,
UnionType,
} from ".";
} from "..";
import { transformerParentSubTraverser } from "./transformerSubTraversers/TransformerParentSubTraverser";
import { CircularDepenedencyAwaiter } from "./transformerSubTraversers/util/CircularDependencyAwaiter";
import { MultiMap } from "./transformerSubTraversers/util/MultiMap";
@ -28,6 +28,7 @@ import type {
UnionTransformerDefinition,
UnionTransformerInputDefinition,
} from "./Transformers";
import { InstanceGraph } from "../instanceGraph/instanceGraph";
// TODO: Lots of "any" in this file. I'm just done with fancy typescript,
// but if I ever feel so inclined, I should fix this in the future.
@ -38,7 +39,7 @@ export class Transformer<
InputReturnTypes extends TransformerInputReturnTypes<Types>,
Context = undefined,
> {
private traverserDefinition: TraverserDefinition<Types>;
private traverserDefinition: TraverserDefinitions<Types>;
private transformers: Transformers<
Types,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
@ -46,7 +47,7 @@ export class Transformer<
>;
constructor(
traverserDefinition: TraverserDefinition<Types>,
traverserDefinition: TraverserDefinitions<Types>,
transformers: TransformersInput<Types, InputReturnTypes, Context>,
) {
this.traverserDefinition = traverserDefinition;
@ -54,12 +55,14 @@ export class Transformer<
}
private applyDefaultInterfaceTransformerProperties<
Type extends InterfaceType<keyof Types>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
ReturnType extends InterfaceReturnType<Type>,
>(
typeName: keyof Types,
typePropertiesInput: InterfaceTransformerInputDefinition<
Types,
TypeName,
Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType,
@ -67,6 +70,7 @@ export class Transformer<
>["properties"],
): InterfaceTransformerDefinition<
Types,
TypeName,
Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType,
@ -89,6 +93,7 @@ export class Transformer<
return agg;
}, {}) as InterfaceTransformerDefinition<
Types,
TypeName,
Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType,
@ -97,12 +102,14 @@ export class Transformer<
}
private applyDefaultInterfaceTransformer<
Type extends InterfaceType<keyof Types>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
ReturnType extends InterfaceReturnType<Type>,
>(
typeName: keyof Types,
typeInput?: InterfaceTransformerInputDefinition<
Types,
TypeName,
Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType,
@ -110,6 +117,7 @@ export class Transformer<
>,
): InterfaceTransformerDefinition<
Types,
TypeName,
Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType,
@ -139,11 +147,13 @@ export class Transformer<
}
private applyDefaultUnionTransformer<
Type extends UnionType<keyof Types>,
TypeName extends keyof Types,
Type extends UnionType<keyof Types> & Types[TypeName],
ReturnType extends UnionReturnType,
>(
typeInput?: UnionTransformerInputDefinition<
Types,
TypeName,
Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType,
@ -151,6 +161,7 @@ export class Transformer<
>,
): UnionTransformerDefinition<
Types,
TypeName,
Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType,
@ -168,11 +179,24 @@ export class Transformer<
}
private applyDefaultPrimitiveTransformer<
Type extends PrimitiveType,
TypeName extends keyof Types,
Type extends PrimitiveType & Types[TypeName],
ReturnType extends PrimitiveReturnType,
>(
typeInput?: PrimitiveTransformerInputDefinition<Type, ReturnType, Context>,
): PrimitiveTransformerDefinition<Type, ReturnType, Context> {
typeInput?: PrimitiveTransformerInputDefinition<
Types,
TypeName,
Type,
ReturnType,
Context
>,
): PrimitiveTransformerDefinition<
Types,
TypeName,
Type,
ReturnType,
Context
> {
if (!typeInput) {
return async (originalData) => {
return originalData;
@ -229,12 +253,15 @@ export class Transformer<
>[TypeName]["return"]
> {
const superPromise = new SuperPromise();
const instanceGraph = new InstanceGraph(this.traverserDefinition);
instanceGraph.getNodeFor(item, itemTypeName);
const toReturn = await transformerParentSubTraverser(item, itemTypeName, {
traverserDefinition: this.traverserDefinition,
transformers: this.transformers,
executingPromises: new MultiMap(),
circularDependencyAwaiter: new CircularDepenedencyAwaiter(),
superPromise,
instanceGraph,
context,
});
await superPromise.wait();

@ -3,7 +3,7 @@ import type {
PrimitiveType,
TraverserTypes,
UnionType,
} from ".";
} from "..";
/* eslint-disable @typescript-eslint/no-explicit-any */
export type InterfaceReturnType<Type extends InterfaceType<any>> = {

@ -10,7 +10,7 @@ import type {
TraverserTypes,
UnionInputReturnType,
UnionType,
} from ".";
} from "..";
export type RecursivelyFindReturnType<
Types extends TraverserTypes<any>,

@ -10,7 +10,10 @@ import type {
TraverserTypes,
UnionReturnType,
UnionType,
} from ".";
} from "..";
import type { InterfaceInstanceNode } from "../instanceGraph/nodes/InterfaceInstanceNode";
import type { PrimitiveInstanceNode } from "../instanceGraph/nodes/PrimitiveInstanceNode";
import type { UnionInstanceNode } from "../instanceGraph/nodes/UnionInstanceNode";
export type GetTransformedChildrenFunction<TransformedChildrenType> =
() => Promise<TransformedChildrenType>;
@ -21,7 +24,8 @@ export type SetReturnPointerFunction<ReturnType> = (
export type InterfaceTransformerFunction<
Types extends TraverserTypes<any>,
Type extends InterfaceType<keyof Types>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
ReturnType extends InterfaceReturnType<Type>,
Context,
> = (
@ -30,12 +34,14 @@ export type InterfaceTransformerFunction<
[PropertyName in keyof ReturnType["properties"]]: ReturnType["properties"][PropertyName];
}>,
setReturnPointer: SetReturnPointerFunction<ReturnType["return"]>,
node: InterfaceInstanceNode<Types, TypeName, Type>,
context: Context,
) => Promise<ReturnType["return"]>;
export type InterfaceTransformerPropertyFunction<
Types extends TraverserTypes<any>,
Type extends InterfaceType<keyof Types>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
ReturnTypes extends TransformerReturnTypes<Types>,
ReturnType extends InterfaceReturnType<Type>,
PropertyName extends keyof Type["properties"],
@ -45,20 +51,29 @@ export type InterfaceTransformerPropertyFunction<
getTransfromedChildren: GetTransformedChildrenFunction<
ReturnTypes[Type["properties"][PropertyName]]["return"]
>,
node: InterfaceInstanceNode<Types, TypeName, Type>,
context: Context,
) => Promise<ReturnType["properties"][PropertyName]>;
export type InterfaceTransformerDefinition<
Types extends TraverserTypes<any>,
Type extends InterfaceType<keyof Types>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
ReturnTypes extends TransformerReturnTypes<Types>,
ReturnType extends InterfaceReturnType<Type>,
Context,
> = {
transformer: InterfaceTransformerFunction<Types, Type, ReturnType, Context>;
transformer: InterfaceTransformerFunction<
Types,
TypeName,
Type,
ReturnType,
Context
>;
properties: {
[PropertyName in keyof Type["properties"]]: InterfaceTransformerPropertyFunction<
Types,
TypeName,
Type,
ReturnTypes,
ReturnType,
@ -70,7 +85,8 @@ export type InterfaceTransformerDefinition<
export type UnionTransformerFunction<
Types extends TraverserTypes<any>,
Type extends UnionType<keyof Types>,
TypeName extends keyof Types,
Type extends UnionType<keyof Types> & Types[TypeName],
ReturnTypes extends TransformerReturnTypes<Types>,
ReturnType extends UnionReturnType,
Context,
@ -80,31 +96,45 @@ export type UnionTransformerFunction<
ReturnTypes[Type["typeNames"]]["return"]
>,
setReturnPointer: SetReturnPointerFunction<ReturnType["return"]>,
node: UnionInstanceNode<Types, TypeName, Type>,
context: Context,
) => Promise<ReturnType["return"]>;
export type UnionTransformerDefinition<
Types extends TraverserTypes<any>,
Type extends UnionType<keyof Types>,
TypeName extends keyof Types,
Type extends UnionType<keyof Types> & Types[TypeName],
ReturnTypes extends TransformerReturnTypes<Types>,
ReturnType extends UnionReturnType,
Context,
> = UnionTransformerFunction<Types, Type, ReturnTypes, ReturnType, Context>;
> = UnionTransformerFunction<
Types,
TypeName,
Type,
ReturnTypes,
ReturnType,
Context
>;
export type PrimitiveTransformerFunction<
Type extends PrimitiveType,
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends PrimitiveType & Types[TypeName],
ReturnType extends PrimitiveReturnType,
Context,
> = (
originalData: Type["type"],
node: PrimitiveInstanceNode<Types, TypeName, Type>,
context: Context,
) => Promise<ReturnType["return"]>;
export type PrimitiveTransformerDefinition<
Type extends PrimitiveType,
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends PrimitiveType & Types[TypeName],
ReturnType extends PrimitiveReturnType,
Context,
> = PrimitiveTransformerFunction<Type, ReturnType, Context>;
> = PrimitiveTransformerFunction<Types, TypeName, Type, ReturnType, Context>;
export type TransformerDefinition<
Types extends TraverserTypes<any>,
@ -115,6 +145,7 @@ export type TransformerDefinition<
? ReturnTypes[TypeName] extends InterfaceReturnType<Types[TypeName]>
? InterfaceTransformerDefinition<
Types,
TypeName,
Types[TypeName],
ReturnTypes,
ReturnTypes[TypeName],
@ -125,6 +156,7 @@ export type TransformerDefinition<
? ReturnTypes[TypeName] extends UnionReturnType
? UnionTransformerDefinition<
Types,
TypeName,
Types[TypeName],
ReturnTypes,
ReturnTypes[TypeName],
@ -134,6 +166,8 @@ export type TransformerDefinition<
: Types[TypeName] extends PrimitiveType
? ReturnTypes[TypeName] extends PrimitiveReturnType
? PrimitiveTransformerDefinition<
Types,
TypeName,
Types[TypeName],
ReturnTypes[TypeName],
Context
@ -159,15 +193,23 @@ export type Transformers<
*/
export type InterfaceTransformerInputDefinition<
Types extends TraverserTypes<any>,
Type extends InterfaceType<keyof Types>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
ReturnTypes extends TransformerReturnTypes<Types>,
ReturnType extends InterfaceReturnType<Type>,
Context,
> = {
transformer: InterfaceTransformerFunction<Types, Type, ReturnType, Context>;
transformer: InterfaceTransformerFunction<
Types,
TypeName,
Type,
ReturnType,
Context
>;
properties?: Partial<{
[PropertyName in keyof Type["properties"]]: InterfaceTransformerPropertyFunction<
Types,
TypeName,
Type,
ReturnTypes,
ReturnType,
@ -179,17 +221,27 @@ export type InterfaceTransformerInputDefinition<
export type UnionTransformerInputDefinition<
Types extends TraverserTypes<any>,
Type extends UnionType<keyof Types>,
TypeName extends keyof Types,
Type extends UnionType<keyof Types> & Types[TypeName],
ReturnTypes extends TransformerReturnTypes<Types>,
ReturnType extends UnionReturnType,
Context,
> = UnionTransformerFunction<Types, Type, ReturnTypes, ReturnType, Context>;
> = UnionTransformerFunction<
Types,
TypeName,
Type,
ReturnTypes,
ReturnType,
Context
>;
export type PrimitiveTransformerInputDefinition<
Type extends PrimitiveType,
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends PrimitiveType & Types[TypeName],
ReturnType extends PrimitiveReturnType,
Context,
> = PrimitiveTransformerFunction<Type, ReturnType, Context>;
> = PrimitiveTransformerFunction<Types, TypeName, Type, ReturnType, Context>;
export type TransformerInputDefinition<
Types extends TraverserTypes<any>,
@ -200,6 +252,7 @@ export type TransformerInputDefinition<
? ReturnTypes[TypeName] extends InterfaceReturnType<Types[TypeName]>
? InterfaceTransformerInputDefinition<
Types,
TypeName,
Types[TypeName],
ReturnTypes,
ReturnTypes[TypeName],
@ -210,6 +263,7 @@ export type TransformerInputDefinition<
? ReturnTypes[TypeName] extends UnionReturnType
? UnionTransformerInputDefinition<
Types,
TypeName,
Types[TypeName],
ReturnTypes,
ReturnTypes[TypeName],
@ -219,6 +273,8 @@ export type TransformerInputDefinition<
: Types[TypeName] extends PrimitiveType
? ReturnTypes[TypeName] extends PrimitiveReturnType
? PrimitiveTransformerInputDefinition<
Types,
TypeName,
Types[TypeName],
ReturnTypes[TypeName],
Context

@ -1,20 +1,21 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { TraverserTypes } from "..";
import type { TraverserTypes } from "../../";
import type {
InterfaceReturnType,
TransformerReturnTypes,
} from "../TransformerReturnTypes";
import type { InterfaceTransformerDefinition } from "../Transformers";
import type { InterfaceTraverserDefinition } from "../TraverserDefinition";
import type { InterfaceType } from "../TraverserTypes";
import type { InterfaceTraverserDefinition } from "../../traverser/TraverserDefinition";
import type { InterfaceType } from "../../traverser/TraverserTypes";
import { transformerParentSubTraverser } from "./TransformerParentSubTraverser";
import type { TransformerSubTraverserGlobals } from "./util/transformerSubTraverserTypes";
import type { InterfaceInstanceNode } from "../../instanceGraph/nodes/InterfaceInstanceNode";
export async function transformerInterfaceSubTraverser<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
ReturnTypes extends TransformerReturnTypes<Types>,
Type extends InterfaceType<keyof Types>,
Type extends InterfaceType<keyof Types> & Types[TypeName],
ReturnType extends InterfaceReturnType<Type>,
Context,
>(
@ -40,6 +41,7 @@ export async function transformerInterfaceSubTraverser<
itemTypeName
] as unknown as InterfaceTransformerDefinition<
Types,
TypeName,
Type,
ReturnTypes,
ReturnType,
@ -99,6 +101,14 @@ export async function transformerInterfaceSubTraverser<
return toReturn;
}
},
globals.instanceGraph.getNodeFor(
item,
itemTypeName,
) as unknown as InterfaceInstanceNode<
Types,
TypeName,
Type
>,
globals.context,
);
return [propertyName, transformedProperty];
@ -111,6 +121,10 @@ export async function transformerInterfaceSubTraverser<
(input) => {
resolve(input);
},
globals.instanceGraph.getNodeFor(
item,
itemTypeName,
) as unknown as InterfaceInstanceNode<Types, TypeName, Type>,
globals.context,
);
resolve(transformedObject);

@ -1,5 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { BaseReturnType, BaseTraverserTypes, TraverserTypes } from "..";
import type {
BaseReturnType,
BaseTraverserTypes,
TraverserTypes,
} from "../../";
import type { TransformerReturnTypes } from "../TransformerReturnTypes";
import { transformerInterfaceSubTraverser } from "./TransformerInterfaceSubTraverser";
import { transformerPrimitiveSubTraverser } from "./TransformerPrimitiveSubTraverser";

@ -1,18 +1,19 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { TraverserTypes } from "..";
import type { TraverserTypes } from "../../";
import type { PrimitiveInstanceNode } from "../../instanceGraph/nodes/PrimitiveInstanceNode";
import type {
PrimitiveReturnType,
TransformerReturnTypes,
} from "../TransformerReturnTypes";
import type { PrimitiveTransformerDefinition } from "../Transformers";
import type { PrimitiveType } from "../TraverserTypes";
import type { PrimitiveType } from "../../traverser/TraverserTypes";
import type { TransformerSubTraverserGlobals } from "./util/transformerSubTraverserTypes";
export async function transformerPrimitiveSubTraverser<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
ReturnTypes extends TransformerReturnTypes<Types>,
Type extends PrimitiveType,
Type extends PrimitiveType & Types[TypeName],
ReturnType extends PrimitiveReturnType,
Context,
>(
@ -23,6 +24,19 @@ export async function transformerPrimitiveSubTraverser<
const { transformers } = globals;
const transformer = transformers[
itemTypeName
] as unknown as PrimitiveTransformerDefinition<Type, ReturnType, Context>;
return transformer(item, globals.context);
] as unknown as PrimitiveTransformerDefinition<
Types,
TypeName,
Type,
ReturnType,
Context
>;
return transformer(
item,
globals.instanceGraph.getNodeFor(
item,
itemTypeName,
) as unknown as PrimitiveInstanceNode<Types, TypeName, Type>,
globals.context,
);
}

@ -1,12 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { TraverserTypes } from "..";
import type { TraverserTypes } from "../../";
import type { UnionInstanceNode } from "../../instanceGraph/nodes/UnionInstanceNode";
import type {
TransformerReturnTypes,
UnionReturnType,
} from "../TransformerReturnTypes";
import type { UnionTransformerDefinition } from "../Transformers";
import type { UnionTraverserDefinition } from "../TraverserDefinition";
import type { UnionType } from "../TraverserTypes";
import type { UnionTraverserDefinition } from "../../traverser/TraverserDefinition";
import type { UnionType } from "../../traverser/TraverserTypes";
import { transformerParentSubTraverser } from "./TransformerParentSubTraverser";
import type { TransformerSubTraverserGlobals } from "./util/transformerSubTraverserTypes";
@ -14,7 +15,7 @@ export async function transformerUnionSubTraverser<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
ReturnTypes extends TransformerReturnTypes<Types>,
Type extends UnionType<keyof Types>,
Type extends UnionType<keyof Types> & Types[TypeName],
ReturnType extends UnionReturnType,
Context,
>(
@ -39,6 +40,7 @@ export async function transformerUnionSubTraverser<
itemTypeName
] as unknown as UnionTransformerDefinition<
Types,
TypeName,
Type,
ReturnTypes,
ReturnType,
@ -66,6 +68,10 @@ export async function transformerUnionSubTraverser<
(input) => {
resolve(input);
},
globals.instanceGraph.getNodeFor(
item,
itemTypeName,
) as unknown as UnionInstanceNode<Types, TypeName, Type>,
globals.context,
);
resolve(transformedObject);

@ -1,4 +1,4 @@
import type { KeyTypes } from "../..";
import type { KeyTypes } from "../../../";
import { MultiMap } from "./MultiMap";
import { MultiSet } from "./MultiSet";
import type { TransformerSubTraverserExecutingPromises } from "./transformerSubTraverserTypes";

@ -1,3 +1,7 @@
/**
* A Multi-Map is a map between the tuple of two items and a value
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
export class MultiMap<Key1, Key2, Value> {
private map: Map<Key1, Map<Key2, Value>> = new Map();

@ -1,3 +1,7 @@
/**
* A Multi-Set is a set where two items occupy a unique spot
*/
export class MultiSet<Key1, Key2> {
private map: Map<Key1, Set<Key2>> = new Map();
// eslint-disable-next-line @typescript-eslint/no-inferrable-types

@ -4,9 +4,10 @@ import type {
BaseTraverserTypes,
KeyTypes,
TransformerReturnTypes,
TraverserDefinition,
TraverserDefinitions,
TraverserTypes,
} from "../..";
} from "../../../";
import type { InstanceGraph } from "../../../instanceGraph/instanceGraph";
import type { Transformers } from "../../Transformers";
import type { CircularDepenedencyAwaiter } from "./CircularDependencyAwaiter";
import type { MultiMap } from "./MultiMap";
@ -30,11 +31,12 @@ export interface TransformerSubTraverserGlobals<
ReturnTypes extends TransformerReturnTypes<Types>,
Context,
> {
traverserDefinition: TraverserDefinition<Types>;
traverserDefinition: TraverserDefinitions<Types>;
transformers: Transformers<Types, ReturnTypes, Context>;
executingPromises: TransformerSubTraverserExecutingPromises<keyof Types>;
circularDependencyAwaiter: CircularDepenedencyAwaiter;
superPromise: SuperPromise;
instanceGraph: InstanceGraph<Types>;
context: Context;
}

@ -1,20 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {
TransformerInputReturnTypes,
TraverserDefinition,
TraverserDefinitions,
TraverserTypes,
VisitorsInput,
} from ".";
import { Transformer, Visitor } from ".";
import type { TransformersInput } from "./Transformers";
} from "..";
import { Transformer, Visitor } from "..";
import type { TransformersInput } from "../transformer/Transformers";
export class Traverser<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Types extends TraverserTypes<any>,
> {
private traverserDefinition: TraverserDefinition<Types>;
private traverserDefinition: TraverserDefinitions<Types>;
constructor(traverserDefinition: TraverserDefinition<Types>) {
constructor(traverserDefinition: TraverserDefinitions<Types>) {
this.traverserDefinition = traverserDefinition;
}

@ -4,7 +4,7 @@ import type {
TraverserTypes,
UnionType,
PrimitiveType,
} from ".";
} from "..";
export type InterfaceTraverserDefinition<Type extends InterfaceType<any>> = {
kind: "interface";
@ -22,14 +22,24 @@ export type PrimitiveTraverserDefinition = {
kind: "primitive";
};
export type TraverserDefinition<Types extends TraverserTypes<any>> = {
[TypeField in keyof Types]: Types[TypeField] extends InterfaceType<
keyof Types
>
? InterfaceTraverserDefinition<Types[TypeField]>
: Types[TypeField] extends UnionType<keyof Types>
? UnionTraverserDefinition<Types[TypeField]>
: Types[TypeField] extends PrimitiveType
export type TraverserDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends Types[TypeName],
> = {
[TN in TypeName]: Type extends InterfaceType<keyof Types>
? InterfaceTraverserDefinition<Type>
: Type extends UnionType<keyof Types>
? UnionTraverserDefinition<Type>
: Type extends PrimitiveType
? PrimitiveTraverserDefinition
: never;
}[TypeName];
export type TraverserDefinitions<Types extends TraverserTypes<any>> = {
[TypeName in keyof Types]: TraverserDefinition<
Types,
TypeName,
Types[TypeName]
>;
};

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { AssertExtends, KeyTypes } from "./UtilTypes";
import type { AssertExtends, KeyTypes } from "../UtilTypes";
export interface InterfaceType<TypeNames extends KeyTypes> {
kind: "interface";

@ -8,15 +8,16 @@ import type {
PrimitiveType,
PrimitiveVisitorDefinition,
PrimitiveVisitorInputDefinition,
TraverserDefinition,
TraverserDefinitions,
TraverserTypes,
UnionType,
UnionVisitorDefinition,
UnionVisitorInputDefinition,
Visitors,
VisitorsInput,
} from ".";
import { MultiSet } from "./transformerSubTraversers/util/MultiSet";
} from "../";
import { InstanceGraph } from "../instanceGraph/instanceGraph";
import { MultiSet } from "../transformer/transformerSubTraversers/util/MultiSet";
import { visitorParentSubTraverser } from "./visitorSubTraversers/VisitorParentSubTraverser";
// TODO: Lots of "any" in this file. I'm just done with fancy typescript,
@ -27,11 +28,11 @@ export class Visitor<
Types extends TraverserTypes<any>,
Context = undefined,
> {
private traverserDefinition: TraverserDefinition<Types>;
private traverserDefinition: TraverserDefinitions<Types>;
private visitors: Visitors<Types, Context>;
constructor(
traverserDefinition: TraverserDefinition<Types>,
traverserDefinition: TraverserDefinitions<Types>,
visitors: VisitorsInput<Types, Context>,
) {
this.traverserDefinition = traverserDefinition;
@ -39,15 +40,17 @@ export class Visitor<
}
private applyDefaultInterfaceVisitorProperties<
Type extends InterfaceType<keyof Types>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
>(
typeName: keyof Types,
typePropertiesInput: InterfaceVisitorInputDefinition<
Types,
TypeName,
Type,
Context
>["properties"],
): InterfaceVisitorDefinition<Types, Type, Context>["properties"] {
): InterfaceVisitorDefinition<Types, TypeName, Type, Context>["properties"] {
return Object.keys(
(this.traverserDefinition[typeName] as InterfaceTraverserDefinition<Type>)
.properties,
@ -60,13 +63,21 @@ export class Visitor<
};
}
return agg;
}, {}) as InterfaceVisitorDefinition<Types, Type, Context>["properties"];
}, {}) as InterfaceVisitorDefinition<
Types,
TypeName,
Type,
Context
>["properties"];
}
private applyDefaultInterfaceVisitor<Type extends InterfaceType<keyof Types>>(
private applyDefaultInterfaceVisitor<
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
>(
typeName: keyof Types,
typeInput?: InterfaceVisitorInputDefinition<Types, Type, Context>,
): InterfaceVisitorDefinition<Types, Type, Context> {
typeInput?: InterfaceVisitorInputDefinition<Types, TypeName, Type, Context>,
): InterfaceVisitorDefinition<Types, TypeName, Type, Context> {
if (!typeInput) {
return {
visitor: async () => {
@ -84,9 +95,12 @@ export class Visitor<
};
}
private applyDefaultUnionVisitor<Type extends UnionType<keyof Types>>(
typeInput?: UnionVisitorInputDefinition<Types, Type, Context>,
): UnionVisitorDefinition<Types, Type, Context> {
private applyDefaultUnionVisitor<
TypeName extends keyof Types,
Type extends UnionType<keyof Types> & Types[TypeName],
>(
typeInput?: UnionVisitorInputDefinition<Types, TypeName, Type, Context>,
): UnionVisitorDefinition<Types, TypeName, Type, Context> {
if (!typeInput) {
return async () => {
return;
@ -95,9 +109,12 @@ export class Visitor<
return typeInput;
}
private applyDefaultPrimitiveVisitor<Type extends PrimitiveType>(
typeInput?: PrimitiveVisitorInputDefinition<Type, Context>,
): PrimitiveVisitorDefinition<Type, Context> {
private applyDefaultPrimitiveVisitor<
TypeName extends keyof Types,
Type extends PrimitiveType & Types[TypeName],
>(
typeInput?: PrimitiveVisitorInputDefinition<Types, TypeName, Type, Context>,
): PrimitiveVisitorDefinition<Types, TypeName, Type, Context> {
if (!typeInput) {
return async () => {
return;
@ -134,10 +151,13 @@ export class Visitor<
itemTypeName: TypeName,
context: Context,
): Promise<void> {
const instanceGraph = new InstanceGraph(this.traverserDefinition);
instanceGraph.getNodeFor(item, itemTypeName);
const toReturn = await visitorParentSubTraverser(item, itemTypeName, {
traverserDefinition: this.traverserDefinition,
visitors: this.visitors,
visitedObjects: new MultiSet(),
instanceGraph,
context,
});
return toReturn;

@ -0,0 +1,157 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {
InterfaceType,
PrimitiveType,
TraverserTypes,
UnionType,
} from "../";
import type { InterfaceInstanceNode } from "../instanceGraph/nodes/InterfaceInstanceNode";
import type { PrimitiveInstanceNode } from "../instanceGraph/nodes/PrimitiveInstanceNode";
import type { UnionInstanceNode } from "../instanceGraph/nodes/UnionInstanceNode";
export type InterfaceVisitorFunction<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
Context,
> = (
originalData: Type["type"],
node: InterfaceInstanceNode<Types, TypeName, Type>,
context: Context,
) => Promise<void>;
export type InterfaceVisitorPropertyFunction<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
PropertyName extends keyof Type["properties"],
Context,
> = (
originalData: Types[Type["properties"][PropertyName]]["type"],
node: InterfaceInstanceNode<Types, TypeName, Type>,
context: Context,
) => Promise<void>;
export type InterfaceVisitorDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
Context,
> = {
visitor: InterfaceVisitorFunction<Types, TypeName, Type, Context>;
properties: {
[PropertyName in keyof Type["properties"]]: InterfaceVisitorPropertyFunction<
Types,
TypeName,
Type,
PropertyName,
Context
>;
};
};
export type UnionVisitorFunction<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends UnionType<keyof Types> & Types[TypeName],
Context,
> = (
originalData: Type["type"],
node: UnionInstanceNode<Types, TypeName, Type>,
context: Context,
) => Promise<void>;
export type UnionVisitorDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends UnionType<keyof Types> & Types[TypeName],
Context,
> = UnionVisitorFunction<Types, TypeName, Type, Context>;
export type PrimitiveVisitorFunction<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends PrimitiveType & Types[TypeName],
Context,
> = (
originalData: Type["type"],
node: PrimitiveInstanceNode<Types, TypeName, Type>,
context: Context,
) => Promise<void>;
export type PrimitiveVisitorDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends PrimitiveType & Types[TypeName],
Context,
> = PrimitiveVisitorFunction<Types, TypeName, Type, Context>;
export type VisitorDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Context,
> = Types[TypeName] extends InterfaceType<keyof Types>
? InterfaceVisitorDefinition<Types, TypeName, Types[TypeName], Context>
: Types[TypeName] extends UnionType<keyof Types>
? UnionVisitorDefinition<Types, TypeName, Types[TypeName], Context>
: Types[TypeName] extends PrimitiveType
? PrimitiveVisitorDefinition<Types, TypeName, Types[TypeName], Context>
: never;
export type Visitors<Types extends TraverserTypes<any>, Context> = {
[TypeName in keyof Types]: VisitorDefinition<Types, TypeName, Context>;
};
/**
* Input
*/
export type InterfaceVisitorInputDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
Context,
> = {
visitor: InterfaceVisitorFunction<Types, TypeName, Type, Context>;
properties?: Partial<{
[PropertyName in keyof Type["properties"]]: InterfaceVisitorPropertyFunction<
Types,
TypeName,
Type,
PropertyName,
Context
>;
}>;
};
export type UnionVisitorInputDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends UnionType<keyof Types> & Types[TypeName],
Context,
> = UnionVisitorFunction<Types, TypeName, Type, Context>;
export type PrimitiveVisitorInputDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends PrimitiveType & Types[TypeName],
Context,
> = PrimitiveVisitorFunction<Types, TypeName, Type, Context>;
export type VisitorInputDefinition<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Context,
> = Types[TypeName] extends InterfaceType<keyof Types>
? InterfaceVisitorInputDefinition<Types, TypeName, Types[TypeName], Context>
: Types[TypeName] extends UnionType<keyof Types>
? UnionVisitorInputDefinition<Types, TypeName, Types[TypeName], Context>
: Types[TypeName] extends PrimitiveType
? PrimitiveVisitorInputDefinition<Types, TypeName, Types[TypeName], Context>
: never;
export type VisitorsInput<
Types extends TraverserTypes<any>,
Context,
> = Partial<{
[TypeName in keyof Types]: VisitorInputDefinition<Types, TypeName, Context>;
}>;

@ -1,14 +1,15 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { InterfaceVisitorDefinition, TraverserTypes } from "..";
import type { InterfaceTraverserDefinition } from "../TraverserDefinition";
import type { InterfaceType } from "../TraverserTypes";
import type { InterfaceVisitorDefinition, TraverserTypes } from "../../";
import type { InterfaceInstanceNode } from "../../instanceGraph/nodes/InterfaceInstanceNode";
import type { InterfaceTraverserDefinition } from "../../traverser/TraverserDefinition";
import type { InterfaceType } from "../../traverser/TraverserTypes";
import type { VisitorSubTraverserGlobals } from "./util/visitorSubTraverserTypes";
import { visitorParentSubTraverser } from "./VisitorParentSubTraverser";
export async function visitorInterfaceSubTraverser<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends InterfaceType<keyof Types>,
Type extends InterfaceType<keyof Types> & Types[TypeName],
Context,
>(
item: Type["type"],
@ -22,16 +23,27 @@ export async function visitorInterfaceSubTraverser<
] as InterfaceTraverserDefinition<Type>;
const visitor = visitors[
itemTypeName
] as unknown as InterfaceVisitorDefinition<Types, Type, Context>;
] as unknown as InterfaceVisitorDefinition<Types, TypeName, Type, Context>;
await Promise.all([
visitor.visitor(item, globals.context),
visitor.visitor(
item,
globals.instanceGraph.getNodeFor(
item,
itemTypeName,
) as unknown as InterfaceInstanceNode<Types, TypeName, Type>,
globals.context,
),
Promise.all(
Object.entries(definition.properties).map(async ([propertyName]) => {
const originalObject = item[propertyName];
const originalPropertyDefinition = definition.properties[propertyName];
const propertyVisitorPromise = visitor.properties[propertyName](
originalObject,
globals.instanceGraph.getNodeFor(
item,
itemTypeName,
) as unknown as InterfaceInstanceNode<Types, TypeName, Type>,
globals.context,
);
let propertyTraverserPromise: Promise<void | void[]>;

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { BaseTraverserTypes, TraverserTypes } from "..";
import type { BaseTraverserTypes, TraverserTypes } from "../../";
import type {
VisitorSubTraverser,
VisitorSubTraverserGlobals,

@ -1,12 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { PrimitiveVisitorDefinition, TraverserTypes } from "..";
import type { PrimitiveType } from "../TraverserTypes";
import type { PrimitiveVisitorDefinition, TraverserTypes } from "../../";
import type { PrimitiveInstanceNode } from "../../instanceGraph/nodes/PrimitiveInstanceNode";
import type { PrimitiveType } from "../../traverser/TraverserTypes";
import type { VisitorSubTraverserGlobals } from "./util/visitorSubTraverserTypes";
export async function visitorPrimitiveSubTraverser<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends PrimitiveType,
Type extends PrimitiveType & Types[TypeName],
Context,
>(
item: Type["type"],
@ -16,6 +17,13 @@ export async function visitorPrimitiveSubTraverser<
const { visitors } = globals;
const visitor = visitors[
itemTypeName
] as unknown as PrimitiveVisitorDefinition<Type, Context>;
return visitor(item, globals.context);
] as unknown as PrimitiveVisitorDefinition<Types, TypeName, Type, Context>;
return visitor(
item,
globals.instanceGraph.getNodeFor(
item,
itemTypeName,
) as unknown as PrimitiveInstanceNode<Types, TypeName, Type>,
globals.context,
);
}

@ -1,14 +1,15 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { TraverserTypes, UnionVisitorDefinition } from "..";
import type { UnionTraverserDefinition } from "../TraverserDefinition";
import type { UnionType } from "../TraverserTypes";
import type { TraverserTypes, UnionVisitorDefinition } from "../../";
import type { UnionInstanceNode } from "../../instanceGraph/nodes/UnionInstanceNode";
import type { UnionTraverserDefinition } from "../../traverser/TraverserDefinition";
import type { UnionType } from "../../traverser/TraverserTypes";
import type { VisitorSubTraverserGlobals } from "./util/visitorSubTraverserTypes";
import { visitorParentSubTraverser } from "./VisitorParentSubTraverser";
export async function visitorUnionSubTraverser<
Types extends TraverserTypes<any>,
TypeName extends keyof Types,
Type extends UnionType<keyof Types>,
Type extends UnionType<keyof Types> & Types[TypeName],
Context,
>(
item: Type["type"],
@ -21,12 +22,20 @@ export async function visitorUnionSubTraverser<
] as UnionTraverserDefinition<Type>;
const visitor = visitors[itemTypeName] as unknown as UnionVisitorDefinition<
Types,
TypeName,
Type,
Context
>;
const itemSpecificTypeName = definition.selector(item);
await Promise.all([
visitor(item, globals.context),
visitor(
item,
globals.instanceGraph.getNodeFor(
item,
itemTypeName,
) as unknown as UnionInstanceNode<Types, TypeName, Type>,
globals.context,
),
visitorParentSubTraverser(item, itemSpecificTypeName, globals),
]);
}

@ -1,11 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {
BaseTraverserTypes,
TraverserDefinition,
TraverserDefinitions,
TraverserTypes,
Visitors,
} from "../..";
import type { MultiSet } from "../../transformerSubTraversers/util/MultiSet";
} from "../../../";
import type { InstanceGraph } from "../../../instanceGraph/instanceGraph";
import type { MultiSet } from "../../../transformer/transformerSubTraversers/util/MultiSet";
export type VisitorSubTraverser<
Types extends TraverserTypes<any>,
@ -22,8 +23,9 @@ export interface VisitorSubTraverserGlobals<
Types extends TraverserTypes<any>,
Context,
> {
traverserDefinition: TraverserDefinition<Types>;
traverserDefinition: TraverserDefinitions<Types>;
visitors: Visitors<Types, Context>;
visitedObjects: MultiSet<object, keyof Types>;
instanceGraph: InstanceGraph<Types>;
context: Context;
}

@ -0,0 +1,156 @@
import type { TraverserDefinitions, ValidateTraverserTypes } from "../../src";
import { InstanceGraph } from "../../src/instanceGraph/instanceGraph";
describe("InstanceGraph", () => {
/**
* Types
*/
type Element = "Water" | "Earth" | "Fire" | "Air";
interface Bender {
name: string;
element: Element;
friends: Person[];
}
interface NonBender {
name: string;
friends: Person[];
}
type Person = Bender | NonBender;
/**
* Raw Data to Traverse
*/
const aang: Bender = {
name: "Aang",
element: "Air",
friends: [],
};
const sokka: NonBender = {
name: "Sokka",
friends: [],
};
const katara: Bender = {
name: "Katara",
element: "Water",
friends: [],
};
aang.friends.push(sokka, katara);
sokka.friends.push(aang, katara);
katara.friends.push(aang, sokka);
/**
* Traverser Types
*/
type AvatarTraverserTypes = ValidateTraverserTypes<{
Element: {
kind: "primitive";
type: Element;
};
Bender: {
kind: "interface";
type: Bender;
properties: {
element: "Element";
friends: "Person";
};
};
NonBender: {
kind: "interface";
type: NonBender;
properties: {
friends: "Person";
};
};
Person: {
kind: "union";
type: Person;
typeNames: "Bender" | "NonBender";
};
}>;
/**
* Create the traverser definition
*/
const avatarTraverserDefinition: TraverserDefinitions<AvatarTraverserTypes> =
{
Element: {
kind: "primitive",
},
Bender: {
kind: "interface",
properties: {
element: "Element",
friends: "Person",
},
},
NonBender: {
kind: "interface",
properties: {
friends: "Person",
},
},
Person: {
kind: "union",
selector: (item) => {
return (item as Bender).element ? "Bender" : "NonBender";
},
},
};
describe("Build Instance Graph", () => {
it("returns child nodes when child methods are called.", () => {
const graph = new InstanceGraph(avatarTraverserDefinition);
const aangBender = graph.getNodeFor(aang, "Bender");
expect(aangBender.typeName).toBe("Bender");
expect(aangBender.instance.name).toBe("Aang");
// child
const aangElement = aangBender.child("element");
expect(aangElement.instance).toBe("Air");
expect(aangElement.typeName).toBe("Element");
const aangFriends = aangBender.child("friends");
expect(aangFriends.length).toBe(2);
const sokkaPerson = aangFriends[0];
const kataraPerson = aangFriends[1];
expect(sokkaPerson.instance.name).toBe("Sokka");
expect(kataraPerson.instance.name).toBe("Katara");
expect(sokkaPerson.typeName).toBe("Person");
expect(kataraPerson.typeName).toBe("Person");
const sokkaNonBender = sokkaPerson.child();
expect(sokkaNonBender.instance.name).toBe("Sokka");
expect(sokkaNonBender.typeName).toBe("NonBender");
if (sokkaNonBender.typeName === "NonBender") {
const aangPerson = sokkaNonBender.child("friends")[0];
const aangBender2 = aangPerson.child();
expect(aangBender2).toBe(aangBender);
}
// allChildren
const [childElemement, childSokka, childKatara] =
aangBender.allChildren();
expect(childElemement.instance).toBe("Air");
expect((childSokka.instance as NonBender).name).toBe("Sokka");
expect((childKatara.instance as Bender).name).toBe("Katara");
const childOfSokkaPerson = sokkaPerson.allChildren();
expect(childOfSokkaPerson.length).toBe(1);
expect(childOfSokkaPerson[0].instance.name).toBe("Sokka");
});
it("returns parent nodes when parent methods are called.", () => {
const graph = new InstanceGraph(avatarTraverserDefinition);
const aangBender = graph.getNodeFor(aang, "Bender");
// parent
const [aangPerson] = aangBender.parent("Person");
expect(aangPerson.instance.name).toBe("Aang");
expect(aangPerson.typeName).toBe("Person");
const [sokkaNonBender] = aangPerson.parent("NonBender", "friends");
const [kataraBender] = aangPerson.parent("Bender", "friends");
expect(sokkaNonBender.typeName).toBe("NonBender");
expect(sokkaNonBender.instance.name).toBe("Sokka");
expect(kataraBender.typeName).toBe("Bender");
expect(kataraBender.instance.name).toBe("Katara");
});
});
describe("Transformer", () => {
it("transforms", () => {});
});
});

@ -1,7 +1,7 @@
import type { TraverserDefinition } from "../../../src";
import type { TraverserDefinitions } from "../../../src";
import type { AvatarTraverserTypes, Bender } from "./AvatarTraverserTypes";
export const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> =
export const avatarTraverserDefinition: TraverserDefinitions<AvatarTraverserTypes> =
{
Element: {
kind: "primitive",

@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist"
"outDir": "./dist",
},
"include": ["./src"]
}

@ -13,6 +13,7 @@
"target": "ES2021",
"sourceMap": true,
"jsx": "react-jsx",
"noErrorTruncation": true
},
"exclude": [
"node_modules",

Loading…
Cancel
Save