build script now prints generates compact typings by default and no jsonld context.

TODO: The separation of code between ldo and compact type generation is bad. There could be more tests for compact, jsdoc for generated typings could be better.
Better have a review over all of this again.
main
Laurin Weger 1 week ago
parent c461beb5a5
commit f9e433cfd4
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 446
      package-lock.json
  2. 46
      packages/cli/src/build.ts
  3. 11
      packages/cli/src/index.ts
  4. 14
      packages/cli/src/templates/shapeTypes.compact.ejs
  5. 34
      packages/cli/src/templates/shapeTypes.ejs
  6. 28
      packages/cli/src/templates/typings.ejs
  7. 6
      packages/ldo/src/LdoBuilder.ts
  8. 12
      packages/ldo/src/LdoDataset.ts
  9. 7
      packages/ldo/src/LdoTransactionDataset.ts
  10. 14
      packages/ldo/src/ShapeType.ts
  11. 4
      packages/schema-converter-shex/src/context/JsonLdContextBuilder.ts
  12. 601
      packages/schema-converter-shex/src/typing/ShexJTypingTransformerCompact.ts
  13. 61
      packages/schema-converter-shex/src/typing/shexjToTyping.ts
  14. 52
      packages/schema-converter-shex/src/typing/shexjToTypingCompact.ts
  15. 58
      packages/schema-converter-shex/src/typing/shexjToTypingLdo.ts
  16. 4
      packages/schema-converter-shex/test/context.test.ts
  17. 1
      packages/schema-converter-shex/test/testData/circular.ts
  18. 1
      packages/schema-converter-shex/test/testData/extendsSimple.ts
  19. 17
      packages/schema-converter-shex/test/testData/mixedPluralUnionError.ts
  20. 15
      packages/schema-converter-shex/test/testData/pluralAnonymous.ts
  21. 15
      packages/schema-converter-shex/test/testData/pluralObjects.ts
  22. 16
      packages/schema-converter-shex/test/testData/pluralUnionObjects.ts
  23. 18
      packages/schema-converter-shex/test/testData/propertyCollision.ts
  24. 1
      packages/schema-converter-shex/test/testData/reusedPredicates.ts
  25. 1
      packages/schema-converter-shex/test/testData/simple.ts
  26. 11
      packages/schema-converter-shex/test/testData/testData.ts
  27. 24
      packages/schema-converter-shex/test/typing.compact.test.ts
  28. 10
      packages/schema-converter-shex/test/typing.test.ts
  29. 5
      packages/subscribable-dataset/src/TransactionDatasetFactory.ts

446
package-lock.json generated

@ -16853,294 +16853,6 @@
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
@ -17157,24 +16869,6 @@
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
@ -17191,24 +16885,6 @@
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
@ -17226,78 +16902,6 @@
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
@ -23459,15 +23063,6 @@
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
"license": "MIT"
},
"node_modules/@types/whatwg-mimetype": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
"integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@ -29671,35 +29266,6 @@
"uglify-js": "^3.1.4"
}
},
"node_modules/happy-dom": {
"version": "18.0.1",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.1.tgz",
"integrity": "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@types/node": "^20.0.0",
"@types/whatwg-mimetype": "^3.0.2",
"whatwg-mimetype": "^3.0.0"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/happy-dom/node_modules/whatwg-mimetype": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/hard-rejection": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
@ -45813,18 +45379,6 @@
"defaults": "^1.0.3"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/web-streams-ponyfill": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/web-streams-ponyfill/-/web-streams-ponyfill-1.4.2.tgz",

@ -17,6 +17,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
interface BuildOptions {
input: string;
output: string;
format?: "ldo" | "compact";
}
export async function build(options: BuildOptions) {
@ -45,26 +46,33 @@ export async function build(options: BuildOptions) {
return;
}
// Convert the content to types
const [typings, context] = await schemaConverterShex(schema);
const format = options.format || "ldo";
const [typings, context] = await schemaConverterShex(schema, { format });
const templates: string[] = ["schema", "typings", "shapeTypes"];
if (format === "ldo") {
templates.unshift("context");
}
await Promise.all(
["context", "schema", "shapeTypes", "typings"].map(
async (templateName) => {
const finalContent = await renderFile(
path.join(__dirname, "./templates", `${templateName}.ejs`),
{
typings: typings.typings,
fileName,
schema: JSON.stringify(schema, null, 2),
context: JSON.stringify(context, null, 2),
},
);
// Save conversion to document
await fs.promises.writeFile(
path.join(options.output, `${fileName}.${templateName}.ts`),
await prettier.format(finalContent, { parser: "typescript" }),
);
},
),
templates.map(async (templateName) => {
const templateFile =
templateName === "shapeTypes" && format === "compact"
? "shapeTypes.compact"
: templateName;
const finalContent = await renderFile(
path.join(__dirname, "./templates", `${templateFile}.ejs`),
{
typings: typings.typings,
fileName,
schema: JSON.stringify(schema, null, 2),
context: JSON.stringify(context, null, 2),
format,
}
);
await fs.promises.writeFile(
path.join(options.output, `${fileName}.${templateName}.ts`),
await prettier.format(finalContent, { parser: "typescript" })
);
})
);
});

@ -16,6 +16,11 @@ program
.description("Build contents of a shex folder into Shape Types")
.option("-i, --input <inputPath>", "Provide the input path", "./.shapes")
.option("-o, --output <outputPath>", "Provide the output path", "./.ldo")
.option(
"-f, --format <format>",
'Typings format: "compact" (default) or "ldo"',
"compact"
)
.action(build);
program
@ -36,17 +41,17 @@ program
.requiredOption(
"-p, --project <projectPath>",
"Provide the path to the root project",
"./",
"./"
)
.requiredOption(
"-s, --shapes <shapesPath>",
"Provide the path to the shapes folder",
"./.shapes",
"./.shapes"
)
.requiredOption(
"-s, --ldo <ldoPath>",
"Provide the path to the ldo folder",
"./.ldo",
"./.ldo"
)
.action(generateReadme);

