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 { ContextDefinition, ExpandedTermDefinition } from "jsonld";
import type {
LdoJsonldContext,
LdoJsonldContextExpandedTermDefinition,
} from "./LdoJsonldContext";
import type { NamedNode } from "@rdfjs/types";
// Create JSONLD Shorthands // Create JSONLD Shorthands
const shorthandToIriMap: Record<string, string> = { 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 * Handles the JSON-LD context and allows conversion between IRIs and terms
*/ */
export class ContextUtil { export class ContextUtil {
public readonly context: ContextDefinition; public readonly context: ContextDefinition | LdoJsonldContext;
private iriToKeyMap: Record<string, string>; private iriToKeyMap: Record<string, string>;
private typeNameToIriToKeyMap: Record<string, Record<string, string>>;
constructor(context: ContextDefinition) { constructor(context: ContextDefinition | LdoJsonldContext) {
this.context = context; this.context = context;
this.iriToKeyMap = {}; 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]) => { Object.entries(context).forEach(([contextKey, contextValue]) => {
if (typeof contextValue === "string") { if (typeof contextValue === "string") {
this.iriToKeyMap[this.keyIdToIri(contextValue)] = contextKey; iriToKeyMap[this.keyIdToIri(contextValue)] = contextKey;
} else if ( } else if (
typeof contextValue === "object" && typeof contextValue === "object" &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any contextValue !== null &&
(contextValue as any)["@id"] !!contextValue["@id"]
) { ) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const iri = this.keyIdToIri(contextValue["@id"]);
this.iriToKeyMap[this.keyIdToIri((contextValue as any)["@id"])] = iriToKeyMap[iri] = contextKey;
contextKey;
} }
}); });
return iriToKeyMap;
} }
public keyToIri(key: string): string { /**
if (!this.context[key]) { * Helper method that gets the relevant context to use if a typename is
return key; * provided
} else if (typeof this.context[key] === "string") { */
return this.keyIdToIri(this.context[key] as string); private getRelevantContext(
// eslint-disable-next-line @typescript-eslint/no-explicit-any key: string,
} else if (this.context[key] && (this.context[key] as any)["@id"]) { typeNames: NamedNode[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any ): ContextDefinition | LdoJsonldContext {
return this.keyIdToIri((this.context[key] as any)["@id"]); 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) { private keyIdToIri(keyId: string) {
if (shorthandToIriMap[keyId]) { if (shorthandToIriMap[keyId]) {
return shorthandToIriMap[keyId]; return shorthandToIriMap[keyId];
@ -52,38 +91,82 @@ export class ContextUtil {
} }
} }
public iriToKey(iri: string): string { /**
if (this.iriToKeyMap[iri]) { * Converts a given JsonLd key into an RDF IRI
return this.iriToKeyMap[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; 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 ( if (
typeof this.context[key] === "object" && typeof relevantContext[key] === "object" &&
(this.context[key] as ExpandedTermDefinition)["@type"] (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"; 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 !!( return !!(
this.context[key] && relevantContext[key] &&
typeof this.context[key] === "object" && typeof relevantContext[key] === "object" &&
(this.context[key] as ExpandedTermDefinition)["@container"] && ((relevantContext[key] as ExpandedTermDefinition)["@container"] ===
(this.context[key] as ExpandedTermDefinition)["@container"] === "@set" "@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 !!( return !!(
this.context[key] && relevantContext[key] &&
typeof this.context[key] === "object" && typeof relevantContext[key] === "object" &&
(this.context[key] as ExpandedTermDefinition)["@type"] && (relevantContext[key] as ExpandedTermDefinition)["@type"] &&
(this.context[key] as ExpandedTermDefinition)["@type"] === (relevantContext[key] as ExpandedTermDefinition)["@type"] ===
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString" "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 { BlankNode, Dataset, NamedNode } from "@rdfjs/types";
import type { ArrayProxyTarget } from "./arrayProxy/createArrayHandler"; import type { ArrayProxyTarget } from "./arrayProxy/createArrayHandler";
import { createArrayHandler } from "./arrayProxy/createArrayHandler"; import { createArrayHandler } from "./arrayProxy/createArrayHandler";
@ -8,6 +8,7 @@ import type { ArrayProxy } from "./arrayProxy/ArrayProxy";
import { _getUnderlyingArrayTarget } from "./types"; import { _getUnderlyingArrayTarget } from "./types";
import type { ContextUtil } from "./ContextUtil"; import type { ContextUtil } from "./ContextUtil";
import type { LanguageOrdering } from "./language/languageTypes"; import type { LanguageOrdering } from "./language/languageTypes";
import { namedNode } from "@rdfjs/data-model";
export interface ProxyContextOptions { export interface ProxyContextOptions {
dataset: Dataset; dataset: Dataset;
@ -18,6 +19,8 @@ export interface ProxyContextOptions {
state?: Record<string, unknown>; 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. * This file keeps track of the target objects used in the proxies.
* The reason is so that JSON.stringify does not recurse inifinitely * The reason is so that JSON.stringify does not recurse inifinitely
@ -107,4 +110,12 @@ export class ProxyContext {
}; };
return new ProxyContext(fullOptions); 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( addObjectToDataset(
{ {
"@id": target[0][0], "@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, } as RawObject,
false, false,
proxyContext, proxyContext,
@ -123,7 +126,12 @@ export function modifyArray<ReturnType>(
const addedNodes = added const addedNodes = added
? (added ? (added
.map((addedValue) => { .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[]) .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 proxyContext = subjectProxy[_proxyContext];
const subjectNode = subjectProxy[_getUnderlyingNode]; const subjectNode = subjectProxy[_getUnderlyingNode];
const predicateNode = namedNode( const predicateNode = namedNode(
proxyContext.contextUtil.keyToIri(predicate as string), proxyContext.contextUtil.keyToIri(
predicate as string,
proxyContext.getRdfType(subjectNode),
),
); );
let objectNode: ObjectNode | null; let objectNode: ObjectNode | null;
if (object == null) { if (object == null) {

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

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

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

@ -48,7 +48,12 @@ export function createSubjectHandler(
const tripleDataset = proxyContext.dataset.match(subject); const tripleDataset = proxyContext.dataset.match(subject);
const keys: Set<string> = new Set(["@id"]); const keys: Set<string> = new Set(["@id"]);
tripleDataset.toArray().forEach((quad) => { 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); return Array.from(keys);
}, },

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

@ -19,7 +19,9 @@ export function getValueForKey(
if (target["@id"].termType === "BlankNode") { if (target["@id"].termType === "BlankNode") {
return undefined; 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) { if (key === "toString" || key === Symbol.toStringTag) {
// TODO: this toString method right now returns [object Object], // TODO: this toString method right now returns [object Object],
@ -31,18 +33,19 @@ export function getValueForKey(
return; return;
} }
const subject = target["@id"]; const subject = target["@id"];
const predicate = namedNode(contextUtil.keyToIri(key)); const rdfType = proxyContext.getRdfType(subject);
if (contextUtil.isArray(key)) { const predicate = namedNode(contextUtil.keyToIri(key, rdfType));
if (contextUtil.isArray(key, rdfType)) {
const arrayProxy = proxyContext.createArrayProxy( const arrayProxy = proxyContext.createArrayProxy(
[subject, predicate, null, null], [subject, predicate, null, null],
false, false,
undefined, undefined,
contextUtil.isLangString(key), contextUtil.isLangString(key, rdfType),
); );
return arrayProxy; return arrayProxy;
} }
let objectDataset = dataset.match(subject, predicate); let objectDataset = dataset.match(subject, predicate);
if (contextUtil.isLangString(key)) { if (contextUtil.isLangString(key, rdfType)) {
objectDataset = filterQuadsByLanguageOrdering( objectDataset = filterQuadsByLanguageOrdering(
objectDataset, objectDataset,
proxyContext.languageOrdering, proxyContext.languageOrdering,

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

@ -14,7 +14,9 @@ export function getNodeFromRawObject(
} else if (!item["@id"]) { } else if (!item["@id"]) {
return blankNode(); return blankNode();
} else if (typeof item["@id"] === "string") { } 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 { } else {
return item["@id"]; return item["@id"];
} }
@ -23,6 +25,7 @@ export function getNodeFromRawObject(
export function getNodeFromRawValue( export function getNodeFromRawValue(
key: string, key: string,
value: RawValue, value: RawValue,
rdfTypes: NamedNode[],
proxyContext: ProxyContext, proxyContext: ProxyContext,
): BlankNode | NamedNode | Literal | undefined { ): BlankNode | NamedNode | Literal | undefined {
// Get the Object Node // Get the Object Node
@ -33,7 +36,8 @@ export function getNodeFromRawValue(
typeof value === "boolean" || typeof value === "boolean" ||
typeof value === "number" 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") { if (datatype === "@id") {
return namedNode(value.toString()); return namedNode(value.toString());
} else { } else {

@ -1,4 +1,6 @@
import { namedNode } from "@rdfjs/data-model";
import { ContextUtil } from "../src/ContextUtil"; import { ContextUtil } from "../src/ContextUtil";
import { scopedContext } from "./scopedExampleData";
describe("ContextUtil", () => { describe("ContextUtil", () => {
describe("keyToIri and iriToKey", () => { describe("keyToIri and iriToKey", () => {
@ -7,22 +9,36 @@ describe("ContextUtil", () => {
name: "http://hl7.org/fhir/name", name: "http://hl7.org/fhir/name",
}; };
const contextUtil = new ContextUtil(fakeContext); 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", () => { it("returns the given key if it is not in the context", () => {
const contextUtil = new ContextUtil({}); const contextUtil = new ContextUtil({});
expect(contextUtil.keyToIri("name")).toBe("name"); expect(contextUtil.keyToIri("name", [])).toBe("name");
expect(contextUtil.iriToKey("http://hl7.org/fhir/name")).toBe( expect(contextUtil.iriToKey("http://hl7.org/fhir/name", [])).toBe(
"http://hl7.org/fhir/name", "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({ const contextUtil = new ContextUtil({
name: { "@type": "http://www.w3.org/2001/XMLSchema#string" }, 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({ const contextUtil = new ContextUtil({
name: { "@id": "http://hl7.org/fhir/name" }, name: { "@id": "http://hl7.org/fhir/name" },
}); });
expect(contextUtil.getType("name")).toBe( expect(contextUtil.getDataType("name", [])).toBe(
"http://www.w3.org/2001/XMLSchema#string", "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 type { ObservationShape, PatientShape } from "./patientExampleData";
import { import {
patientData, patientData,
patientContext,
tinyPatientData, tinyPatientData,
tinyArrayPatientData, tinyArrayPatientData,
patientDataWithBlankNodes, patientDataWithBlankNodes,
tinyPatientDataWithBlankNodes, tinyPatientDataWithBlankNodes,
tinyPatientDataWithLanguageTags, tinyPatientDataWithLanguageTags,
patientUnnestedContext,
patientNestedContext,
} from "./patientExampleData"; } from "./patientExampleData";
import { namedNode, quad, literal, defaultGraph } from "@rdfjs/data-model"; import { namedNode, quad, literal, defaultGraph } from "@rdfjs/data-model";
import type { Dataset, NamedNode } from "@rdfjs/types"; import type { Dataset, NamedNode } from "@rdfjs/types";
import type { ContextDefinition } from "jsonld"; 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< async function getLoadedDataset(): Promise<
[Dataset, ObservationShape, JsonldDatasetProxyBuilder] [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", () => { describe("read", () => {
it("retreives a primitive", async () => { it("retreives a primitive", async () => {
const [, observation] = await getLoadedDataset(); const [, observation] = await getLoadedDataset();
@ -407,23 +428,32 @@ describe("jsonldDatasetProxy", () => {
const [, observation] = await getLoadedDataset(); const [, observation] = await getLoadedDataset();
expect(observation["@context"]).toEqual(patientContext); 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", () => { describe("write", () => {
it("sets a primitive value that doesn't exist yet", async () => { it("sets a primitive value that doesn't exist yet", async () => {
const [dataset, observation] = await getEmptyObservationDataset(); const [dataset, observation] = await getEmptyObservationDataset();
observation.type = { "@id": "Observation" };
observation.notes = "Cool Notes"; observation.notes = "Cool Notes";
expect(dataset.toString()).toBe( 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 () => { it("sets primitive number and boolean values", async () => {
const [dataset, patient] = await getEmptyPatientDataset(); const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.age = 35; patient.age = 35;
patient.isHappy = true; patient.isHappy = true;
expect(dataset.toString()).toBe( 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 () => { it("replaces a primitive value that currently exists", async () => {
const [dataset, observation] = await getEmptyObservationDataset(); 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( quad(
namedNode("http://example.com/Observation1"), namedNode("http://example.com/Observation1"),
namedNode("http://hl7.org/fhir/notes"), namedNode("http://hl7.org/fhir/notes"),
literal("Cool Notes"), literal("Cool Notes"),
), ),
); ]);
observation.notes = "Lame Notes"; observation.notes = "Lame Notes";
expect(dataset.toString()).toBe( 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 [dataset, observation] = await getEmptyObservationDataset();
const patient: PatientShape = { const patient: PatientShape = {
"@id": "http://example.com/Patient1", "@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
birthdate: "2001-01-01", birthdate: "2001-01-01",
}; };
observation.type = { "@id": "Observation" };
observation.subject = patient; observation.subject = patient;
expect(dataset.toString()).toBe( 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(); const [dataset, observation] = await getTinyLoadedDataset();
observation.subject = undefined; observation.subject = undefined;
expect(dataset.toString()).toBe( 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 () => { it("Creates a blank node if the id is blank during set", async () => {
const [dataset, observation] = await getEmptyObservationDataset(); 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?.["@id"]).toBeUndefined();
expect(observation.subject.name).toEqual(["Joe"]); expect(observation.subject.name).toEqual(["Joe"]);
expect( expect(
@ -500,12 +538,14 @@ describe("jsonldDatasetProxy", () => {
const [dataset, observation] = await getEmptyObservationDataset(); const [dataset, observation] = await getEmptyObservationDataset();
const patient: PatientShape = { const patient: PatientShape = {
"@id": "http://example.com/Patient1", "@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
birthdate: "2001-01-01", birthdate: "2001-01-01",
name: ["Jon", "Bon", "Jovi"], name: ["Jon", "Bon", "Jovi"],
}; };
observation.type = { "@id": "Observation" };
observation.subject = patient; observation.subject = patient;
expect(dataset.toString()).toBe( 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 [dataset, observation] = await getEmptyObservationDataset();
const patient1: PatientShape = { const patient1: PatientShape = {
"@id": "http://example.com/Patient1", "@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
name: ["jon"], name: ["jon"],
}; };
const patient2: PatientShape = { const patient2: PatientShape = {
"@id": "http://example.com/patient2", "@id": "http://example.com/patient2",
type: { "@id": "Patient" },
name: ["jane"], name: ["jane"],
roommate: [patient1], roommate: [patient1],
}; };
patient1.roommate = [patient2]; patient1.roommate = [patient2];
observation.type = { "@id": "Observation" };
observation.subject = patient1; observation.subject = patient1;
expect(dataset.toString()).toBe( 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 () => { it("adds a proxy object to the array", async () => {
const [, , builder] = await getTinyLoadedDataset(); const [, , builder] = await getTinyLoadedDataset();
const patient3 = builder.fromSubject( const patient3: PatientShape = builder.fromSubject(
namedNode("http://example.com/Patient3"), namedNode("http://example.com/Patient3"),
); );
const patient1 = builder.fromSubject( patient3.type = { "@id": "Patient" };
const patient1: PatientShape = builder.fromSubject(
namedNode("http://example.com/Patient1"), namedNode("http://example.com/Patient1"),
); );
patient3.roommate.push(patient1); patient3.roommate?.push(patient1);
}); });
it("sets a primitive on an array", async () => { it("sets a primitive on an array", async () => {
const [dataset, patient] = await getEmptyPatientDataset(); const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
(patient.name as string[])[0] = "jon"; (patient.name as string[])[0] = "jon";
expect(dataset.toString()).toBe( 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 () => { it("sets a primitive on an array and overwrites one that already is there", async () => {
const [dataset, patient] = await getEmptyPatientDataset(); const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
dataset.add( dataset.add(
quad( quad(
namedNode("http://example.com/Patient1"), namedNode("http://example.com/Patient1"),
@ -557,15 +603,16 @@ describe("jsonldDatasetProxy", () => {
); );
(patient.name as string[])[0] = "not jon"; (patient.name as string[])[0] = "not jon";
expect(dataset.toString()).toBe( 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 () => { it("sets an array", async () => {
const [dataset, patient] = await getEmptyPatientDataset(); const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = ["Joe", "Mama"]; patient.name = ["Joe", "Mama"];
expect(dataset.toString()).toBe( 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 [dataset, observation] = await getTinyLoadedDataset();
const replacementPatient: PatientShape = { const replacementPatient: PatientShape = {
"@id": "http://example.com/ReplacementPatient", "@id": "http://example.com/ReplacementPatient",
type: { "@id": "Patient" },
name: ["Jackson"], name: ["Jackson"],
}; };
observation.subject = replacementPatient; observation.subject = replacementPatient;
expect(dataset.toString()).toBe( 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 () => { it("Does not overwrite the full object when a partial object is provided", async () => {
const [dataset, observation] = await getTinyLoadedDataset(); 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( 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 [dataset, observation] = await getTinyLoadedDataset();
const replacementPatient: PatientShape = { const replacementPatient: PatientShape = {
"@id": "http://example.com/ReplacementPatient", "@id": "http://example.com/ReplacementPatient",
type: { "@id": "Patient" },
name: ["Jackson"], name: ["Jackson"],
}; };
const roommateArr = observation?.subject?.roommate as PatientShape[]; const roommateArr = observation?.subject?.roommate as PatientShape[];
roommateArr[0] = replacementPatient; roommateArr[0] = replacementPatient;
expect(dataset.toString()).toBe( 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[]; const roommateArr = observation.subject?.roommate as PatientShape[];
roommateArr[0] = { roommateArr[0] = {
"@id": "http://example.com/ReplacementPatient", "@id": "http://example.com/ReplacementPatient",
type: { "@id": "Patient" },
name: ["Jackson"], name: ["Jackson"],
}; };
expect(roommateArr.length).toBe(2); expect(roommateArr.length).toBe(2);
@ -619,7 +672,7 @@ describe("jsonldDatasetProxy", () => {
patient["@id"] = "http://example.com/RenamedPatient"; patient["@id"] = "http://example.com/RenamedPatient";
expect(patient["@id"]).toBe("http://example.com/RenamedPatient"); expect(patient["@id"]).toBe("http://example.com/RenamedPatient");
expect(dataset.toString()).toBe( 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(); const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject; delete observation.subject;
expect(dataset.toString()).toBe( 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(); const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject?.name; delete observation.subject?.name;
expect(dataset.toString()).toBe( 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(); const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject?.roommate?.[0]; delete observation.subject?.roommate?.[0];
expect(dataset.toString()).toBe( 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 () => { 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 [dataset, observation] = await getTinyLoadedDatasetWithBlankNodes();
const deletedBlankNode =
observation.subject?.roommate?.[0][_getUnderlyingNode];
delete observation.subject?.roommate?.[0]; delete observation.subject?.roommate?.[0];
expect(dataset.toString()).toBe( expect(dataset.match(deletedBlankNode).size).toBe(0);
'<http://example.com/Observation1> <http://hl7.org/fhir/subject> _:b26_Patient1 .\n_:b26_Patient1 <http://hl7.org/fhir/name> "Garrett" .\n',
);
}); });
it("Removes a literal in an array when using the delete operator", async () => { it("Removes a literal in an array when using the delete operator", async () => {
const [dataset, observation] = await getTinyLoadedDataset(); const [dataset, observation] = await getTinyLoadedDataset();
delete observation.subject?.name?.[0]; delete observation.subject?.name?.[0];
expect(dataset.toString()).toBe( 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"]; delete observation["@id"];
expect(observation).toEqual({ "@id": "http://example.com/Observation1" }); expect(observation).toEqual({ "@id": "http://example.com/Observation1" });
expect(dataset.toString()).toBe( 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 [dataset, observation] = await getTinyLoadedDataset();
const replacementPatient: PatientShape = { const replacementPatient: PatientShape = {
"@id": "http://example.com/Patient1", "@id": "http://example.com/Patient1",
type: { "@id": "Patient" },
name: ["Mister Sneaky"], name: ["Mister Sneaky"],
}; };
observation.subject = replacementPatient; observation.subject = replacementPatient;
expect(dataset.toString()).toBe( 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 () => { it("handles Object.assign", async () => {
const [dataset, observation] = await getTinyLoadedDataset(); const [dataset, observation] = await getTinyLoadedDataset();
Object.assign(observation, { Object.assign(observation.subject!, {
age: 35, age: 35,
isHappy: true, isHappy: true,
}); });
expect(dataset.toString()).toBe( 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 () => { it("Adds elements to the array even if they were modified by the datastore", async () => {
const [dataset, patient] = await getEmptyPatientDataset(); const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = ["Joe", "Blow"]; patient.name = ["Joe", "Blow"];
dataset.add( dataset.add(
quad( quad(
@ -745,6 +800,7 @@ describe("jsonldDatasetProxy", () => {
it("Removes elements from the array even if they were modified by the datastore", async () => { it("Removes elements from the array even if they were modified by the datastore", async () => {
const [dataset, patient] = await getEmptyPatientDataset(); const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = ["Joe", "Blow"]; patient.name = ["Joe", "Blow"];
dataset.delete( dataset.delete(
quad( quad(
@ -758,6 +814,7 @@ describe("jsonldDatasetProxy", () => {
it("Removes and adds from the array even if they were modified by the datastore", async () => { it("Removes and adds from the array even if they were modified by the datastore", async () => {
const [dataset, patient] = await getEmptyPatientDataset(); const [dataset, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name = ["Joe", "Blow"]; patient.name = ["Joe", "Blow"];
dataset.delete( dataset.delete(
quad( quad(
@ -793,7 +850,10 @@ describe("jsonldDatasetProxy", () => {
it("Prevents duplicates for Objects", async () => { it("Prevents duplicates for Objects", async () => {
const [, observation] = await getLoadedDataset(); const [, observation] = await getLoadedDataset();
const roommates = observation.subject?.roommate as PatientShape[]; 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.length).toBe(1);
expect(roommates[0].name?.[0]).toBe("Amy"); expect(roommates[0].name?.[0]).toBe("Amy");
}); });
@ -824,12 +884,13 @@ describe("jsonldDatasetProxy", () => {
delete arr[5]; delete arr[5];
expect(arr).toEqual(["Garrett", "Bobby", "Ferguson"]); expect(arr).toEqual(["Garrett", "Bobby", "Ferguson"]);
expect(dataset.toString()).toEqual( 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 () => { it("Can set a triple object named node with just a string", async () => {
const [dataset, observation] = await getEmptyObservationDataset(); const [dataset, observation] = await getEmptyObservationDataset();
observation.type = { "@id": "Observation" };
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
observation.subject = "http://example.com/Patient1"; observation.subject = "http://example.com/Patient1";
@ -837,7 +898,7 @@ describe("jsonldDatasetProxy", () => {
"@id": "http://example.com/Patient1", "@id": "http://example.com/Patient1",
}); });
expect(dataset.toString()).toBe( 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); arr.copyWithin(0, 2, 3);
expect(arr).toEqual(["Ferguson", "Bobby"]); expect(arr).toEqual(["Ferguson", "Bobby"]);
expect(dataset.toString()).toEqual( 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); arr.copyWithin(0, 2);
expect(arr).toEqual(["Ferguson", "Bobby"]); expect(arr).toEqual(["Ferguson", "Bobby"]);
expect(dataset.toString()).toEqual( 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.copyWithin(0, undefined, 2)).not.toThrowError();
expect(arr).toEqual(["Garrett", "Bobby", "Ferguson"]); expect(arr).toEqual(["Garrett", "Bobby", "Ferguson"]);
expect(dataset.toString()).toEqual( 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); arr.fill("Beepy", 2, 5);
expect(arr).toEqual(["Garrett", "Bobby", "Beepy"]); expect(arr).toEqual(["Garrett", "Bobby", "Beepy"]);
expect(dataset.toString()).toEqual( 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.pop()).toBe("Ferguson");
expect(arr).toEqual(["Garrett", "Bobby"]); expect(arr).toEqual(["Garrett", "Bobby"]);
expect(dataset.toString()).toEqual( 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"); arr.push("Beepy");
expect(arr).toEqual(["Garrett", "Bobby", "Ferguson", "Beepy"]); expect(arr).toEqual(["Garrett", "Bobby", "Ferguson", "Beepy"]);
expect(dataset.toString()).toEqual( 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(); patient.name?.reverse();
expect(patient.name).toEqual(["Ferguson", "Bobby", "Garrett"]); expect(patient.name).toEqual(["Ferguson", "Bobby", "Garrett"]);
expect(dataset.toString()).toBe( 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.shift()).toEqual("Garrett");
expect(arr).toEqual(["Bobby", "Ferguson"]); expect(arr).toEqual(["Bobby", "Ferguson"]);
expect(dataset.toString()).toEqual( 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(patient.name).toEqual(["Bobby", "Garrett", "Ferguson"]);
expect(dataset.toString()).toBe( 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"); arr.splice(1, 0, "Beepy");
expect(arr).toEqual(["Garrett", "Beepy", "Bobby", "Ferguson"]); expect(arr).toEqual(["Garrett", "Beepy", "Bobby", "Ferguson"]);
expect(dataset.toString()).toEqual( 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); arr.splice(1, 1);
expect(arr).toEqual(["Garrett", "Ferguson"]); expect(arr).toEqual(["Garrett", "Ferguson"]);
expect(dataset.toString()).toEqual( 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"); arr.unshift("Beepy");
expect(arr).toEqual(["Beepy", "Garrett", "Bobby", "Ferguson"]); expect(arr).toEqual(["Beepy", "Garrett", "Bobby", "Ferguson"]);
expect(dataset.toString()).toEqual( 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 () => { it("errors if an object is added without the correct parameters", async () => {
expect(() => expect(() =>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
patients.push({ patients.push({
"@id": "http://example.com/Patient4", "@id": "http://example.com/Patient4",
name: ["Dippy"], name: ["Dippy"],
@ -1241,7 +1304,11 @@ describe("jsonldDatasetProxy", () => {
it("cannot write to a collection that matches the null, predicate, null pattern", () => { it("cannot write to a collection that matches the null, predicate, null pattern", () => {
expect( expect(
() => (patients[1] = { "@id": "http://example.com/Patient4" }), () =>
(patients[1] = {
"@id": "http://example.com/Patient4",
type: { "@id": "Patient" },
}),
).toThrow( ).toThrow(
"A collection that does not specify a match for both a subject or predicate cannot be modified directly.", "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 () => { it("initializes a patient using the fromJSON method", async () => {
const [, , builder] = await getEmptyPatientDataset(); const [, , builder] = await getEmptyPatientDataset();
const patient = builder.fromJson<PatientShape>({ const patient = builder.fromJson<PatientShape>({
type: { "@id": "Patient" },
name: ["Jack", "Horner"], name: ["Jack", "Horner"],
birthdate: "1725/11/03", birthdate: "1725/11/03",
age: 298, age: 298,
roommate: [ roommate: [
{ {
type: { "@id": "Patient" },
name: ["Ethical", "Bug"], name: ["Ethical", "Bug"],
}, },
], ],
@ -1288,11 +1357,13 @@ describe("jsonldDatasetProxy", () => {
const [, , builder] = await getEmptyPatientDataset(); const [, , builder] = await getEmptyPatientDataset();
const patient = builder.fromJson<PatientShape>({ const patient = builder.fromJson<PatientShape>({
"@id": "http://example.com/Patient13", "@id": "http://example.com/Patient13",
type: { "@id": "Patient" },
name: ["Jack", "Horner"], name: ["Jack", "Horner"],
birthdate: "1725/11/03", birthdate: "1725/11/03",
age: 298, age: 298,
roommate: [ roommate: [
{ {
type: { "@id": "Patient" },
name: ["Ethical", "Bug"], name: ["Ethical", "Bug"],
}, },
], ],
@ -1314,9 +1385,10 @@ describe("jsonldDatasetProxy", () => {
const patient4 = builder const patient4 = builder
.write(namedNode("http://example.com/Patient4Doc")) .write(namedNode("http://example.com/Patient4Doc"))
.fromSubject<PatientShape>(namedNode("https://example.com/Patient4")); .fromSubject<PatientShape>(namedNode("https://example.com/Patient4"));
patient4.type = { "@id": "Patient" };
patient4.name = ["Jackson"]; patient4.name = ["Jackson"];
expect(dataset.toString()).toBe( 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 doc3 = namedNode("http://example.com/Doc3");
const [, patient] = await getEmptyPatientDataset(); const [, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name?.push("default"); patient.name?.push("default");
const end1 = write(doc1).using(patient); const end1 = write(doc1).using(patient);
patient.name?.push("1"); patient.name?.push("1");
@ -1426,6 +1499,7 @@ describe("jsonldDatasetProxy", () => {
const doc1 = namedNode("http://example.com/Doc1"); const doc1 = namedNode("http://example.com/Doc1");
const [, patient] = await getEmptyPatientDataset(); const [, patient] = await getEmptyPatientDataset();
patient.type = { "@id": "Patient" };
patient.name?.push("Default"); patient.name?.push("Default");
const [patientOnDoc1] = write(doc1).usingCopy(patient); const [patientOnDoc1] = write(doc1).usingCopy(patient);
patientOnDoc1.name?.push("Doc1"); patientOnDoc1.name?.push("Doc1");
@ -1676,4 +1750,14 @@ describe("jsonldDatasetProxy", () => {
expect(enSet.size).toBe(0); 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 { ContextDefinition } from "jsonld";
import type { Schema } from "shexj"; import type { Schema } from "shexj";
import type { LdoJsonldContext } from "../src/LdoJsonldContext";
export interface ObservationShape { export interface ObservationShape {
"@id"?: string; "@id"?: string;
"@context"?: ContextDefinition; "@context"?: ContextDefinition;
type: { "@id": "Observation" };
subject?: PatientShape; subject?: PatientShape;
notes?: string; notes?: string;
langNotes?: string; langNotes?: string;
@ -12,7 +14,7 @@ export interface ObservationShape {
export type PatientShape = { export type PatientShape = {
"@id"?: string; "@id"?: string;
"@context"?: ContextDefinition; "@context"?: ContextDefinition;
type?: { "@id": "Patient" }; type: { "@id": "Patient" };
name?: string[]; name?: string[];
langName?: string[]; langName?: string[];
birthdate?: string; birthdate?: string;
@ -26,11 +28,12 @@ export type PatientShape = {
// @ts-ignore // @ts-ignore
export const patientSchema: Schema = {}; export const patientSchema: Schema = {};
export const patientContext: ContextDefinition = { export const patientUnnestedContext: ContextDefinition = {
type: { type: {
"@id": "@type", "@id": "@type",
}, },
Patient: "http://hl7.org/fhir/Patient", Patient: "http://hl7.org/fhir/Patient",
Observation: "http://hl7.org/fhir/Observation",
subject: { "@id": "http://hl7.org/fhir/subject", "@type": "@id" }, subject: { "@id": "http://hl7.org/fhir/subject", "@type": "@id" },
name: { name: {
"@id": "http://hl7.org/fhir/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 = ` export const patientData = `
@prefix example: <http://example.com/> . @prefix example: <http://example.com/> .
@prefix fhir: <http://hl7.org/fhir/> . @prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @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:notes "Cool Notes"^^xsd:string ;
fhir:subject example:Patient1 . fhir:subject example:Patient1 .
example:Patient1 example:Patient1 a fhir:Patient ;
rdf:type fhir:Patient ; rdf:type fhir:Patient ;
fhir:name "Garrett"^^xsd:string, "Bobby"^^xsd:string, "Ferguson"^^xsd:string ; fhir:name "Garrett"^^xsd:string, "Bobby"^^xsd:string, "Ferguson"^^xsd:string ;
fhir:birthdate "1986-01-01"^^xsd:date ; fhir:birthdate "1986-01-01"^^xsd:date ;
@ -87,7 +142,7 @@ example:Patient1
fhir:isHappy "true"^^xsd:boolean ; fhir:isHappy "true"^^xsd:boolean ;
fhir:roommate example:Patient2, example:Patient3 . fhir:roommate example:Patient2, example:Patient3 .
example:Patient2 example:Patient2 a fhir:Patient ;
rdf:type fhir:Patient ; rdf:type fhir:Patient ;
fhir:name "Rob"^^xsd:string ; fhir:name "Rob"^^xsd:string ;
fhir:birthdate "1987-01-01"^^xsd:date ; fhir:birthdate "1987-01-01"^^xsd:date ;
@ -95,7 +150,7 @@ example:Patient2
fhir:isHappy "false"^^xsd:boolean ; fhir:isHappy "false"^^xsd:boolean ;
fhir:roommate example:Patient1, example:Patient3 . fhir:roommate example:Patient1, example:Patient3 .
example:Patient3 example:Patient3 a fhir:Patient ;
rdf:type fhir:Patient ; rdf:type fhir:Patient ;
fhir:name "Amy"^^xsd:string ; fhir:name "Amy"^^xsd:string ;
fhir:birthdate "1988-01-01"^^xsd:date ; fhir:birthdate "1988-01-01"^^xsd:date ;
@ -108,25 +163,25 @@ export const patientDataWithBlankNodes = `
@prefix fhir: <http://hl7.org/fhir/> . @prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
example:Observation1 example:Observation1 a fhir:Observation ;
fhir:notes "Cool Notes"^^xsd:string ; fhir:notes "Cool Notes"^^xsd:string ;
fhir:subject _:Patient1 . fhir:subject _:Patient1 .
_:Patient1 _:Patient1 a fhir:Patient ;
fhir:name "Garrett"^^xsd:string, "Bobby"^^xsd:string, "Ferguson"^^xsd:string ; fhir:name "Garrett"^^xsd:string, "Bobby"^^xsd:string, "Ferguson"^^xsd:string ;
fhir:birthdate "1986-01-01"^^xsd:date ; fhir:birthdate "1986-01-01"^^xsd:date ;
fhir:age "35"^^xsd:integer ; fhir:age "35"^^xsd:integer ;
fhir:isHappy "true"^^xsd:boolean ; fhir:isHappy "true"^^xsd:boolean ;
fhir:roommate _:Patient2, _:Patient3 . fhir:roommate _:Patient2, _:Patient3 .
_:Patient2 _:Patient2 a fhir:Patient ;
fhir:name "Rob"^^xsd:string ; fhir:name "Rob"^^xsd:string ;
fhir:birthdate "1987-01-01"^^xsd:date ; fhir:birthdate "1987-01-01"^^xsd:date ;
fhir:age "34"^^xsd:integer ; fhir:age "34"^^xsd:integer ;
fhir:isHappy "false"^^xsd:boolean ; fhir:isHappy "false"^^xsd:boolean ;
fhir:roommate _:Patient1, _:Patient3 . fhir:roommate _:Patient1, _:Patient3 .
_:Patient3 _:Patient3 a fhir:Patient ;
fhir:name "Amy"^^xsd:string ; fhir:name "Amy"^^xsd:string ;
fhir:birthdate "1988-01-01"^^xsd:date ; fhir:birthdate "1988-01-01"^^xsd:date ;
fhir:age "33"^^xsd:integer ; fhir:age "33"^^xsd:integer ;
@ -138,14 +193,14 @@ export const tinyPatientData = `
@prefix fhir: <http://hl7.org/fhir/> . @prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
example:Observation1 example:Observation1 a fhir:Observation ;
fhir:subject example:Patient1 . fhir:subject example:Patient1 .
example:Patient1 example:Patient1 a fhir:Patient ;
fhir:name "Garrett"^^xsd:string ; fhir:name "Garrett"^^xsd:string ;
fhir:roommate example:Patient2 . fhir:roommate example:Patient2 .
example:Patient2 example:Patient2 a fhir:Patient ;
fhir:name "Rob"^^xsd:string ; fhir:name "Rob"^^xsd:string ;
fhir:roommate example:Patient1 . fhir:roommate example:Patient1 .
`; `;
@ -155,7 +210,7 @@ export const tinyArrayPatientData = `
@prefix fhir: <http://hl7.org/fhir/> . @prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @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 . 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 fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
example:Observation1 example:Observation1 a fhir:Observation ;
fhir:subject _:Patient1 . fhir:subject _:Patient1 .
_:Patient1 _:Patient1 a fhir:Patient ;
fhir:name "Garrett"^^xsd:string ; fhir:name "Garrett"^^xsd:string ;
fhir:roommate _:Patient2 . fhir:roommate _:Patient2 .
_:Patient2 _:Patient2 a fhir:Patient ;
fhir:name "Rob"^^xsd:string ; fhir:name "Rob"^^xsd:string ;
fhir:roommate _:Patient1 . fhir:roommate _:Patient1 .
`; `;
@ -181,14 +236,14 @@ export const tinyPatientDataWithLanguageTags = `
@prefix fhir: <http://hl7.org/fhir/> . @prefix fhir: <http://hl7.org/fhir/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
example:Observation1 example:Observation1 a fhir:Observation ;
fhir:subject example:Patient1 ; fhir:subject example:Patient1 ;
fhir:langNotes "Cool Notes" ; fhir:langNotes "Cool Notes" ;
fhir:langNotes "Cooler Notes"@en ; fhir:langNotes "Cooler Notes"@en ;
fhir:langNotes "Notas Geniales"@es ; fhir:langNotes "Notas Geniales"@es ;
fhir:langNotes "Notes Sympas"@fr . fhir:langNotes "Notes Sympas"@fr .
example:Patient1 example:Patient1 a fhir:Patient ;
fhir:langName "Jon" ; fhir:langName "Jon" ;
fhir:langName "John"@en ; fhir:langName "John"@en ;
fhir:langName "Juan"@es ; 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", "homepage": "https://github.com/o-development/ldobjects/tree/main/packages/schema-converter-shex#readme",
"devDependencies": { "devDependencies": {
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.24",
"@shexjs/parser": "^1.0.0-alpha.24", "@shexjs/parser": "^1.0.0-alpha.24",
"@types/jest": "^27.0.3", "@types/jest": "^27.0.3",
"@types/jsonld": "^1.5.6", "@types/jsonld": "^1.5.6",
@ -34,7 +35,7 @@
], ],
"dependencies": { "dependencies": {
"@ldo/traverser-shexj": "^0.0.1-alpha.24", "@ldo/traverser-shexj": "^0.0.1-alpha.24",
"dts-dom": "^3.6.0", "dts-dom": "~3.6.0",
"jsonld2graphobject": "^0.0.5" "jsonld2graphobject": "^0.0.5"
}, },
"publishConfig": { "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 * JsonLd Context Builder
*/ */
export class JsonLdContextBuilder { export class JsonLdContextBuilder {
private iriAnnotations: Record<string, Annotation[]> = {}; protected iriAnnotations: Record<string, Annotation[]> = {};
private iriTypes: Record<string, ExpandedTermDefinition> = {}; protected iriTypes: Record<
private generatedNames: Record<string, string> | undefined; 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[]) { addSubject(iri: string, rdfType?: string, annotations?: Annotation[]) {
if (!this.iriAnnotations[iri]) { const relevantBuilder = this.getRelevantBuilder(rdfType);
this.iriAnnotations[iri] = []; if (!relevantBuilder.iriAnnotations[iri]) {
relevantBuilder.iriAnnotations[iri] = [];
} }
if (annotations && annotations.length > 0) { if (annotations && annotations.length > 0) {
this.iriAnnotations[iri].push(...annotations); relevantBuilder.iriAnnotations[iri].push(...annotations);
} }
} }
@ -68,22 +89,24 @@ export class JsonLdContextBuilder {
iri: string, iri: string,
expandedTermDefinition: ExpandedTermDefinition, expandedTermDefinition: ExpandedTermDefinition,
isContainer: boolean, isContainer: boolean,
rdfType?: string,
annotations?: Annotation[], annotations?: Annotation[],
) { ) {
this.addSubject(iri, annotations); const relevantBuilder = this.getRelevantBuilder(rdfType);
if (!this.iriTypes[iri]) { relevantBuilder.addSubject(iri, undefined, annotations);
this.iriTypes[iri] = expandedTermDefinition; if (!relevantBuilder.iriTypes[iri]) {
relevantBuilder.iriTypes[iri] = expandedTermDefinition;
if (isContainer) { if (isContainer) {
this.iriTypes[iri]["@container"] = "@set"; relevantBuilder.iriTypes[iri]["@isCollection"] = true;
} }
} else { } else {
const curDef = this.iriTypes[iri]; const curDef = relevantBuilder.iriTypes[iri];
const newDef = expandedTermDefinition; const newDef = expandedTermDefinition;
// TODO: if you reuse the same predicate with a different cardinality, // TODO: if you reuse the same predicate with a different cardinality,
// it will overwrite the past cardinality. Perhapse we might want to // it will overwrite the past cardinality. Perhapse we might want to
// split contexts in the various shapes. // split contexts in the various shapes.
if (isContainer) { if (isContainer) {
curDef["@container"] = "@set"; curDef["@isCollection"] = true;
} }
// If the old and new versions both have types // If the old and new versions both have types
if (curDef["@type"] && newDef["@type"]) { if (curDef["@type"] && newDef["@type"]) {
@ -149,12 +172,13 @@ export class JsonLdContextBuilder {
return generatedNames; return generatedNames;
} }
getNameFromIri(iri: string) { getNameFromIri(iri: string, rdfType?: string) {
if (!this.generatedNames) { const relevantBuilder = this.getRelevantBuilder(rdfType);
this.generatedNames = this.generateNames(); if (!relevantBuilder.generatedNames) {
relevantBuilder.generatedNames = relevantBuilder.generateNames();
} }
if (this.generatedNames[iri]) { if (relevantBuilder.generatedNames[iri]) {
return this.generatedNames[iri]; return relevantBuilder.generatedNames[iri];
} else { } else {
return iri; return iri;
} }
@ -165,13 +189,25 @@ export class JsonLdContextBuilder {
const namesMap = this.generateNames(); const namesMap = this.generateNames();
Object.entries(namesMap).forEach(([iri, name]) => { Object.entries(namesMap).forEach(([iri, name]) => {
if (this.iriTypes[iri]) { if (this.iriTypes[iri]) {
contextDefnition[name] = { let subContext: ExpandedTermDefinition = {
"@id": "@id":
iri === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" iri === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
? "@type" ? "@type"
: iri, : 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 { } else {
contextDefnition[name] = iri; contextDefnition[name] = iri;
} }

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

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

@ -3,9 +3,11 @@ import * as dom from "dts-dom";
import type { Annotation } from "shexj"; import type { Annotation } from "shexj";
import { nameFromObject } from "../context/JsonLdContextBuilder"; import { nameFromObject } from "../context/JsonLdContextBuilder";
import type { ShapeInterfaceDeclaration } from "./ShapeInterfaceDeclaration"; import type { ShapeInterfaceDeclaration } from "./ShapeInterfaceDeclaration";
import { getRdfTypesForTripleConstraint } from "../util/getRdfTypesForTripleConstraint";
import { dedupeObjectTypeMembers } from "./util/dedupeObjectTypeMembers";
export interface ShexJTypeTransformerContext { export interface ShexJTypeTransformerContext {
getNameFromIri: (iri: string) => string; getNameFromIri: (iri: string, rdfType?: string) => string;
} }
export function commentFromAnnotations( export function commentFromAnnotations(
@ -125,12 +127,19 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
} }
// Use EXTENDS // Use EXTENDS
if (transformedChildren.extends) { if (transformedChildren.extends) {
newInterface.baseTypes = [];
transformedChildren.extends.forEach((extendsItem) => { transformedChildren.extends.forEach((extendsItem) => {
if ((extendsItem as dom.InterfaceDeclaration).kind === "interface") { const extendsInterface = extendsItem as dom.InterfaceDeclaration;
newInterface.baseTypes?.push( if (extendsInterface.kind === "interface") {
extendsItem as dom.InterfaceDeclaration, // 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 const objectType = name
? dom.create.interface(name) ? dom.create.interface(name)
: dom.create.objectType([]); : dom.create.objectType([]);
const eachOfComment = commentFromAnnotations(eachOf.annotations);
setReturnPointer(objectType); setReturnPointer(objectType);
// Get Input property expressions // Get Input property expressions
const inputPropertyExpressions: dom.PropertyDeclaration[] = []; const inputPropertyExpressions: dom.PropertyDeclaration[] = [];
@ -178,49 +186,9 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
} }
}, },
); );
objectType.members.push(
// Merge property expressions ...dedupeObjectTypeMembers(inputPropertyExpressions),
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));
return objectType; return objectType;
}, },
}, },
@ -229,10 +197,19 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
tripleConstraint, tripleConstraint,
getTransformedChildren, getTransformedChildren,
setReturnPointer, setReturnPointer,
node,
context, context,
) => { ) => {
const transformedChildren = await getTransformedChildren(); 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 = const isArray =
tripleConstraint.max !== undefined && tripleConstraint.max !== 1; tripleConstraint.max !== undefined && tripleConstraint.max !== 1;
const isOptional = tripleConstraint.min === 0; const isOptional = tripleConstraint.min === 0;
@ -258,6 +235,7 @@ export const ShexJTypingTransformer = ShexJTraverser.createTransformer<
nodeConstraint, nodeConstraint,
_getTransformedChildren, _getTransformedChildren,
setReturnPointer, setReturnPointer,
node,
context, context,
) => { ) => {
if (nodeConstraint.datatype) { if (nodeConstraint.datatype) {

@ -42,7 +42,7 @@ export async function shexjToTyping(
.emit(declaration, { .emit(declaration, {
rootFlags: dom.ContextFlags.InAmbientNamespace, rootFlags: dom.ContextFlags.InAmbientNamespace,
}) })
.replace("\r\n", "\n"), .replace(/\r\n/g, "\n"),
dts: declaration, 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", baseNode: "http://example.com/SampleParent",
successfulContext: { successfulContext: {
type: { "@id": "@type" }, Parent: {
Parent: "http://example.com/Parent", "@id": "http://example.com/Parent",
hasChild: { "@id": "http://example.com/hasChild", "@type": "@id" }, "@context": {
Child: "http://example.com/Child", type: { "@id": "@type" },
hasParent: { "@id": "http://example.com/hasParent", "@type": "@id" }, 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: 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/> PREFIX foaf: <http://xmlns.com/foaf/0.1/>
ex:EntityShape { ex:EntityShape {
a [ ex:Entity ] ;
ex:entityId . ex:entityId .
} }
ex:PersonShape EXTENDS @ex:EntityShape { ex:PersonShape EXTENDS @ex:EntityShape {
a [ ex:Person ] ;
foaf:name . foaf:name .
} }
ex:EmployeeShape EXTENDS @ex:PersonShape { ex:EmployeeShape EXTENDS @ex:PersonShape {
a [ ex:Employee ] ;
ex:employeeNumber . ex:employeeNumber .
} }
`, `,
@ -34,10 +37,37 @@ export const extendsSimple: TestData = {
`, `,
baseNode: "http://example.com/SampleParent", baseNode: "http://example.com/SampleParent",
successfulContext: { successfulContext: {
entityId: "https://example.com/entityId", Entity: {
name: "http://xmlns.com/foaf/0.1/name", "@id": "https://example.com/Entity",
employeeNumber: "https://example.com/employeeNumber", "@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: 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"; import type { TestData } from "./testData";
/** /**
* Circular * Old Extends
*/ */
export const oldExtends: TestData = { export const oldExtends: TestData = {
name: "old extends", name: "old extends",
@ -9,21 +9,24 @@ export const oldExtends: TestData = {
PREFIX ex: <https://example.com/> PREFIX ex: <https://example.com/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/> PREFIX foaf: <http://xmlns.com/foaf/0.1/>
ex:EntityShape { ex:EntityShape EXTRA a {
$ex:EntityRef ( $ex:EntityRef (
a [ ex:Entity ] ;
ex:entityId . ex:entityId .
) )
} }
ex:PersonShape { ex:PersonShape EXTRA a {
$ex:PersonRef ( $ex:PersonRef (
&ex:EntityRef ; &ex:EntityRef ;
a [ ex:Person ] ;
foaf:name . foaf:name .
) )
} }
ex:EmployeeShape EXTENDS @ex:PersonShape { ex:EmployeeShape EXTRA a {
&ex:PersonRef ; &ex:PersonRef ;
a [ ex:Employee ] ;
ex:employeeNumber . ex:employeeNumber .
} }
`, `,
@ -39,10 +42,37 @@ export const oldExtends: TestData = {
`, `,
baseNode: "http://example.com/SampleParent", baseNode: "http://example.com/SampleParent",
successfulContext: { successfulContext: {
entityId: "https://example.com/entityId", Entity: {
name: "http://xmlns.com/foaf/0.1/name", "@id": "https://example.com/Entity",
employeeNumber: "https://example.com/employeeNumber", "@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: 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: ``, sampleTurtle: ``,
baseNode: "", baseNode: "",
successfulContext: { successfulContext: {
type: { "@id": "@type" }, Person: {
Person: "http://schema.org/Person", "@id": "http://schema.org/Person",
Person2: "http://xmlns.com/foaf/0.1/Person", "@context": {
hasEmail: { type: {
"@id": "http://www.w3.org/2006/vcard/ns#hasEmail", "@id": "@type",
"@type": "@id", },
"@container": "@set", 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", Person2: {
Home: "http://www.w3.org/2006/vcard/ns#Home", "@id": "http://xmlns.com/foaf/0.1/Person",
ISDN: "http://www.w3.org/2006/vcard/ns#ISDN", "@context": {
Internet: "http://www.w3.org/2006/vcard/ns#Internet", type: {
Intl: "http://www.w3.org/2006/vcard/ns#Intl", "@id": "@type",
Label: "http://www.w3.org/2006/vcard/ns#Label", },
Parcel: "http://www.w3.org/2006/vcard/ns#Parcel", hasEmail: {
Postal: "http://www.w3.org/2006/vcard/ns#Postal", "@id": "http://www.w3.org/2006/vcard/ns#hasEmail",
Pref: "http://www.w3.org/2006/vcard/ns#Pref", "@type": "@id",
Work: "http://www.w3.org/2006/vcard/ns#Work", "@isCollection": true,
X400: "http://www.w3.org/2006/vcard/ns#X400", },
value: { "@id": "http://www.w3.org/2006/vcard/ns#value", "@type": "@id" }, name: {
name: { "@id": "http://xmlns.com/foaf/0.1/name",
"@id": "http://xmlns.com/foaf/0.1/name", "@type": "http://www.w3.org/2001/XMLSchema#string",
"@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: 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 { app:LawShape {
rdf:type [ app:Law ] ; rdf:type [ app:Law ] ;
app:name xsd:string ; app:name xsd:string *;
app:path IRI ; app:path IRI ;
} }
app:VocabularyShape { app:VocabularyShape {
rdf:type [ app:Vocabulary ] ; rdf:type [ app:Vocabulary ] ;
app:name xsd:string ; app:name xsd:string ;
app:path IRI ; app:path IRI *;
} }
`, `,
sampleTurtle: ``, sampleTurtle: ``,
baseNode: "http://example.com/SampleParent", baseNode: "http://example.com/SampleParent",
successfulContext: { successfulContext: {
type: { "@id": "@type" }, Document: {
Document: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Document", "@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Document",
vocabulary: { "@context": {
"@id": type: {
"https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#vocabulary", "@id": "@type",
"@type": "@id", },
"@container": "@set", 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: Vocabulary: {
"https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Vocabulary", "@id":
name: { "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Vocabulary",
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name", "@context": {
"@type": "http://www.w3.org/2001/XMLSchema#string", type: {
}, "@id": "@type",
path: { },
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path", name: {
"@type": "@id", "@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: { Law: {
"@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#law", "@id": "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Law",
"@type": "@id", "@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: 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: { givenName: {
"@id": "http://xmlns.com/foaf/0.1/givenName", "@id": "http://xmlns.com/foaf/0.1/givenName",
"@type": "http://www.w3.org/2001/XMLSchema#string", "@type": "http://www.w3.org/2001/XMLSchema#string",
"@container": "@set", "@isCollection": true,
}, },
familyName: { familyName: {
"@id": "http://xmlns.com/foaf/0.1/familyName", "@id": "http://xmlns.com/foaf/0.1/familyName",
@ -41,10 +41,10 @@ export const simple: TestData = {
phone: { phone: {
"@id": "http://xmlns.com/foaf/0.1/phone", "@id": "http://xmlns.com/foaf/0.1/phone",
"@type": "@id", "@type": "@id",
"@container": "@set", "@isCollection": true,
}, },
mbox: { "@id": "http://xmlns.com/foaf/0.1/mbox", "@type": "@id" }, mbox: { "@id": "http://xmlns.com/foaf/0.1/mbox", "@type": "@id" },
}, },
successfulTypings: 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 { activityPub } from "./activityPub";
import { circular } from "./circular"; import { circular } from "./circular";
import { profile } from "./profile"; import { profile } from "./profile";
@ -6,14 +6,14 @@ import { reducedProfile } from "./reducedProfile";
import { simple } from "./simple"; import { simple } from "./simple";
import { extendsSimple } from "./extendsSimple"; import { extendsSimple } from "./extendsSimple";
import { reusedPredicates } from "./reusedPredicates"; import { reusedPredicates } from "./reusedPredicates";
// import { oldExtends } from "./oldExtends"; import { oldExtends } from "./oldExtends";
export interface TestData { export interface TestData {
name: string; name: string;
shexc: string; shexc: string;
sampleTurtle: string; sampleTurtle: string;
baseNode: string; baseNode: string;
successfulContext: ContextDefinition; successfulContext: LdoJsonldContext;
successfulTypings: string; successfulTypings: string;
} }
@ -24,6 +24,6 @@ export const testData: TestData[] = [
reducedProfile, reducedProfile,
activityPub, activityPub,
extendsSimple, extendsSimple,
// oldExtends, oldExtends,
reusedPredicates, 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, receiver,
) => { ) => {
const subject = target["@id"]; const subject = target["@id"];
const rdfTypes = this.getRdfType(subject);
if (typeof key === "symbol") { if (typeof key === "symbol") {
// Do Nothing // Do Nothing
} else if (key === "@id") { } else if (key === "@id") {
this.addListener([subject, null, null, null]); this.addListener([subject, null, null, null]);
} else if (!this.contextUtil.isArray(key)) { } else if (!this.contextUtil.isArray(key, rdfTypes)) {
const predicate = namedNode(this.contextUtil.keyToIri(key)); const predicate = namedNode(this.contextUtil.keyToIri(key, rdfTypes));
this.addListener([subject, predicate, null, null]); this.addListener([subject, predicate, null, null]);
} }
return oldGetFunction && oldGetFunction(target, key, receiver); return oldGetFunction && oldGetFunction(target, key, receiver);

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

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

@ -41,7 +41,7 @@ import type {
tripleExprRef, tripleExprRef,
valueSetValue, valueSetValue,
Wildcard, Wildcard,
} from "shexj"; } from "./ShexJTypes";
import type { ValidateTraverserTypes } from "@ldo/type-traverser"; import type { ValidateTraverserTypes } from "@ldo/type-traverser";
export type ShexJTraverserTypes = ValidateTraverserTypes<{ export type ShexJTraverserTypes = ValidateTraverserTypes<{
@ -110,7 +110,7 @@ export type ShexJTraverserTypes = ValidateTraverserTypes<{
shapeDeclRef: { shapeDeclRef: {
kind: "union"; kind: "union";
type: shapeDeclRef; type: shapeDeclRef;
typeNames: "shapeDeclLabel"; typeNames: "shapeDeclLabel" | "ShapeDecl";
}; };
shapeDeclLabel: { shapeDeclLabel: {
kind: "union"; 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. - `type`: The typescript type corresponding to this primitive.
### Creating a traverser definition ### 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) ![Traverse Definition IntelliSense](/tutorialImages/TraveserDefinitionIntellisense.png)
In our example, the TraverserDefinition looks like: In our example, the TraverserDefinitions looks like:
```typescript ```typescript
const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> = { const avatarTraverserDefinitions: TraverserDefinitions<AvatarTraverserTypes> = {
Element: { Element: {
kind: "primitive", kind: "primitive",
}, },
@ -193,7 +193,7 @@ const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> = {
``` ```
#### Defining a Union Selector #### 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. 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 ```typescript
const avatarTraverser = new Traverser<AvatarTraverserTypes>( const avatarTraverser = new Traverser<AvatarTraverserTypes>(
avatarTraverserDefinition avatarTraverserDefinitions
); );
``` ```

@ -1,5 +1,10 @@
import type { TraverserDefinition, ValidateTraverserTypes } from "../src"; import type {
import { Traverser } from "../src"; ValidateTraverserTypes,
ItemNamed,
TraverserDefinitions,
} from "../src";
import { InstanceGraph } from "../src/instanceGraph/instanceGraph";
import type { ParentIdentifiers } from "../src/ReverseRelationshipTypes";
async function run() { async function run() {
/** /**
@ -56,7 +61,7 @@ async function run() {
}; };
NonBender: { NonBender: {
kind: "interface"; kind: "interface";
type: Bender; type: NonBender;
properties: { properties: {
friends: "Person"; 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 * Create the traverser definition
*/ */
const avatarTraverserDefinition: TraverserDefinition<AvatarTraverserTypes> = { const avatarTraverserDefinition: TraverserDefinitions<AvatarTraverserTypes> =
Element: { {
kind: "primitive", Element: {
}, kind: "primitive",
Bender: {
kind: "interface",
properties: {
element: "Element",
friends: "Person",
}, },
}, Bender: {
NonBender: { kind: "interface",
kind: "interface", properties: {
properties: { element: "Element",
friends: "Person", friends: "Person",
},
}, },
}, NonBender: {
Person: { kind: "interface",
kind: "union", properties: {
selector: (item) => { friends: "Person",
return (item as Bender).element ? "Bender" : "NonBender"; },
}, },
}, Person: {
}; kind: "union",
selector: (item) => {
return (item as Bender).element ? "Bender" : "NonBender";
},
},
};
/** /**
* Instantiate the Traverser * Instantiate the Traverser
@ -103,65 +117,65 @@ async function run() {
avatarTraverserDefinition, avatarTraverserDefinition,
); );
/** // /**
* Create a visitor // * Create a visitor
*/ // */
const avatarVisitor = avatarTraverser.createVisitor<undefined>({ // const avatarVisitor = avatarTraverser.createVisitor<undefined>({
Element: async (item) => { // Element: async (item) => {
console.log(`Element: ${item}`); // console.log(`Element: ${item}`);
}, // },
Bender: { // Bender: {
visitor: async (item) => { // visitor: async (item) => {
console.log(`Bender: ${item.name}`); // console.log(`Bender: ${item.name}`);
}, // },
properties: { // properties: {
element: async (item) => { // element: async (item) => {
console.log(`Bender.element: ${item}`); // console.log(`Bender.element: ${item}`);
}, // },
}, // },
}, // },
NonBender: { // NonBender: {
visitor: async (item) => { // visitor: async (item) => {
console.log(`NonBender: ${item.name}`); // console.log(`NonBender: ${item.name}`);
}, // },
}, // },
Person: async (item) => { // Person: async (item) => {
console.log(`Person: ${item.name}`); // console.log(`Person: ${item.name}`);
}, // },
}); // });
/** // /**
* Run the visitor on data // * Run the visitor on data
*/ // */
console.log( // console.log(
"############################## Visitor Logs ##############################", // "############################## Visitor Logs ##############################",
); // );
await avatarVisitor.visit(aang, "Bender", undefined); // await avatarVisitor.visit(aang, "Bender", undefined);
/** // /**
* Create a visitor that uses context // * Create a visitor that uses context
*/ // */
interface AvatarCountingVisitorContext { // interface AvatarCountingVisitorContext {
numberOfBenders: number; // numberOfBenders: number;
} // }
const avatarCountingVisitor = // const avatarCountingVisitor =
avatarTraverser.createVisitor<AvatarCountingVisitorContext>({ // avatarTraverser.createVisitor<AvatarCountingVisitorContext>({
Bender: { // Bender: {
visitor: async (item, context) => { // visitor: async (item, context) => {
context.numberOfBenders++; // context.numberOfBenders++;
}, // },
}, // },
}); // });
/** // /**
* Run the counting visitor // * Run the counting visitor
*/ // */
console.log( // console.log(
"############################## Found Number of Benders Using Visitor ##############################", // "############################## Found Number of Benders Using Visitor ##############################",
); // );
const countContext: AvatarCountingVisitorContext = { numberOfBenders: 0 }; // const countContext: AvatarCountingVisitorContext = { numberOfBenders: 0 };
await avatarCountingVisitor.visit(aang, "Bender", countContext); // await avatarCountingVisitor.visit(aang, "Bender", countContext);
console.log(countContext.numberOfBenders); // console.log(countContext.numberOfBenders);
/** /**
* Set up a transformer * 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 "./UtilTypes";
export * from "./TraverserDefinition";
export * from "./TransformerReturnTypes"; export * from "./traverser/TraverserDefinition";
export * from "./TransformerReturnTypesDefaults"; export * from "./transformer/TransformerReturnTypes";
export * from "./Traverser"; export * from "./transformer/TransformerReturnTypesDefaults";
export * from "./Transformer"; export * from "./traverser/Traverser";
export * from "./Visitor"; export * from "./transformer/Transformer";
export * from "./Visitors";
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, PrimitiveReturnType,
PrimitiveType, PrimitiveType,
TransformerInputReturnTypes, TransformerInputReturnTypes,
TraverserDefinition, TraverserDefinitions,
TraverserTypes, TraverserTypes,
UnionReturnType, UnionReturnType,
UnionType, UnionType,
} from "."; } from "..";
import { transformerParentSubTraverser } from "./transformerSubTraversers/TransformerParentSubTraverser"; import { transformerParentSubTraverser } from "./transformerSubTraversers/TransformerParentSubTraverser";
import { CircularDepenedencyAwaiter } from "./transformerSubTraversers/util/CircularDependencyAwaiter"; import { CircularDepenedencyAwaiter } from "./transformerSubTraversers/util/CircularDependencyAwaiter";
import { MultiMap } from "./transformerSubTraversers/util/MultiMap"; import { MultiMap } from "./transformerSubTraversers/util/MultiMap";
@ -28,6 +28,7 @@ import type {
UnionTransformerDefinition, UnionTransformerDefinition,
UnionTransformerInputDefinition, UnionTransformerInputDefinition,
} from "./Transformers"; } from "./Transformers";
import { InstanceGraph } from "../instanceGraph/instanceGraph";
// TODO: Lots of "any" in this file. I'm just done with fancy typescript, // 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. // 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>, InputReturnTypes extends TransformerInputReturnTypes<Types>,
Context = undefined, Context = undefined,
> { > {
private traverserDefinition: TraverserDefinition<Types>; private traverserDefinition: TraverserDefinitions<Types>;
private transformers: Transformers< private transformers: Transformers<
Types, Types,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
@ -46,7 +47,7 @@ export class Transformer<
>; >;
constructor( constructor(
traverserDefinition: TraverserDefinition<Types>, traverserDefinition: TraverserDefinitions<Types>,
transformers: TransformersInput<Types, InputReturnTypes, Context>, transformers: TransformersInput<Types, InputReturnTypes, Context>,
) { ) {
this.traverserDefinition = traverserDefinition; this.traverserDefinition = traverserDefinition;
@ -54,12 +55,14 @@ export class Transformer<
} }
private applyDefaultInterfaceTransformerProperties< private applyDefaultInterfaceTransformerProperties<
Type extends InterfaceType<keyof Types>, TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
ReturnType extends InterfaceReturnType<Type>, ReturnType extends InterfaceReturnType<Type>,
>( >(
typeName: keyof Types, typeName: keyof Types,
typePropertiesInput: InterfaceTransformerInputDefinition< typePropertiesInput: InterfaceTransformerInputDefinition<
Types, Types,
TypeName,
Type, Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType, ReturnType,
@ -67,6 +70,7 @@ export class Transformer<
>["properties"], >["properties"],
): InterfaceTransformerDefinition< ): InterfaceTransformerDefinition<
Types, Types,
TypeName,
Type, Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType, ReturnType,
@ -89,6 +93,7 @@ export class Transformer<
return agg; return agg;
}, {}) as InterfaceTransformerDefinition< }, {}) as InterfaceTransformerDefinition<
Types, Types,
TypeName,
Type, Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType, ReturnType,
@ -97,12 +102,14 @@ export class Transformer<
} }
private applyDefaultInterfaceTransformer< private applyDefaultInterfaceTransformer<
Type extends InterfaceType<keyof Types>, TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
ReturnType extends InterfaceReturnType<Type>, ReturnType extends InterfaceReturnType<Type>,
>( >(
typeName: keyof Types, typeName: keyof Types,
typeInput?: InterfaceTransformerInputDefinition< typeInput?: InterfaceTransformerInputDefinition<
Types, Types,
TypeName,
Type, Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType, ReturnType,
@ -110,6 +117,7 @@ export class Transformer<
>, >,
): InterfaceTransformerDefinition< ): InterfaceTransformerDefinition<
Types, Types,
TypeName,
Type, Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType, ReturnType,
@ -139,11 +147,13 @@ export class Transformer<
} }
private applyDefaultUnionTransformer< private applyDefaultUnionTransformer<
Type extends UnionType<keyof Types>, TypeName extends keyof Types,
Type extends UnionType<keyof Types> & Types[TypeName],
ReturnType extends UnionReturnType, ReturnType extends UnionReturnType,
>( >(
typeInput?: UnionTransformerInputDefinition< typeInput?: UnionTransformerInputDefinition<
Types, Types,
TypeName,
Type, Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType, ReturnType,
@ -151,6 +161,7 @@ export class Transformer<
>, >,
): UnionTransformerDefinition< ): UnionTransformerDefinition<
Types, Types,
TypeName,
Type, Type,
ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>, ApplyTransformerReturnTypesDefaults<Types, InputReturnTypes>,
ReturnType, ReturnType,
@ -168,11 +179,24 @@ export class Transformer<
} }
private applyDefaultPrimitiveTransformer< private applyDefaultPrimitiveTransformer<
Type extends PrimitiveType, TypeName extends keyof Types,
Type extends PrimitiveType & Types[TypeName],
ReturnType extends PrimitiveReturnType, ReturnType extends PrimitiveReturnType,
>( >(
typeInput?: PrimitiveTransformerInputDefinition<Type, ReturnType, Context>, typeInput?: PrimitiveTransformerInputDefinition<
): PrimitiveTransformerDefinition<Type, ReturnType, Context> { Types,
TypeName,
Type,
ReturnType,
Context
>,
): PrimitiveTransformerDefinition<
Types,
TypeName,
Type,
ReturnType,
Context
> {
if (!typeInput) { if (!typeInput) {
return async (originalData) => { return async (originalData) => {
return originalData; return originalData;
@ -229,12 +253,15 @@ export class Transformer<
>[TypeName]["return"] >[TypeName]["return"]
> { > {
const superPromise = new SuperPromise(); const superPromise = new SuperPromise();
const instanceGraph = new InstanceGraph(this.traverserDefinition);
instanceGraph.getNodeFor(item, itemTypeName);
const toReturn = await transformerParentSubTraverser(item, itemTypeName, { const toReturn = await transformerParentSubTraverser(item, itemTypeName, {
traverserDefinition: this.traverserDefinition, traverserDefinition: this.traverserDefinition,
transformers: this.transformers, transformers: this.transformers,
executingPromises: new MultiMap(), executingPromises: new MultiMap(),
circularDependencyAwaiter: new CircularDepenedencyAwaiter(), circularDependencyAwaiter: new CircularDepenedencyAwaiter(),
superPromise, superPromise,
instanceGraph,
context, context,
}); });
await superPromise.wait(); await superPromise.wait();

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

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

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

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

@ -1,5 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* 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 type { TransformerReturnTypes } from "../TransformerReturnTypes";
import { transformerInterfaceSubTraverser } from "./TransformerInterfaceSubTraverser"; import { transformerInterfaceSubTraverser } from "./TransformerInterfaceSubTraverser";
import { transformerPrimitiveSubTraverser } from "./TransformerPrimitiveSubTraverser"; import { transformerPrimitiveSubTraverser } from "./TransformerPrimitiveSubTraverser";

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

@ -1,4 +1,4 @@
import type { KeyTypes } from "../.."; import type { KeyTypes } from "../../../";
import { MultiMap } from "./MultiMap"; import { MultiMap } from "./MultiMap";
import { MultiSet } from "./MultiSet"; import { MultiSet } from "./MultiSet";
import type { TransformerSubTraverserExecutingPromises } from "./transformerSubTraverserTypes"; 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 */ /* eslint-disable @typescript-eslint/no-explicit-any */
export class MultiMap<Key1, Key2, Value> { export class MultiMap<Key1, Key2, Value> {
private map: Map<Key1, Map<Key2, Value>> = new Map(); 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> { export class MultiSet<Key1, Key2> {
private map: Map<Key1, Set<Key2>> = new Map(); private map: Map<Key1, Set<Key2>> = new Map();
// eslint-disable-next-line @typescript-eslint/no-inferrable-types // eslint-disable-next-line @typescript-eslint/no-inferrable-types

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

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

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

@ -8,15 +8,16 @@ import type {
PrimitiveType, PrimitiveType,
PrimitiveVisitorDefinition, PrimitiveVisitorDefinition,
PrimitiveVisitorInputDefinition, PrimitiveVisitorInputDefinition,
TraverserDefinition, TraverserDefinitions,
TraverserTypes, TraverserTypes,
UnionType, UnionType,
UnionVisitorDefinition, UnionVisitorDefinition,
UnionVisitorInputDefinition, UnionVisitorInputDefinition,
Visitors, Visitors,
VisitorsInput, VisitorsInput,
} from "."; } from "../";
import { MultiSet } from "./transformerSubTraversers/util/MultiSet"; import { InstanceGraph } from "../instanceGraph/instanceGraph";
import { MultiSet } from "../transformer/transformerSubTraversers/util/MultiSet";
import { visitorParentSubTraverser } from "./visitorSubTraversers/VisitorParentSubTraverser"; import { visitorParentSubTraverser } from "./visitorSubTraversers/VisitorParentSubTraverser";
// TODO: Lots of "any" in this file. I'm just done with fancy typescript, // 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>, Types extends TraverserTypes<any>,
Context = undefined, Context = undefined,
> { > {
private traverserDefinition: TraverserDefinition<Types>; private traverserDefinition: TraverserDefinitions<Types>;
private visitors: Visitors<Types, Context>; private visitors: Visitors<Types, Context>;
constructor( constructor(
traverserDefinition: TraverserDefinition<Types>, traverserDefinition: TraverserDefinitions<Types>,
visitors: VisitorsInput<Types, Context>, visitors: VisitorsInput<Types, Context>,
) { ) {
this.traverserDefinition = traverserDefinition; this.traverserDefinition = traverserDefinition;
@ -39,15 +40,17 @@ export class Visitor<
} }
private applyDefaultInterfaceVisitorProperties< private applyDefaultInterfaceVisitorProperties<
Type extends InterfaceType<keyof Types>, TypeName extends keyof Types,
Type extends InterfaceType<keyof Types> & Types[TypeName],
>( >(
typeName: keyof Types, typeName: keyof Types,
typePropertiesInput: InterfaceVisitorInputDefinition< typePropertiesInput: InterfaceVisitorInputDefinition<
Types, Types,
TypeName,
Type, Type,
Context Context
>["properties"], >["properties"],
): InterfaceVisitorDefinition<Types, Type, Context>["properties"] { ): InterfaceVisitorDefinition<Types, TypeName, Type, Context>["properties"] {
return Object.keys( return Object.keys(
(this.traverserDefinition[typeName] as InterfaceTraverserDefinition<Type>) (this.traverserDefinition[typeName] as InterfaceTraverserDefinition<Type>)
.properties, .properties,
@ -60,13 +63,21 @@ export class Visitor<
}; };
} }
return agg; 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, typeName: keyof Types,
typeInput?: InterfaceVisitorInputDefinition<Types, Type, Context>, typeInput?: InterfaceVisitorInputDefinition<Types, TypeName, Type, Context>,
): InterfaceVisitorDefinition<Types, Type, Context> { ): InterfaceVisitorDefinition<Types, TypeName, Type, Context> {
if (!typeInput) { if (!typeInput) {
return { return {
visitor: async () => { visitor: async () => {
@ -84,9 +95,12 @@ export class Visitor<
}; };
} }
private applyDefaultUnionVisitor<Type extends UnionType<keyof Types>>( private applyDefaultUnionVisitor<
typeInput?: UnionVisitorInputDefinition<Types, Type, Context>, TypeName extends keyof Types,
): UnionVisitorDefinition<Types, Type, Context> { Type extends UnionType<keyof Types> & Types[TypeName],
>(
typeInput?: UnionVisitorInputDefinition<Types, TypeName, Type, Context>,
): UnionVisitorDefinition<Types, TypeName, Type, Context> {
if (!typeInput) { if (!typeInput) {
return async () => { return async () => {
return; return;
@ -95,9 +109,12 @@ export class Visitor<
return typeInput; return typeInput;
} }
private applyDefaultPrimitiveVisitor<Type extends PrimitiveType>( private applyDefaultPrimitiveVisitor<
typeInput?: PrimitiveVisitorInputDefinition<Type, Context>, TypeName extends keyof Types,
): PrimitiveVisitorDefinition<Type, Context> { Type extends PrimitiveType & Types[TypeName],
>(
typeInput?: PrimitiveVisitorInputDefinition<Types, TypeName, Type, Context>,
): PrimitiveVisitorDefinition<Types, TypeName, Type, Context> {
if (!typeInput) { if (!typeInput) {
return async () => { return async () => {
return; return;
@ -134,10 +151,13 @@ export class Visitor<
itemTypeName: TypeName, itemTypeName: TypeName,
context: Context, context: Context,
): Promise<void> { ): Promise<void> {
const instanceGraph = new InstanceGraph(this.traverserDefinition);
instanceGraph.getNodeFor(item, itemTypeName);
const toReturn = await visitorParentSubTraverser(item, itemTypeName, { const toReturn = await visitorParentSubTraverser(item, itemTypeName, {
traverserDefinition: this.traverserDefinition, traverserDefinition: this.traverserDefinition,
visitors: this.visitors, visitors: this.visitors,
visitedObjects: new MultiSet(), visitedObjects: new MultiSet(),
instanceGraph,
context, context,
}); });
return toReturn; 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 */ /* eslint-disable @typescript-eslint/no-explicit-any */
import type { InterfaceVisitorDefinition, TraverserTypes } from ".."; import type { InterfaceVisitorDefinition, TraverserTypes } from "../../";
import type { InterfaceTraverserDefinition } from "../TraverserDefinition"; import type { InterfaceInstanceNode } from "../../instanceGraph/nodes/InterfaceInstanceNode";
import type { InterfaceType } from "../TraverserTypes"; import type { InterfaceTraverserDefinition } from "../../traverser/TraverserDefinition";
import type { InterfaceType } from "../../traverser/TraverserTypes";
import type { VisitorSubTraverserGlobals } from "./util/visitorSubTraverserTypes"; import type { VisitorSubTraverserGlobals } from "./util/visitorSubTraverserTypes";
import { visitorParentSubTraverser } from "./VisitorParentSubTraverser"; import { visitorParentSubTraverser } from "./VisitorParentSubTraverser";
export async function visitorInterfaceSubTraverser< export async function visitorInterfaceSubTraverser<
Types extends TraverserTypes<any>, Types extends TraverserTypes<any>,
TypeName extends keyof Types, TypeName extends keyof Types,
Type extends InterfaceType<keyof Types>, Type extends InterfaceType<keyof Types> & Types[TypeName],
Context, Context,
>( >(
item: Type["type"], item: Type["type"],
@ -22,16 +23,27 @@ export async function visitorInterfaceSubTraverser<
] as InterfaceTraverserDefinition<Type>; ] as InterfaceTraverserDefinition<Type>;
const visitor = visitors[ const visitor = visitors[
itemTypeName itemTypeName
] as unknown as InterfaceVisitorDefinition<Types, Type, Context>; ] as unknown as InterfaceVisitorDefinition<Types, TypeName, Type, Context>;
await Promise.all([ 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( Promise.all(
Object.entries(definition.properties).map(async ([propertyName]) => { Object.entries(definition.properties).map(async ([propertyName]) => {
const originalObject = item[propertyName]; const originalObject = item[propertyName];
const originalPropertyDefinition = definition.properties[propertyName]; const originalPropertyDefinition = definition.properties[propertyName];
const propertyVisitorPromise = visitor.properties[propertyName]( const propertyVisitorPromise = visitor.properties[propertyName](
originalObject, originalObject,
globals.instanceGraph.getNodeFor(
item,
itemTypeName,
) as unknown as InterfaceInstanceNode<Types, TypeName, Type>,
globals.context, globals.context,
); );
let propertyTraverserPromise: Promise<void | void[]>; let propertyTraverserPromise: Promise<void | void[]>;

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

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

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

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

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

Loading…
Cancel
Save