make ids for compact typings mandatory

main
Laurin Weger 3 weeks ago
parent 87c64c7f75
commit 42a6476fb8
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 3
      packages/ldo/src/index.ts
  2. 17
      packages/ldo/src/util.ts
  3. 47
      packages/schema-converter-shex/src/typing/ShexJTypingTransformerCompact.ts
  4. 2
      packages/schema-converter-shex/test/testData/circular.ts
  5. 2
      packages/schema-converter-shex/test/testData/extendsSimple.ts
  6. 2
      packages/schema-converter-shex/test/testData/mixedPluralUnionError.ts
  7. 4
      packages/schema-converter-shex/test/testData/pluralAnonymous.ts
  8. 2
      packages/schema-converter-shex/test/testData/pluralObjects.ts
  9. 2
      packages/schema-converter-shex/test/testData/pluralUnionObjects.ts
  10. 2
      packages/schema-converter-shex/test/testData/propertyCollision.ts
  11. 2
      packages/schema-converter-shex/test/testData/reusedPredicates.ts
  12. 2
      packages/schema-converter-shex/test/testData/simple.ts
  13. 3
      packages/schema-converter-shex/test/testData/singleAnonymous.ts
  14. 6
      packages/schema-converter-shex/test/typing.compact.test.ts

@ -5,8 +5,7 @@ export * from "./LdoDataset.js";
export * from "./LdoTransactionDataset.js";
export * from "./LdoBuilder.js";
export * from "./createLdoDataset.js";
import type { LdoBase as LdoBaseImport } from "./util.js";
export type LdoBase = LdoBaseImport;
export type { LdoBase, LdoCompactBase } from "./util.js";
export * from "./types.js";
export type { LdSet, LdoJsonldContext } from "@ldo/jsonld-dataset-proxy";
export { set } from "@ldo/jsonld-dataset-proxy";

@ -19,13 +19,20 @@ import type {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type LdoBase = Record<string, any>;
/**
* @category Types
* `LdoCompactBase` is an interface defining that a Linked Data Object
* in compact format is a JavaScript Object Literal and has an id.
*/
export type LdoCompactBase = LdoBase & { id: string };
/**
* Converts a node/string into just a node
* @param input A Node or string
* @returns A node
*/
export function normalizeNodeName<NodeType extends AnyNode>(
input: NodeType | string,
input: NodeType | string
): NodeType {
return (typeof input === "string" ? namedNode(input) : input) as NodeType;
}
@ -36,13 +43,13 @@ export function normalizeNodeName<NodeType extends AnyNode>(
* @returns An array of nodes
*/
export function normalizeNodeNames<NodeType extends AnyNode>(
inputs: (NodeType | string)[],
inputs: (NodeType | string)[]
): NodeType[] {
return inputs.map((input) => normalizeNodeName<NodeType>(input));
}
export function canDatasetStartTransaction(
dataset: Dataset,
dataset: Dataset
): dataset is ISubscribableDataset<Quad> {
return (
typeof (dataset as ISubscribableDataset).startTransaction === "function"
@ -50,13 +57,13 @@ export function canDatasetStartTransaction(
}
export function isTransactionalDataset(
dataset: Dataset,
dataset: Dataset
): dataset is ITransactionDataset<Quad> {
return typeof (dataset as ITransactionDataset).commit === "function";
}
export function getTransactionalDatasetFromLdo(
ldo: LdoBase,
ldo: LdoBase
): [ITransactionDataset<Quad>, SubjectProxy | SetProxy] {
const proxy = getProxyFromObject(ldo);
const dataset = proxy[_getUnderlyingDataset];

@ -248,7 +248,8 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.Optional
// Root interfaces should have mandatory id
dom.DeclarationFlags.None
)
);
}
@ -308,7 +309,6 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
idSeen.add(idx);
// normalize id type to IRI
m.type = dom.create.namedTypeReference("IRI");
m.flags = dom.DeclarationFlags.Optional;
return true;
}
return false;
@ -451,7 +451,7 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.Optional
dom.DeclarationFlags.None
)
);
}
@ -470,7 +470,7 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.Optional
dom.DeclarationFlags.None
)
);
}
@ -493,6 +493,45 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
} as dom.Type;
}
} else {
// Singular
// If anonymous object or union of object-like types, ensure id: IRI is present (mandatory)
if (objLike) {
if ((valueType as dom.ObjectType).kind === "object") {
const members = (valueType as unknown as {
members?: dom.PropertyDeclaration[];
}).members;
const hasId = (members || []).some((m) => m.name === "id");
if (!hasId && members) {
members.unshift(
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.None,
)
);
}
}
} else if (isUnion && unionAllObjLike) {
(valueType as dom.UnionType).members = (
valueType as dom.UnionType
).members.map((m) => {
if ((m as dom.ObjectType).kind === "object") {
const mems = (m as unknown as { members?: dom.PropertyDeclaration[] })
.members;
const hasId = (mems || []).some((mm) => mm.name === "id");
if (!hasId && mems) {
mems.unshift(
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.None,
)
);
}
}
return m;
});
}
// Singular: always the interface/object type itself (never Id union)
if (
(valueType as dom.InterfaceDeclaration).kind === "interface" &&

@ -72,5 +72,5 @@ export const circular: TestData = {
},
successfulTypings:
'import { LdSet, LdoJsonldContext } from "@ldo/ldo"\n\nexport interface Parent {\n "@id"?: string;\n "@context"?: LdoJsonldContext;\n type?: LdSet<{\n "@id": "Parent";\n }>;\n hasChild: Child;\n}\n\nexport interface Child {\n "@id"?: string;\n "@context"?: LdoJsonldContext;\n type?: LdSet<{\n "@id": "Child";\n }>;\n hasParent: Parent;\n}\n\n',
successfulCompactTypings: `export type IRI = string;\n\nexport interface Parent {\n id?: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type?: "http://example.com/Parent";\n /**\n * Original IRI: http://example.com/hasChild\n */\n hasChild: Child;\n}\n\nexport interface Child {\n id?: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type?: "http://example.com/Child";\n /**\n * Original IRI: http://example.com/hasParent\n */\n hasParent: Parent;\n}\n\n`,
successfulCompactTypings: `export type IRI = string;\n\nexport interface Parent {\n id: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type?: "http://example.com/Parent";\n /**\n * Original IRI: http://example.com/hasChild\n */\n hasChild: Child;\n}\n\nexport interface Child {\n id: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type?: "http://example.com/Child";\n /**\n * Original IRI: http://example.com/hasParent\n */\n hasParent: Parent;\n}\n\n`,
};