@ -0,0 +1,14 @@
import { CompactShapeType } from "@ldo/ldo";
import { <%- fileName %>Schema } from "./<%- fileName %>.schema";
import {
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%>
<%- typing.dts.name %>,
<% } }); -%>} from "./<%- fileName %>.typings";
// Compact ShapeTypes for <%- fileName %>
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%>
export const <%- typing.dts.name %>ShapeType: CompactShapeType<<%- typing.dts.name %>> = {
schema: <%- fileName %>Schema,
shape: "<%- typing.dts.shapeId %>",
};
<% } }); -%>

@ -1,24 +1,16 @@
import { ShapeType } from "@ldo/ldo";
import { <%- fileName %>Schema } from "./<%- fileName %>.schema";
import { <%- fileName %>Context } from "./<%- fileName %>.context";
import {
<% typings.forEach((typing) => { -%>
<%- typing.dts.name %>,
<% }); -%>} from "./<%- fileName %>.typings";
import { <%- fileName %>Context } from "./<%- fileName %>.context";
import {
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%>
<%- typing.dts.name %>,
<% } }); -%>} from "./<%- fileName %>.typings";
/**
* =============================================================================
* LDO ShapeTypes <%- fileName %>
* =============================================================================
*/
<% typings.forEach((typing) => { -%>
/**
* <%- typing.dts.name %> ShapeType
*/
export const <%- typing.dts.name %>ShapeType: ShapeType<<%- typing.dts.name %>> = {
schema: <%- fileName %>Schema,
shape: "<%- typing.dts.shapeId %>",
context: <%- fileName %>Context,
};
<% }); -%>
// LDO ShapeTypes for <%- fileName %>
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%>
export const <%- typing.dts.name %>ShapeType: ShapeType<<%- typing.dts.name %>> = {
schema: <%- fileName %>Schema,
shape: "<%- typing.dts.shapeId %>",
context: <%- fileName %>Context,
};
<% } }); -%>

@ -1,14 +1,18 @@
import { LdoJsonldContext, LdSet } from "@ldo/ldo";
<% if (format==='ldo' ) { -%>
import { LdoJsonldContext, LdSet } from "@ldo/ldo";
<% } else { -%>
export type IRI = string;
<% } -%>
/**
* =============================================================================
* Typescript Typings for <%- fileName %>
* =============================================================================
*/
/**
* =============================================================================
* Typescript Typings for <%- fileName %>
* =============================================================================
*/
<% typings.forEach((typing) => { -%>
/**
* <%- typing.dts.name %> Type
*/
export <%- typing.typingString -%>
<% }); -%>
<% typings.forEach((typing)=> { -%>
/**
* <%- typing.dts.name %> Type
*/
export <%- typing.typingString -%>
<% }); -%>