@ -80,5 +80,5 @@ export const extendsSimple: TestData = {
},
successfulTypings:
'import { LdSet, LdoJsonldContext } from "@ldo/ldo"\n\nexport interface Entity {\n "@id"?: string;\n "@context"?: LdoJsonldContext;\n type: LdSet<{\n "@id": "Entity";\n }>;\n entityId: any;\n}\n\nexport interface Person {\n "@id"?: LdSet<string | string>;\n "@context"?: LdSet<LdoJsonldContext | LdoJsonldContext>;\n type: LdSet<{\n "@id": "Entity";\n } | {\n "@id": "Person";\n }>;\n entityId: any;\n name: any;\n}\n\nexport interface Employee {\n "@id"?: LdSet<string | string | string>;\n "@context"?: LdSet<LdoJsonldContext | LdoJsonldContext | LdoJsonldContext>;\n type: LdSet<{\n "@id": "Entity";\n } | {\n "@id": "Person";\n } | {\n "@id": "Employee";\n }>;\n entityId: any;\n name: any;\n employeeNumber: any;\n}\n\n',
successfulCompactTypings: `export type IRI = string;\n\nexport interface Entity {\n id?: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://example.com/Entity";\n /**\n * Original IRI: https://example.com/entityId\n */\n entityId: any;\n}\n\nexport interface Person {\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type | Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://example.com/Entity" | "https://example.com/Person";\n /**\n * Original IRI: https://example.com/entityId\n */\n entityId: any;\n id?: IRI;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/name\n */\n name: any;\n}\n\nexport interface Employee {\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type | Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type | Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://example.com/Entity" | "https://example.com/Person" | "https://example.com/Employee";\n /**\n * Original IRI: https://example.com/entityId\n */\n entityId: any;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/name\n */\n name: any;\n id?: IRI;\n /**\n * Original IRI: https://example.com/employeeNumber\n */\n employeeNumber: any;\n}\n\n`,
successfulCompactTypings: `export type IRI = string;\n\nexport interface Entity {\n id: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://example.com/Entity";\n /**\n * Original IRI: https://example.com/entityId\n */\n entityId: any;\n}\n\nexport interface Person {\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type | Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://example.com/Entity" | "https://example.com/Person";\n /**\n * Original IRI: https://example.com/entityId\n */\n entityId: any;\n id: IRI;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/name\n */\n name: any;\n}\n\nexport interface Employee {\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type | Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type | Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://example.com/Entity" | "https://example.com/Person" | "https://example.com/Employee";\n /**\n * Original IRI: https://example.com/entityId\n */\n entityId: any;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/name\n */\n name: any;\n id: IRI;\n /**\n * Original IRI: https://example.com/employeeNumber\n */\n employeeNumber: any;\n}\n\n`,
};