@ -4,7 +4,7 @@ import type {
JsonldDatasetProxyBuilder,
LdSet,
} from "@ldo/jsonld-dataset-proxy";
import type { ShapeType } from "./ShapeType.js";
import type { ShapeType, CompactShapeType, AnyShapeType } from "./ShapeType.js";
import type { LdoBase } from "./util.js";
import { normalizeNodeName, normalizeNodeNames } from "./util.js";
@ -32,7 +32,7 @@ export class LdoBuilder<Type extends LdoBase> {
* @internal
*/
protected jsonldDatasetProxyBuilder: JsonldDatasetProxyBuilder;
protected shapeType: ShapeType<Type>;
protected shapeType: ShapeType<Type> | CompactShapeType<Type>;
/**
* Initializes the LdoBuilder
@ -42,7 +42,7 @@ export class LdoBuilder<Type extends LdoBase> {
*/
constructor(
jsonldDatasetProxyBuilder: JsonldDatasetProxyBuilder,
shapeType: ShapeType<Type>,
shapeType: AnyShapeType<Type>,
) {
this.jsonldDatasetProxyBuilder = jsonldDatasetProxyBuilder;
this.shapeType = shapeType;

@ -2,7 +2,7 @@ import type { Quad } from "@rdfjs/types";
import jsonldDatasetProxy from "@ldo/jsonld-dataset-proxy";
import { SubscribableDataset } from "@ldo/subscribable-dataset";
import { LdoBuilder } from "./LdoBuilder.js";
import type { ShapeType } from "./ShapeType.js";
import type { AnyShapeType, ShapeType, CompactShapeType } from "./ShapeType.js";
import type { LdoBase } from "./index.js";
import { LdoTransactionDataset } from "./LdoTransactionDataset.js";
import type { ILdoDataset } from "./types.js";
@ -35,10 +35,14 @@ export class LdoDataset
* @returns A builder for the given type
*/
public usingType<Type extends LdoBase>(
shapeType: ShapeType<Type>,
shapeType: AnyShapeType<Type>,
): LdoBuilder<Type> {
const proxyBuilder = jsonldDatasetProxy(this, shapeType.context);
return new LdoBuilder(proxyBuilder, shapeType);
const context = (shapeType as ShapeType<Type>).context || {};
const proxyBuilder = jsonldDatasetProxy(this, context);
return new LdoBuilder(
proxyBuilder,
shapeType as ShapeType<Type> | CompactShapeType<Type>,
);
}
public startTransaction(): LdoTransactionDataset {

@ -2,7 +2,7 @@ import { TransactionDataset } from "@ldo/subscribable-dataset";
import type { Quad } from "@rdfjs/types";
import type { ILdoDataset } from "./types.js";
import { LdoBuilder } from "./LdoBuilder.js";
import type { ShapeType } from "./ShapeType.js";
import type { ShapeType, AnyShapeType } from "./ShapeType.js";
import type { LdoBase } from "./util.js";
import jsonldDatasetProxy from "@ldo/jsonld-dataset-proxy";
@ -11,9 +11,10 @@ export class LdoTransactionDataset
implements ILdoDataset
{
usingType<Type extends LdoBase>(
shapeType: ShapeType<Type>,
shapeType: AnyShapeType<Type>,
): LdoBuilder<Type> {
const proxyBuilder = jsonldDatasetProxy(this, shapeType.context);
const context = (shapeType as ShapeType<Type>).context || {};
const proxyBuilder = jsonldDatasetProxy(this, context);
return new LdoBuilder(proxyBuilder, shapeType);
}
}

@ -54,3 +54,17 @@ export type ShapeType<Type extends LdoBase> = {
*/
exampleData?: Type;
};
/**
* CompactShapeType is a reduced variant without a JSON-LD context, used for the
* compact typings format.
*/
export type CompactShapeType<Type extends LdoBase> = {
schema: Schema;
shape: string;
exampleData?: Type;
};
export type AnyShapeType<T extends LdoBase> =
| ShapeType<T>
| CompactShapeType<T>;

@ -9,14 +9,14 @@ import { hashValueSetValue } from "./util/hashValueSetValue.js";
export function iriToName(iri: string): string {
try {
const url = new URL(iri);
let name: string
let name: string;
if (url.hash) {
name = url.hash.slice(1);
} else {
const splitPathname = url.pathname.split("/");
name = splitPathname[splitPathname.length - 1];
}
return name.replace(/(?<!^)Shape$/, '')
return name.replace(/(?<!^)Shape$/, "");
} catch (err) {
return iri;
}

@ -0,0 +1,601 @@
import ShexJTraverser from "@ldo/traverser-shexj";
import type { Annotation } from "shexj";
import { nameFromObject } from "../context/JsonLdContextBuilder.js";
import type { ShapeInterfaceDeclaration } from "./ShapeInterfaceDeclaration.js";
import { getRdfTypesForTripleConstraint } from "../util/getRdfTypesForTripleConstraint.js";
import * as dom from "dts-dom";
// Collected enum alias names (e.g., AuthenticatedAgentId) to emit at end
export const additionalCompactEnumAliases = new Set<string>();
export interface CompactTransformerContext {
getNameFromIri: (iri: string, rdfType?: string) => string;
}
function commentFromAnnotations(
annotations?: Annotation[],
): string | undefined {
const commentAnnotationObject = annotations?.find(
(annotation) =>
annotation.predicate === "http://www.w3.org/2000/01/rdf-schema#comment",
)?.object;
if (typeof commentAnnotationObject === "string")
return commentAnnotationObject;
return commentAnnotationObject?.value;
}
// Helper: classify a dom.Type into categories we care about.
function isObjectLike(t: dom.Type): boolean {
return (
(t as dom.ObjectType).kind === "object" ||
(t as dom.InterfaceDeclaration).kind === "interface"
);
}
function isPrimitiveLike(t: dom.Type): boolean {
const kind = (t as any)?.kind;
if (kind === "name") return true; // named references and intrinsic tokens
if (kind === "union") {
return (t as dom.UnionType).members.every(isPrimitiveLike);
}
if (kind === "type-parameter") return true;
// Fallback: treat scalar intrinsic tokens as primitive
const intrinsicKinds = new Set(["string", "number", "boolean", "undefined"]);
return intrinsicKinds.has(kind || "");
}
// Property name collision resolution using predicate IRI mapping
const predicateIriByProp = new WeakMap<dom.PropertyDeclaration, string>();
/**
* resolveCollisions
* -----------------
* Purpose: ensure that properties derived from different predicate IRIs but
* sharing the same local ending (final segment after '/', '#' or ':') become
* uniquely named TypeScript properties.
*
* Strategy (simplified):
* 1. Group properties by their current (local) name.
* 2. For any group with more than one property, rename each to
* `${secondLast}_${local}` where `secondLast` is the segment immediately
* before the local segment in the predicate IRI.
* 3. If collisions still remain (i.e. two IRIs share both secondLast and local
* segments), fall back to using a sanitized form of the full IRI (without the
* protocol) as the property name.
*/
function resolveCollisions(props: dom.PropertyDeclaration[]): void {
const groups = new Map<string, dom.PropertyDeclaration[]>();
props.forEach((p) => {
const base = p.name.replace(/\d+$/, "");
if (!groups.has(base)) groups.set(base, []);
groups.get(base)!.push(p);
});
groups.forEach((list) => {
if (list.length < 2) return;
// First pass rename using second last segment
list.forEach((prop) => {
const iri = predicateIriByProp.get(prop) || prop.name;
const segs = iri.split(/[#:\/]/).filter(Boolean);
if (!segs.length) return;
const local = segs.at(-1)!;
const secondLast = segs.length > 1 ? segs.at(-2)! : undefined;
if (secondLast) {
prop.name = `${secondLast}_${local}`;
}
});
// Detect any remaining duplicates after first pass
const nameCounts = new Map<string, number>();
list.forEach((p) =>
nameCounts.set(p.name, (nameCounts.get(p.name) || 0) + 1),
);
list.forEach((p) => {
if (nameCounts.get(p.name)! > 1) {
p.name = predicateIriByProp.get(p) || p.name;
}
});
});
}
// Merge duplicate properties without introducing LdSet. If a property appears multiple
// times (e.g., via EXTENDS or grouped expressions) we:
// - union the types (flattening existing unions)
// - if one side is Set<T> and the other is plain U, produce Set<T|U>
// - if both are Set<A>, Set<B> -> Set<A|B>
// - preserve optional flag if any occurrence optional
function dedupeCompactProperties(
props: dom.PropertyDeclaration[],
): dom.PropertyDeclaration[] {
const byName: Record<string, dom.PropertyDeclaration> = {};
const isSetRef = (t: dom.Type): t is dom.NamedTypeReference =>
(t as any).kind === "name" && (t as any).name === "Set";
const getSetInner = (t: dom.Type): dom.Type =>
isSetRef(t) ? (t as any).typeArguments[0] : t;
const toSet = (inner: dom.Type): dom.Type =>
({ kind: "name", name: "Set", typeArguments: [inner] }) as any;
const makeUnion = (a: dom.Type, b: dom.Type): dom.Type => {
const collect = (t: dom.Type, acc: dom.Type[]) => {
if ((t as any).kind === "union") {
(t as any).members.forEach((m: dom.Type) => collect(m, acc));
} else acc.push(t);
};
const members: dom.Type[] = [];
collect(a, members);
collect(b, members);
// de-dup via string emission heuristic
const seen = new Set<string>();
const filtered: dom.Type[] = [];
members.forEach((m) => {
const key =
(m as any).name ||
(m as any).value ||
(m as any).kind + JSON.stringify(m);
if (!seen.has(key)) {
seen.add(key);
filtered.push(m);
}
});
if (filtered.length === 1) return filtered[0];
return dom.create.union(filtered);
};
props.forEach((p) => {
const existing = byName[p.name];
if (!existing) {
byName[p.name] = p;
return;
}
// If predicates differ, keep both (assign numeric suffix to new one)
const predExisting = predicateIriByProp.get(existing);
const predNew = predicateIriByProp.get(p);
if (predExisting && predNew && predExisting !== predNew) {
const base = p.name;
let counter = 2;
while (byName[`${base}${counter}`]) counter++;
const newName = `${base}${counter}`;
const clone = dom.create.property(newName, p.type, p.flags);
clone.jsDocComment = p.jsDocComment;
if (predNew) predicateIriByProp.set(clone, predNew);
byName[newName] = clone;
return;
}
const existingSet = isSetRef(existing.type);
const newSet = isSetRef(p.type);
let mergedType: dom.Type;
if (existingSet && newSet) {
mergedType = toSet(
makeUnion(getSetInner(existing.type), getSetInner(p.type)),
);
} else if (existingSet && !newSet) {
mergedType = toSet(makeUnion(getSetInner(existing.type), p.type));
} else if (!existingSet && newSet) {
mergedType = toSet(makeUnion(existing.type, getSetInner(p.type)));
} else {
mergedType = makeUnion(existing.type, p.type);
}
const optional =
existing.flags === dom.DeclarationFlags.Optional ||
p.flags === dom.DeclarationFlags.Optional
? dom.DeclarationFlags.Optional
: dom.DeclarationFlags.None;
const merged = dom.create.property(p.name, mergedType, optional);
merged.jsDocComment =
existing.jsDocComment && p.jsDocComment
? `${existing.jsDocComment} | ${p.jsDocComment}`
: existing.jsDocComment || p.jsDocComment;
// Preserve predicate mapping
const pred = predicateIriByProp.get(existing) || predicateIriByProp.get(p);
if (pred) predicateIriByProp.set(merged, pred);
byName[p.name] = merged;
});
return Object.values(byName);
}
export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
{
Schema: { return: dom.TopLevelDeclaration[] };
ShapeDecl: { return: dom.InterfaceDeclaration };
Shape: { return: dom.InterfaceDeclaration };
EachOf: { return: dom.ObjectType | dom.InterfaceDeclaration };
TripleConstraint: { return: dom.PropertyDeclaration };
NodeConstraint: { return: dom.Type };
ShapeOr: { return: dom.UnionType };
ShapeAnd: { return: dom.IntersectionType };
ShapeNot: { return: never };
ShapeExternal: { return: never };
},
CompactTransformerContext
>({
// Transformer from Schema to interfaces
Schema: {
transformer: async (_schema, getTransformedChildren) => {
const transformedChildren = await getTransformedChildren();
const interfaces: dom.TopLevelDeclaration[] = [];
transformedChildren.shapes?.forEach((shape) => {
if (
typeof shape !== "string" &&
(shape as dom.InterfaceDeclaration).kind === "interface"
) {
interfaces.push(shape as dom.InterfaceDeclaration);
}
});
return interfaces;
},
},
// Transformer from ShapeDecl to interface
ShapeDecl: {
transformer: async (shapeDecl, getTransformedChildren) => {
const shapeName = nameFromObject(shapeDecl) || "Shape";
const { shapeExpr } = await getTransformedChildren();
if ((shapeExpr as dom.InterfaceDeclaration).kind === "interface") {
const shapeInterface = shapeExpr as ShapeInterfaceDeclaration;
shapeInterface.name = shapeName;
// Preserve shape id for downstream shapeTypes generation
// (mirrors standard transformer behavior)
shapeInterface.shapeId = shapeDecl.id;
if (
!shapeInterface.members.find(
(m) => m.kind === "property" && m.name === "id",
)
) {
shapeInterface.members.unshift(
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.Optional,
),
);
}
return shapeInterface;
}
throw new Error(
"Unsupported direct shape expression on ShapeDecl for compact format.",
);
},
},
// Transformer from Shape to interface
Shape: {
transformer: async (_shape, getTransformedChildren, setReturnPointer) => {
const newInterface: ShapeInterfaceDeclaration = dom.create.interface("");
setReturnPointer(newInterface);
const transformedChildren = await getTransformedChildren();
if (
typeof transformedChildren.expression !== "string" &&
transformedChildren.expression &&
((transformedChildren.expression as dom.ObjectType).kind === "object" ||
(transformedChildren.expression as dom.InterfaceDeclaration).kind ===
"interface")
) {
newInterface.members.push(
...(transformedChildren.expression as dom.ObjectType).members,
);
} else if (
(transformedChildren.expression as dom.PropertyDeclaration)?.kind ===
"property"
) {
newInterface.members.push(
transformedChildren.expression as dom.PropertyDeclaration,
);
}
if (transformedChildren.extends) {
transformedChildren.extends.forEach((ext) => {
const extInt = ext as dom.InterfaceDeclaration;
if (extInt.kind === "interface") {
const merged = [
...extInt.members.filter(
(m) => !(m.kind === "property" && m.name === "id"),
),
...newInterface.members,
].filter(
(m): m is dom.PropertyDeclaration => m.kind === "property",
);
newInterface.members = dedupeCompactProperties(merged);
}
});
}
// Final pass: ensure only a single id property
const idSeen = new Set<number>();
newInterface.members = newInterface.members.filter((m, idx) => {
if (m.kind !== "property" || m.name !== "id") return true;
if (idSeen.size === 0) {
idSeen.add(idx);
// normalize id type to IRI
m.type = dom.create.namedTypeReference("IRI");
m.flags = dom.DeclarationFlags.Optional;
return true;
}
return false;
});
return newInterface;
},
},
// Transformer from EachOf to object type. EachOf contains the `expressions` array of properties (TripleConstraint)
EachOf: {
transformer: async (eachOf, getTransformedChildren, setReturnPointer) => {
const transformedChildren = await getTransformedChildren();
const name = nameFromObject(eachOf);
const objectType = name
? dom.create.interface(name)
: dom.create.objectType([]);
setReturnPointer(objectType);
const inputProps: dom.PropertyDeclaration[] = [];
transformedChildren.expressions.forEach((expr) => {
if (!expr || typeof expr === "string") return;
const kind = (expr as any).kind;
if (kind === "property") {
inputProps.push(expr as dom.PropertyDeclaration);
} else if (kind === "object" || kind === "interface") {
(expr as dom.ObjectType | dom.InterfaceDeclaration).members.forEach(
(m) => {
if ((m as any).kind === "property") {
inputProps.push(m as dom.PropertyDeclaration);
}
},
);
}
});
const deduped = dedupeCompactProperties(inputProps);
resolveCollisions(deduped);
objectType.members.push(...deduped);
return objectType;
},
},
// Transformer from triple constraints to type properties.
TripleConstraint: {
transformer: async (
tripleConstraint,
getTransformedChildren,
_setReturnPointer,
node,
context,
) => {
const transformedChildren = await getTransformedChildren();
const rdfTypes = getRdfTypesForTripleConstraint(node);
const baseName = context.getNameFromIri(
tripleConstraint.predicate,
rdfTypes[0],
);
const max = tripleConstraint.max;
const isPlural = max === -1 || (max !== undefined && max !== 1);
const isOptional = tripleConstraint.min === 0;
let valueType: dom.Type = dom.type.any;
if (transformedChildren.valueExpr)
valueType = transformedChildren.valueExpr as dom.Type;
// Normalize NodeConstraint returned object forms for IRIs into IRI
// Heuristic: existing transformer (compact) returns string/number/boolean OR object/interface.
// We treat any simple string/number/boolean/name as primitive.
// Determine category
const objLike = isObjectLike(valueType);
const isUnion =
(valueType as unknown as { kind?: string })?.kind === "union";
const unionMembers: dom.Type[] = isUnion
? (valueType as dom.UnionType).members
: [];
const unionAllObjLike =
isUnion && unionMembers.length > 0 && unionMembers.every(isObjectLike);
const primLike = isPrimitiveLike(valueType);
if (
!primLike &&
!objLike &&
(valueType as dom.UnionType).kind === "union"
) {
const u = valueType as dom.UnionType;
const hasObj = u.members.some(isObjectLike);
const hasPrim = u.members.some(isPrimitiveLike);
if (isPlural && hasObj && hasPrim) {
throw new Error(
`Mixed plural union (object + primitive) not supported for predicate ${tripleConstraint.predicate}`,
);
}
}
let finalType: dom.Type;
if (isPlural) {
if (objLike || unionAllObjLike) {
if (
(valueType as dom.InterfaceDeclaration).kind === "interface" &&
(valueType as dom.InterfaceDeclaration).name
) {
const ifaceName = (valueType as dom.InterfaceDeclaration).name;
// Dictionary of full object instances keyed by IRI
finalType = {
kind: "name",
name: "Record",
typeArguments: [
dom.create.namedTypeReference("IRI"),
dom.create.namedTypeReference(ifaceName),
],
} as dom.Type;
} else {
// Anonymous object or union of anonymous/interface objects
let valueForRecord: dom.Type = valueType;
if (unionAllObjLike) {
// Ensure each union member has id?: IRI if anonymous object
(valueType as dom.UnionType).members = (
valueType as dom.UnionType
).members.map((m) => {
if ((m as dom.InterfaceDeclaration).kind === "interface")
return m;
if ((m as dom.ObjectType).kind === "object") {
const anonMembers = (
m as unknown as { members?: dom.PropertyDeclaration[] }
).members;
const hasId = (anonMembers || []).some(
(mm) => mm.name === "id",
);
if (!hasId && anonMembers) {
anonMembers.unshift(
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.Optional,
),
);
}
}
return m;
});
valueForRecord = valueType; // union retained
} else {
const anon = valueType as dom.ObjectType;
const anonMembers = (
anon as unknown as { members?: dom.PropertyDeclaration[] }
).members;
const hasId = (anonMembers || []).some((m) => m.name === "id");
if (!hasId && anonMembers) {
anonMembers.unshift(
dom.create.property(
"id",
dom.create.namedTypeReference("IRI"),
dom.DeclarationFlags.Optional,
),
);
}
valueForRecord = anon as dom.Type;
}
finalType = {
kind: "name",
name: "Record",
typeArguments: [
dom.create.namedTypeReference("IRI"),
valueForRecord,
],
} as dom.Type;
}
} else {
finalType = {
kind: "name",
name: "Set",
typeArguments: [valueType],
} as dom.Type;
}
} else {
// Singular: always the interface/object type itself (never Id union)
if (
(valueType as dom.InterfaceDeclaration).kind === "interface" &&
(valueType as dom.InterfaceDeclaration).name
) {
finalType = dom.create.namedTypeReference(
(valueType as dom.InterfaceDeclaration).name,
);
} else {
finalType = valueType;
}
}
const prop = dom.create.property(
baseName,
finalType,
isOptional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None,
);
predicateIriByProp.set(prop, tripleConstraint.predicate);
prop.jsDocComment =
commentFromAnnotations(tripleConstraint.annotations) || "";
// Always append original predicate IRI reference (compact format only)
// If an existing comment is present, add a blank line before the Original IRI line.
if (prop.jsDocComment) {
prop.jsDocComment = `${prop.jsDocComment}\n\nOriginal IRI: ${tripleConstraint.predicate}`;
} else {
prop.jsDocComment = `Original IRI: ${tripleConstraint.predicate}`;
}
return prop;
},
},
// Transformer from node constraint to type
NodeConstraint: {
transformer: async (nodeConstraint) => {
if (nodeConstraint.datatype) {
switch (nodeConstraint.datatype) {
case "http://www.w3.org/2001/XMLSchema#boolean":
return dom.type.boolean;
case "http://www.w3.org/2001/XMLSchema#byte":
case "http://www.w3.org/2001/XMLSchema#decimal":
case "http://www.w3.org/2001/XMLSchema#double":
case "http://www.w3.org/2001/XMLSchema#float":
case "http://www.w3.org/2001/XMLSchema#int":
case "http://www.w3.org/2001/XMLSchema#integer":
case "http://www.w3.org/2001/XMLSchema#long":
case "http://www.w3.org/2001/XMLSchema#negativeInteger":
case "http://www.w3.org/2001/XMLSchema#nonNegativeInteger":
case "http://www.w3.org/2001/XMLSchema#nonPositiveInteger":
case "http://www.w3.org/2001/XMLSchema#positiveInteger":
case "http://www.w3.org/2001/XMLSchema#short":
case "http://www.w3.org/2001/XMLSchema#unsignedLong":
case "http://www.w3.org/2001/XMLSchema#unsignedInt":
case "http://www.w3.org/2001/XMLSchema#unsignedShort":
case "http://www.w3.org/2001/XMLSchema#unsignedByte":
return dom.type.number;
default:
return dom.type.string; // treat most as string
}
}
if (nodeConstraint.nodeKind) {
switch (nodeConstraint.nodeKind) {
case "iri":
return dom.create.namedTypeReference("IRI");
case "bnode":
return dom.type.string; // opaque id as string
case "nonliteral":
return dom.create.namedTypeReference("IRI");
case "literal":
default:
return dom.type.string;
}
}
if (nodeConstraint.values) {
const u = dom.create.union([]);
nodeConstraint.values.forEach((v) => {
if (typeof v === "string") u.members.push(dom.type.stringLiteral(v));
});
if (!u.members.length) return dom.type.string;
if (u.members.length === 1) return u.members[0];
return u;
}
return dom.type.any;
},
},
// Transformer from ShapeOr to union type
ShapeOr: {
transformer: async (_shapeOr, getTransformedChildren) => {
const tc = await getTransformedChildren();
const valid: dom.Type[] = [];
tc.shapeExprs.forEach((t) => {
if (typeof t === "object") valid.push(t);
});
return dom.create.union(valid);
},
},
// Transformer from ShapeAnd to intersection type
ShapeAnd: {
transformer: async (_shapeAnd, getTransformedChildren) => {
const tc = await getTransformedChildren();
const valid: dom.Type[] = [];
tc.shapeExprs.forEach((t) => {
if (typeof t === "object") valid.push(t);
});
return dom.create.intersection(valid);
},
},
// Transformer from ShapeNot to type - not supported.
ShapeNot: {
transformer: async () => {
throw new Error("ShapeNot not supported (compact)");
},
},
// Transformer from ShapeExternal to type - not supported.
ShapeExternal: {
transformer: async () => {
throw new Error("ShapeExternal not supported (compact)");
},
},
});

@ -1,59 +1,18 @@
import type { ContextDefinition } from "jsonld";
import type { Schema } from "shexj";
import { JsonLdContextBuilder } from "../context/JsonLdContextBuilder.js";
import { ShexJNameVisitor } from "../context/ShexJContextVisitor.js";
import { jsonld2graphobject } from "jsonld2graphobject";
import { ShexJTypingTransformer } from "./ShexJTypingTransformer.js";
import * as dom from "dts-dom";
import type { TypeingReturn } from "./shexjToTypingLdo.js";
import { shexjToTypingLdo } from "./shexjToTypingLdo.js";
import { shexjToTypingCompact } from "./shexjToTypingCompact.js";
export interface TypeingReturn {
typingsString: string;
typings: {
typingString: string;
dts: dom.TopLevelDeclaration;
}[];
export interface TypingsOptions {
format?: "ldo" | "compact";
}
export async function shexjToTyping(
shexj: Schema,
): Promise<[TypeingReturn, ContextDefinition]> {
const processedShexj: Schema = (await jsonld2graphobject(
{
...shexj,
"@id": "SCHEMA",
"@context": "http://www.w3.org/ns/shex.jsonld",
},
"SCHEMA",
)) as unknown as Schema;
const jsonLdContextBuilder = new JsonLdContextBuilder();
await ShexJNameVisitor.visit(processedShexj, "Schema", jsonLdContextBuilder);
const declarations = await ShexJTypingTransformer.transform(
processedShexj,
"Schema",
{
getNameFromIri:
jsonLdContextBuilder.getNameFromIri.bind(jsonLdContextBuilder),
},
);
const typings = declarations.map((declaration) => {
return {
typingString: dom
.emit(declaration, {
rootFlags: dom.ContextFlags.InAmbientNamespace,
})
.replace(/\r\n/g, "\n"),
dts: declaration,
};
});
const typingsString =
`import { LdSet, LdoJsonldContext } from "@ldo/ldo"\n\n` +
typings.map((typing) => `export ${typing.typingString}`).join("");
const typeingReturn: TypeingReturn = {
typingsString,
typings,
};
return [typeingReturn, jsonLdContextBuilder.generateJsonldContext()];
options: TypingsOptions = {},
): Promise<[TypeingReturn, ContextDefinition | undefined]> {
const format = options.format || "ldo";
if (format === "compact") return shexjToTypingCompact(shexj);
return shexjToTypingLdo(shexj);
}

@ -0,0 +1,52 @@
import type { Schema } from "shexj";
import { jsonld2graphobject } from "jsonld2graphobject";
import { ShexJNameVisitor } from "../context/ShexJContextVisitor.js";
import { JsonLdContextBuilder } from "../context/JsonLdContextBuilder.js";
import {
ShexJTypingTransformerCompact,
additionalCompactEnumAliases,
} from "./ShexJTypingTransformerCompact.js";
import * as dom from "dts-dom";
import type { TypeingReturn } from "./shexjToTypingLdo.js";
export async function shexjToTypingCompact(
shexj: Schema,
): Promise<[TypeingReturn, undefined]> {
// Prepare processed schema (names still rely on context visitor)
const processedShexj: Schema = (await jsonld2graphobject(
{
...shexj,
"@id": "SCHEMA",
"@context": "http://www.w3.org/ns/shex.jsonld",
},
"SCHEMA",
)) as unknown as Schema;
const nameBuilder = new JsonLdContextBuilder();
await ShexJNameVisitor.visit(processedShexj, "Schema", nameBuilder);
additionalCompactEnumAliases.clear();
const declarations = await ShexJTypingTransformerCompact.transform(
processedShexj,
"Schema",
{
getNameFromIri: nameBuilder.getNameFromIri.bind(nameBuilder),
},
);
// Append only enum aliases (no interface Id aliases in compact format now)
additionalCompactEnumAliases.forEach((alias) => {
const exists = declarations.some((d) => (d as any).name === alias);
if (!exists) declarations.push(dom.create.alias(alias, dom.type.string));
});
const typings = declarations.map((declaration) => ({
typingString: dom
.emit(declaration, { rootFlags: dom.ContextFlags.InAmbientNamespace })
.replace(/\r\n/g, "\n"),
dts: declaration,
}));
const header = `export type IRI = string;\n\n`;
const typingsString =
header + typings.map((t) => `export ${t.typingString}`).join("");
return [{ typingsString, typings }, undefined];
}

@ -0,0 +1,58 @@
import type { ContextDefinition } from "jsonld";
import type { Schema } from "shexj";
import { JsonLdContextBuilder } from "../context/JsonLdContextBuilder.js";
import { ShexJNameVisitor } from "../context/ShexJContextVisitor.js";
import { jsonld2graphobject } from "jsonld2graphobject";
import { ShexJTypingTransformer } from "./ShexJTypingTransformer.js";
import * as dom from "dts-dom";
export interface TypeingReturn {
typingsString: string;
typings: {
typingString: string;
dts: dom.TopLevelDeclaration;
}[];
}
export async function shexjToTypingLdo(
shexj: Schema,
): Promise<[TypeingReturn, ContextDefinition]> {
const processedShexj: Schema = (await jsonld2graphobject(
{
...shexj,
"@id": "SCHEMA",
"@context": "http://www.w3.org/ns/shex.jsonld",
},
"SCHEMA",
)) as unknown as Schema;
const jsonLdContextBuilder = new JsonLdContextBuilder();
await ShexJNameVisitor.visit(processedShexj, "Schema", jsonLdContextBuilder);
const declarations = await ShexJTypingTransformer.transform(
processedShexj,
"Schema",
{
getNameFromIri:
jsonLdContextBuilder.getNameFromIri.bind(jsonLdContextBuilder),
},
);
const typings = declarations.map((declaration) => {
return {
typingString: dom
.emit(declaration, {
rootFlags: dom.ContextFlags.InAmbientNamespace,
})
.replace(/\r\n/g, "\n"),
dts: declaration,
};
});
const header = `import { LdSet, LdoJsonldContext } from "@ldo/ldo"\n\n`;
const typingsString =
header + typings.map((t) => `export ${t.typingString}`).join("");
const typeingReturn: TypeingReturn = { typingsString, typings };
return [typeingReturn, jsonLdContextBuilder.generateJsonldContext()];
}

@ -7,13 +7,13 @@ console.warn = () => {};
describe("context", () => {
testData.forEach(({ name, shexc, successfulContext }) => {
// Skip entries with empty context placeholder (compact-only tests)
if (!successfulContext || !Object.keys(successfulContext).length) return;
it(`Creates a context for ${name}`, async () => {
const schema: Schema = parser
.construct("https://ldo.js.org/")
.parse(shexc);
// console.log("SCHEMA:", JSON.stringify(schema, null, 2));
const context = await shexjToContext(schema);
// console.log("CONTEXT:", JSON.stringify(context, null, 2));
expect(context).toEqual(successfulContext);
});
});

@ -72,4 +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`,
};

@ -80,4 +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`,
};

@ -0,0 +1,17 @@
import type { TestData } from "./testData.js";
export const mixedPluralUnionError: TestData = {
name: "mixed plural union error",
shexc: `
PREFIX ex: <http://ex/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
ex:FooShape { ex:mixed ( @ex:BarShape OR @ex:BazShape )* }
ex:BarShape { ex:label . }
ex:BazShape { ex:other . }
`,
sampleTurtle: ``,
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`,
};

@ -0,0 +1,15 @@
import type { TestData } from "./testData.js";
export const pluralAnonymous: TestData = {
name: "plural anonymous",
shexc: `
PREFIX ex: <http://ex/>
ex:ConfigHolderShape { ex:configs @ex:ConfigShape* }
ex:ConfigShape { ex:key . ; ex:val . }
`,
sampleTurtle: ``,
baseNode: "http://ex/cfg1",
successfulContext: {},
successfulTypings: "",
successfulCompactTypings: `export type IRI = string;\n\nexport interface ConfigHolder {\n id?: IRI;\n /**\n * Original IRI: http://ex/configs\n */\n configs?: Record<IRI, Config>;\n}\n\nexport interface Config {\n id?: IRI;\n /**\n * Original IRI: http://ex/key\n */\n key: any;\n /**\n * Original IRI: http://ex/val\n */\n val: any;\n}\n\n`,
};

@ -0,0 +1,15 @@
import type { TestData } from "./testData.js";
export const pluralObjects: TestData = {
name: "plural objects",
shexc: `
PREFIX ex: <http://ex/>
ex:FooShape { ex:bars @ex:BarShape* }
ex:BarShape { ex:name . }
`,
sampleTurtle: ``,
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`,
};

@ -0,0 +1,16 @@
import type { TestData } from "./testData.js";
export const pluralUnionObjects: TestData = {
name: "plural union objects",
shexc: `
PREFIX ex: <http://ex/>
ex:A { ex:items ( @ex:Foo OR @ex:Bar )* }
ex:Foo { ex:f . }
ex:Bar { ex:b . }
`,
sampleTurtle: ``,
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`,
};

@ -0,0 +1,18 @@
import type { TestData } from "./testData.js";
export const propertyCollision: TestData = {
name: "property collision",
shexc: `
PREFIX ex: <http://ex/>
PREFIX ex2: <http://ex2/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX v1: <http://example.com/v1#>
PREFIX ver: <http://api.example.com/v2.1:>
ex:C { ex:label . ; ex2:label . ; foaf:label . ; v1:label . ; ver:label . }
`,
sampleTurtle: ``,
baseNode: "http://ex/c1",
successfulContext: {} as any,
successfulTypings: "",
successfulCompactTypings: `export type IRI = string;\n\nexport interface C {\n id?: IRI;\n /**\n * Original IRI: http://ex/label\n */\n ex_label: any;\n /**\n * Original IRI: http://ex2/label\n */\n ex2_label: any;\n /**\n * Original IRI: http://xmlns.com/foaf/0.1/label\n */\n "0.1_label": any;\n /**\n * Original IRI: http://example.com/v1#label\n */\n v1_label: any;\n /**\n * Original IRI: http://api.example.com/v2.1:label\n */\n "v2.1:label": any;\n}\n\n`,
};

@ -114,4 +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`,
};

@ -57,4 +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`,
};

@ -11,6 +11,11 @@ import { orSimple } from "./orSimple.js";
import { andSimple } from "./andSimple.js";
import { eachOfAndSimple } from "./eachOfAndSimple.js";
import { multipleSharedPredicates } from "./multipleSharedPredicates.js";
import { pluralObjects } from "./pluralObjects.js";
import { pluralAnonymous } from "./pluralAnonymous.js";
import { mixedPluralUnionError } from "./mixedPluralUnionError.js";
import { pluralUnionObjects } from "./pluralUnionObjects.js";
import { propertyCollision } from "./propertyCollision.js";
export interface TestData {
name: string;
@ -19,6 +24,7 @@ export interface TestData {
baseNode: string;
successfulContext: LdoJsonldContext;
successfulTypings: string;
successfulCompactTypings?: string;
}
export const testData: TestData[] = [
@ -34,4 +40,9 @@ export const testData: TestData[] = [
andSimple,
eachOfAndSimple,
multipleSharedPredicates,
pluralObjects,
pluralAnonymous,
mixedPluralUnionError,
pluralUnionObjects,
propertyCollision,
];

@ -0,0 +1,24 @@
import parser from "@shexjs/parser";
import { testData } from "./testData/testData.js";
import { shexjToTyping } from "../src/typing/shexjToTyping.js";
import type { Schema } from "shexj";
console.warn = () => {};
describe("typing-compact", () => {
testData.forEach((td) => {
const { name, shexc, successfulCompactTypings } = td;
if (!successfulCompactTypings) return; // skip if neither
it(`Creates compact typings for ${name}`, async () => {
const schema: Schema = parser
.construct("https://ldo.js.org/")
.parse(shexc);
const [compact] = await shexjToTyping(schema, { format: "compact" });
const normalize = (s: string) =>
s.replace(/\r\n/g, "\n").replace(/\n+$/s, "\n");
expect(normalize(compact.typingsString)).toBe(
normalize(successfulCompactTypings)
);
});
});
});

@ -6,16 +6,16 @@ import type { Schema } from "shexj";
console.warn = () => {};
describe("typing", () => {
testData.forEach(({ name, shexc, successfulTypings }) => {
it(`Creates a typings for ${name}`, async () => {
testData.forEach((td) => {
const { name, shexc, successfulTypings } = td;
if (!successfulTypings) return; // skip entries only meant for compact tests
it(`Creates typings for ${name}`, async () => {
const schema: Schema = parser
.construct("https://ldo.js.org/")
.parse(shexc);
// console.log("SCHEMA:", JSON.stringify(schema, null, 2));
const [typings] = await shexjToTyping(schema);
// console.log(typings.typingsString);
// console.log(JSON.stringify(typings.typingsString));
expect(typings.typingsString).toBe(successfulTypings);
// Compact format tested in typing.compact.test.ts
});
});
});

@ -1,5 +1,8 @@
import type { BaseQuad, DatasetFactory } from "@rdfjs/types";
import type { ISubscribableDataset, ITransactionDatasetFactory } from "./types.js";
import type {
ISubscribableDataset,
ITransactionDatasetFactory,
} from "./types.js";
import { TransactionDataset } from "./TransactionDataset.js";
export class TransactionDatasetFactory<InAndOutQuad extends BaseQuad>

Loading…
Cancel
Save