@ -13,5 +13,5 @@ export const mixedPluralUnionError: TestData = {
baseNode: "http://ex/foo2",
successfulContext: {},
successfulTypings: "",
successfulCompactTypings: `export type IRI = string;\n\nexport interface Foo {\n id?: IRI;\n /**\n * Original IRI: http://ex/mixed\n */\n mixed?: Record<IRI, Bar | Baz>;\n}\n\nexport interface Bar {\n id?: IRI;\n /**\n * Original IRI: http://ex/label\n */\n label: any;\n}\n\nexport interface Baz {\n id?: IRI;\n /**\n * Original IRI: http://ex/other\n */\n other: any;\n}\n\n`,
successfulCompactTypings: `export type IRI = string;\n\nexport interface Foo {\n id: IRI;\n /**\n * Original IRI: http://ex/mixed\n */\n mixed?: Record<IRI, Bar | Baz>;\n}\n\nexport interface Bar {\n id: IRI;\n /**\n * Original IRI: http://ex/label\n */\n label: any;\n}\n\nexport interface Baz {\n id: IRI;\n /**\n * Original IRI: http://ex/other\n */\n other: any;\n}\n\n`,
};

@ -13,12 +13,12 @@ export const pluralAnonymous: TestData = {
successfulCompactTypings: `export type IRI = string;
export interface ConfigHolder {
id?: IRI;
id: IRI;
/**
* Original IRI: http://ex.org/configs
*/
configs?: Record<IRI, {
id?: IRI;
id: IRI;
/**
* Original IRI: http://ex.org/key
*/

@ -11,5 +11,5 @@ export const pluralObjects: TestData = {
baseNode: "http://ex/foo1",
successfulContext: {},
successfulTypings: "", // not used in this test context
successfulCompactTypings: `export type IRI = string;\n\nexport interface Foo {\n id?: IRI;\n /**\n * Original IRI: http://ex/bars\n */\n bars?: Record<IRI, Bar>;\n}\n\nexport interface Bar {\n id?: IRI;\n /**\n * Original IRI: http://ex/name\n */\n name: any;\n}\n\n`,
successfulCompactTypings: `export type IRI = string;\n\nexport interface Foo {\n id: IRI;\n /**\n * Original IRI: http://ex/bars\n */\n bars?: Record<IRI, Bar>;\n}\n\nexport interface Bar {\n id: IRI;\n /**\n * Original IRI: http://ex/name\n */\n name: any;\n}\n\n`,
};

@ -12,5 +12,5 @@ export const pluralUnionObjects: TestData = {
baseNode: "http://ex/a1",
successfulContext: {} as any,
successfulTypings: "",
successfulCompactTypings: `export type IRI = string;\n\nexport interface A {\n id?: IRI;\n /**\n * Original IRI: http://ex/items\n */\n items?: Record<IRI, Foo | Bar>;\n}\n\nexport interface Foo {\n id?: IRI;\n /**\n * Original IRI: http://ex/f\n */\n f: any;\n}\n\nexport interface Bar {\n id?: IRI;\n /**\n * Original IRI: http://ex/b\n */\n b: any;\n}\n\n`,
successfulCompactTypings: `export type IRI = string;\n\nexport interface A {\n id: IRI;\n /**\n * Original IRI: http://ex/items\n */\n items?: Record<IRI, Foo | Bar>;\n}\n\nexport interface Foo {\n id: IRI;\n /**\n * Original IRI: http://ex/f\n */\n f: any;\n}\n\nexport interface Bar {\n id: IRI;\n /**\n * Original IRI: http://ex/b\n */\n b: any;\n}\n\n`,
};

@ -17,7 +17,7 @@ export const propertyCollision: TestData = {
successfulCompactTypings: `export type IRI = string;
export interface C {
id?: IRI;
id: IRI;
/**
* Original IRI: http://ex/label
*/

@ -114,5 +114,5 @@ export const reusedPredicates: TestData = {
},
successfulTypings:
'import { LdSet, LdoJsonldContext } from "@ldo/ldo"\n\nexport interface Document {\n "@id"?: string;\n "@context"?: LdoJsonldContext;\n type: LdSet<{\n "@id": "Document";\n }>;\n vocabulary?: LdSet<Vocabulary>;\n law: Law;\n}\n\nexport interface Law {\n "@id"?: string;\n "@context"?: LdoJsonldContext;\n type: LdSet<{\n "@id": "Law";\n }>;\n name?: LdSet<string>;\n path: {\n "@id": string;\n };\n}\n\nexport interface Vocabulary {\n "@id"?: string;\n "@context"?: LdoJsonldContext;\n type: LdSet<{\n "@id": "Vocabulary";\n }>;\n name: string;\n path?: LdSet<{\n "@id": string;\n }>;\n}\n\n',
successfulCompactTypings: `export type IRI = string;\n\nexport interface Document {\n id?: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Document";\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#vocabulary\n */\n vocabulary?: Record<IRI, Vocabulary>;\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#law\n */\n law: Law;\n}\n\nexport interface Law {\n id?: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Law";\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name\n */\n name?: Set<string>;\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path\n */\n path: IRI;\n}\n\nexport interface Vocabulary {\n id?: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Vocabulary";\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name\n */\n name: string;\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path\n */\n path?: Set<IRI>;\n}\n\n`,
successfulCompactTypings: `export type IRI = string;\n\nexport interface Document {\n id: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Document";\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#vocabulary\n */\n vocabulary?: Record<IRI, Vocabulary>;\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#law\n */\n law: Law;\n}\n\nexport interface Law {\n id: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Law";\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name\n */\n name?: Set<string>;\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path\n */\n path: IRI;\n}\n\nexport interface Vocabulary {\n id: IRI;\n /**\n * Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type\n */\n type: "https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#Vocabulary";\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#name\n */\n name: string;\n /**\n * Original IRI: https://www.forsakringskassan.se/vocabs/fk-sem-poc.ttl#path\n */\n path?: Set<IRI>;\n}\n\n`,
};

@ -57,5 +57,5 @@ export const simple: TestData = {
},
successfulTypings:
'import { LdSet, LdoJsonldContext } from "@ldo/ldo"\n\nexport interface Employee {\n "@id"?: string;\n "@context"?: LdoJsonldContext;\n givenName: LdSet<string>;\n familyName: string;\n phone?: LdSet<{\n "@id": string;\n }>;\n mbox: {\n "@id": string;\n };\n someDouble: number;\n}\n\n',
successfulCompactTypings: `export type IRI = string;\n\nexport interface Employee {\n id?: IRI;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/givenName\n */\n givenName: Set<string>;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/familyName\n */\n familyName: string;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/phone\n */\n phone?: Set<IRI>;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/mbox\n */\n mbox: IRI;\n /**\n * Original IRI: https://ns.example/someDouble\n */\n someDouble: number;\n}\n\n`,
successfulCompactTypings: `export type IRI = string;\n\nexport interface Employee {\n id: IRI;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/givenName\n */\n givenName: Set<string>;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/familyName\n */\n familyName: string;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/phone\n */\n phone?: Set<IRI>;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/mbox\n */\n mbox: IRI;\n /**\n * Original IRI: https://ns.example/someDouble\n */\n someDouble: number;\n}\n\n`,
};

@ -13,11 +13,12 @@ export const singleAnonymous: TestData = {
successfulCompactTypings: `export type IRI = string;
export interface ConfigHolder {
id?: IRI;
id: IRI;
/**
* Original IRI: http://ex/config
*/
config: {
id: IRI;
/**
* Original IRI: http://ex/key
*/

@ -15,7 +15,11 @@ describe("typing-compact", () => {
.parse(shexc);
const [compact] = await shexjToTyping(schema, { format: "compact" });
const normalize = (s: string) =>
s.replace(/\r\n/g, "\n").replace(/\n+$/s, "\n");
s
.replace(/\r\n/g, "\n")
.replace(/\n+$/s, "\n")
// Ignore leading indentation differences
.replace(/^ +/gm, "");
expect(normalize(compact.typingsString)).toBe(
normalize(successfulCompactTypings)
);

Loading…
Cancel
Save