@ng-org/shex-orm 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +240 -0
- package/dist/ShexJTypes.d.ts +542 -0
- package/dist/ShexJTypes.d.ts.map +1 -0
- package/dist/ShexJTypes.js +10 -0
- package/dist/build.d.ts +8 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +72 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/schema-converter/__tests__/typingTransformer.test.d.ts +2 -0
- package/dist/schema-converter/__tests__/typingTransformer.test.d.ts.map +1 -0
- package/dist/schema-converter/__tests__/typingTransformer.test.js +76 -0
- package/dist/schema-converter/converter.d.ts +12 -0
- package/dist/schema-converter/converter.d.ts.map +1 -0
- package/dist/schema-converter/converter.js +79 -0
- package/dist/schema-converter/templates/schema.ejs +8 -0
- package/dist/schema-converter/templates/shapeTypes.ejs +14 -0
- package/dist/schema-converter/templates/typings.ejs +14 -0
- package/dist/schema-converter/transformers/ShexJSchemaTransformer.d.ts +348 -0
- package/dist/schema-converter/transformers/ShexJSchemaTransformer.d.ts.map +1 -0
- package/dist/schema-converter/transformers/ShexJSchemaTransformer.js +239 -0
- package/dist/schema-converter/transformers/ShexJTypingTransformer.d.ts +366 -0
- package/dist/schema-converter/transformers/ShexJTypingTransformer.d.ts.map +1 -0
- package/dist/schema-converter/transformers/ShexJTypingTransformer.js +623 -0
- package/dist/schema-converter/util/ShapeInterfaceDeclaration.d.ts +5 -0
- package/dist/schema-converter/util/ShapeInterfaceDeclaration.d.ts.map +1 -0
- package/dist/schema-converter/util/ShapeInterfaceDeclaration.js +1 -0
- package/dist/schema-converter/util/annotateReadablePredicates.d.ts +8 -0
- package/dist/schema-converter/util/annotateReadablePredicates.d.ts.map +1 -0
- package/dist/schema-converter/util/annotateReadablePredicates.js +148 -0
- package/dist/schema-converter/util/dedupeObjectTypeMembers.d.ts +3 -0
- package/dist/schema-converter/util/dedupeObjectTypeMembers.d.ts.map +1 -0
- package/dist/schema-converter/util/dedupeObjectTypeMembers.js +47 -0
- package/dist/schema-converter/util/getRdfTypesForTripleConstraint.d.ts +4 -0
- package/dist/schema-converter/util/getRdfTypesForTripleConstraint.d.ts.map +1 -0
- package/dist/schema-converter/util/getRdfTypesForTripleConstraint.js +98 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/util/forAllShapes.d.ts +2 -0
- package/dist/util/forAllShapes.d.ts.map +1 -0
- package/dist/util/forAllShapes.js +25 -0
- package/package.json +67 -0
- package/src/ShexJTypes.ts +616 -0
- package/src/build.ts +106 -0
- package/src/cli.ts +23 -0
- package/src/index.ts +1 -0
- package/src/schema-converter/__tests__/typingTransformer.test.ts +85 -0
- package/src/schema-converter/converter.ts +128 -0
- package/src/schema-converter/templates/schema.ejs +8 -0
- package/src/schema-converter/templates/shapeTypes.ejs +14 -0
- package/src/schema-converter/templates/typings.ejs +14 -0
- package/src/schema-converter/transformers/ShexJSchemaTransformer.ts +284 -0
- package/src/schema-converter/transformers/ShexJTypingTransformer.ts +807 -0
- package/src/schema-converter/util/ShapeInterfaceDeclaration.ts +5 -0
- package/src/schema-converter/util/annotateReadablePredicates.ts +173 -0
- package/src/schema-converter/util/dedupeObjectTypeMembers.ts +61 -0
- package/src/schema-converter/util/getRdfTypesForTripleConstraint.ts +153 -0
- package/src/types.ts +51 -0
- package/src/util/forAllShapes.ts +39 -0
package/src/build.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Copyright (c) 2025 Laurin Weger, Par le Peuple, NextGraph.org developers
|
|
2
|
+
// All rights reserved.
|
|
3
|
+
// Copyright (c) 2023 Jackson Morgan
|
|
4
|
+
// Licensed under the Apache License, Version 2.0
|
|
5
|
+
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
|
6
|
+
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
|
7
|
+
// at your option. All files in the project carrying such
|
|
8
|
+
// notice may not be copied, modified, or distributed except
|
|
9
|
+
// according to those terms.
|
|
10
|
+
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
11
|
+
|
|
12
|
+
import fs from "fs-extra";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import type { Schema } from "./ShexJTypes.ts";
|
|
15
|
+
import parser from "@shexjs/parser";
|
|
16
|
+
import { shexJConverter } from "./schema-converter/converter.ts";
|
|
17
|
+
import { renderFile } from "ejs";
|
|
18
|
+
import prettier from "prettier";
|
|
19
|
+
import loading from "loading-cli";
|
|
20
|
+
import { dirname } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { forAllShapes } from "./util/forAllShapes.ts";
|
|
23
|
+
import annotateReadablePredicates from "./schema-converter/util/annotateReadablePredicates.ts";
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
|
|
29
|
+
interface BuildOptions {
|
|
30
|
+
input: string;
|
|
31
|
+
output: string;
|
|
32
|
+
baseIRI?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function build({
|
|
36
|
+
input: inputFile,
|
|
37
|
+
output: outputFile,
|
|
38
|
+
baseIRI = "https://nextgraph.org/shapes#",
|
|
39
|
+
}: BuildOptions) {
|
|
40
|
+
const load = loading("Preparing Environment");
|
|
41
|
+
load.start();
|
|
42
|
+
// Prepare new folder by clearing/and/or creating it
|
|
43
|
+
if (fs.existsSync(outputFile)) {
|
|
44
|
+
await fs.promises.rm(outputFile, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
await fs.promises.mkdir(outputFile);
|
|
47
|
+
|
|
48
|
+
const fileTemplates: string[] = [];
|
|
49
|
+
|
|
50
|
+
// Pre-annotate schema with readablePredicate to unify naming across outputs
|
|
51
|
+
fileTemplates.push("schema", "typings", "shapeTypes");
|
|
52
|
+
|
|
53
|
+
load.text = "Generating Schema Documents";
|
|
54
|
+
await forAllShapes(inputFile, async (fileName, shexC) => {
|
|
55
|
+
// Convert to ShexJ
|
|
56
|
+
let schema: Schema;
|
|
57
|
+
try {
|
|
58
|
+
// Prase Shex schema to JSON.
|
|
59
|
+
// TODO: Do we need the base IRI?
|
|
60
|
+
// @ts-ignore ...
|
|
61
|
+
schema = parser.construct(baseIRI).parse(shexC);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const errMessage =
|
|
64
|
+
err instanceof Error
|
|
65
|
+
? err.message
|
|
66
|
+
: typeof err === "string"
|
|
67
|
+
? err
|
|
68
|
+
: "Unknown Error";
|
|
69
|
+
console.error(`Error processing ${fileName}: ${errMessage}`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Add readable predicates to schema as the single source of truth.
|
|
74
|
+
// @ts-ignore ...
|
|
75
|
+
annotateReadablePredicates(schema);
|
|
76
|
+
|
|
77
|
+
const [typings, compactSchema] = await shexJConverter(schema);
|
|
78
|
+
|
|
79
|
+
await Promise.all(
|
|
80
|
+
fileTemplates.map(async (templateName) => {
|
|
81
|
+
const finalContent = await renderFile(
|
|
82
|
+
path.join(
|
|
83
|
+
__dirname,
|
|
84
|
+
"schema-converter",
|
|
85
|
+
"templates",
|
|
86
|
+
`${templateName}.ejs`
|
|
87
|
+
),
|
|
88
|
+
{
|
|
89
|
+
typings: typings.typings,
|
|
90
|
+
fileName,
|
|
91
|
+
schema: JSON.stringify(schema, null, 2),
|
|
92
|
+
compactSchema: JSON.stringify(compactSchema, null, 2),
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
await fs.promises.writeFile(
|
|
96
|
+
path.join(outputFile, `${fileName}.${templateName}.ts`),
|
|
97
|
+
await prettier.format(finalContent, {
|
|
98
|
+
parser: "typescript",
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
load.stop();
|
|
106
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
import { build } from "./build.ts";
|
|
5
|
+
|
|
6
|
+
program
|
|
7
|
+
.name("NG-ORM")
|
|
8
|
+
.description("CLI to some JavaScript string utilities")
|
|
9
|
+
.version("0.1.0");
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.command("build")
|
|
13
|
+
.description("Build contents of a shex folder into Shape Types")
|
|
14
|
+
.option("-i, --input <inputPath>", "Provide the input path", "./.shapes")
|
|
15
|
+
.option("-o, --output <outputPath>", "Provide the output path", "./.orm")
|
|
16
|
+
.option(
|
|
17
|
+
"-b, --baseIRI <baseIri>",
|
|
18
|
+
"The base IRI for anonymous shapes",
|
|
19
|
+
"https://nextgraph.org/shapes#"
|
|
20
|
+
)
|
|
21
|
+
.action(build);
|
|
22
|
+
|
|
23
|
+
program.parse();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./types.ts";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import annotateReadablePredicates from "../util/annotateReadablePredicates.ts";
|
|
3
|
+
import { shexJConverter } from "../converter.ts";
|
|
4
|
+
|
|
5
|
+
const TYPE_IRI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
|
|
6
|
+
|
|
7
|
+
function buildSchema() {
|
|
8
|
+
return {
|
|
9
|
+
type: "Schema",
|
|
10
|
+
shapes: [
|
|
11
|
+
{
|
|
12
|
+
type: "ShapeDecl",
|
|
13
|
+
id: "http://example.org/Expense",
|
|
14
|
+
shapeExpr: {
|
|
15
|
+
type: "Shape",
|
|
16
|
+
expression: {
|
|
17
|
+
type: "EachOf",
|
|
18
|
+
expressions: [
|
|
19
|
+
{
|
|
20
|
+
type: "TripleConstraint",
|
|
21
|
+
predicate: TYPE_IRI,
|
|
22
|
+
valueExpr: {
|
|
23
|
+
type: "NodeConstraint",
|
|
24
|
+
values: [
|
|
25
|
+
{
|
|
26
|
+
value: "http://example.org/Expense",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: "ShapeDecl",
|
|
37
|
+
id: "http://example.org/ExpenseCategory",
|
|
38
|
+
shapeExpr: {
|
|
39
|
+
type: "Shape",
|
|
40
|
+
extra: [TYPE_IRI],
|
|
41
|
+
expression: {
|
|
42
|
+
type: "EachOf",
|
|
43
|
+
expressions: [
|
|
44
|
+
{
|
|
45
|
+
type: "TripleConstraint",
|
|
46
|
+
predicate: TYPE_IRI,
|
|
47
|
+
valueExpr: {
|
|
48
|
+
type: "NodeConstraint",
|
|
49
|
+
values: [
|
|
50
|
+
{
|
|
51
|
+
value: "http://example.org/ExpenseCategory",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function buildTypingsText(): Promise<string> {
|
|
65
|
+
const schema = buildSchema();
|
|
66
|
+
annotateReadablePredicates(schema as any);
|
|
67
|
+
const [typings] = await shexJConverter(schema as any);
|
|
68
|
+
return typings.typingsString;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe("ShexJTypingTransformer", () => {
|
|
72
|
+
it("emits literal unions for rdf:type constraints", async () => {
|
|
73
|
+
const typings = await buildTypingsText();
|
|
74
|
+
expect(typings).toMatch(
|
|
75
|
+
/interface Expense[\s\S]*?"@type": "http:\/\/example\.org\/Expense";/
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("treats EXTRA rdf:type predicates as plural", async () => {
|
|
80
|
+
const typings = await buildTypingsText();
|
|
81
|
+
expect(typings).toMatch(
|
|
82
|
+
/interface ExpenseCategory[\s\S]*?"@type": Set<"http:\/\/example\.org\/ExpenseCategory" \|[\s]*\(?string[\s]*&[\s]*\{[\s]*\}\)?>;/
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Copyright (c) 2025 Laurin Weger, Par le Peuple, NextGraph.org developers
|
|
2
|
+
// All rights reserved.
|
|
3
|
+
// Copyright (c) 2023 Jackson Morgan
|
|
4
|
+
// Licensed under the Apache License, Version 2.0
|
|
5
|
+
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
|
6
|
+
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
|
7
|
+
// at your option. All files in the project carrying such
|
|
8
|
+
// notice may not be copied, modified, or distributed except
|
|
9
|
+
// according to those terms.
|
|
10
|
+
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
11
|
+
|
|
12
|
+
import type { Schema } from "@ldo/traverser-shexj";
|
|
13
|
+
import { jsonld2graphobject } from "jsonld2graphobject";
|
|
14
|
+
import * as dom from "dts-dom";
|
|
15
|
+
import {
|
|
16
|
+
ShexJTypingTransformerCompact,
|
|
17
|
+
additionalCompactEnumAliases,
|
|
18
|
+
} from "./transformers/ShexJTypingTransformer.ts";
|
|
19
|
+
import { ShexJSchemaTransformerCompact } from "./transformers/ShexJSchemaTransformer.ts";
|
|
20
|
+
import type { Schema as ShapeSchema, Shape } from "../types.ts";
|
|
21
|
+
|
|
22
|
+
export interface TypingReturn {
|
|
23
|
+
typingsString: string;
|
|
24
|
+
typings: {
|
|
25
|
+
typingString: string;
|
|
26
|
+
dts: dom.TopLevelDeclaration;
|
|
27
|
+
}[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function shexJConverter(
|
|
31
|
+
shexj: Schema
|
|
32
|
+
): Promise<[TypingReturn, ShapeSchema]> {
|
|
33
|
+
// Prepare processed schema (names still rely on context visitor)
|
|
34
|
+
const processedShexj: Schema = (await jsonld2graphobject(
|
|
35
|
+
{
|
|
36
|
+
...shexj,
|
|
37
|
+
"@id": "SCHEMA",
|
|
38
|
+
"@context": "http://www.w3.org/ns/shex.jsonld",
|
|
39
|
+
},
|
|
40
|
+
"SCHEMA"
|
|
41
|
+
)) as unknown as Schema;
|
|
42
|
+
|
|
43
|
+
additionalCompactEnumAliases.clear();
|
|
44
|
+
const declarations = await ShexJTypingTransformerCompact.transform(
|
|
45
|
+
processedShexj,
|
|
46
|
+
"Schema",
|
|
47
|
+
null
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const compactSchemaShapesUnflattened =
|
|
51
|
+
await ShexJSchemaTransformerCompact.transform(
|
|
52
|
+
processedShexj,
|
|
53
|
+
"Schema",
|
|
54
|
+
null
|
|
55
|
+
);
|
|
56
|
+
const compactSchema = flattenSchema(compactSchemaShapesUnflattened);
|
|
57
|
+
|
|
58
|
+
// Append only enum aliases (no interface Id aliases in compact format now)
|
|
59
|
+
const hasName = (d: unknown): d is { name: string } =>
|
|
60
|
+
typeof (d as { name?: unknown }).name === "string";
|
|
61
|
+
additionalCompactEnumAliases.forEach((alias) => {
|
|
62
|
+
const exists = declarations.some((d) => hasName(d) && d.name === alias);
|
|
63
|
+
if (!exists)
|
|
64
|
+
declarations.push(
|
|
65
|
+
dom.create.alias(alias, dom.create.namedTypeReference("IRI"))
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const typings = declarations.map((declaration) => ({
|
|
70
|
+
typingString: dom
|
|
71
|
+
.emit(declaration, {
|
|
72
|
+
rootFlags: dom.ContextFlags.InAmbientNamespace,
|
|
73
|
+
})
|
|
74
|
+
.replace(/\r\n/g, "\n"),
|
|
75
|
+
dts: declaration,
|
|
76
|
+
}));
|
|
77
|
+
const header = `export type IRI = string;\n\n`;
|
|
78
|
+
const typingsString =
|
|
79
|
+
header + typings.map((t) => `export ${t.typingString}`).join("");
|
|
80
|
+
|
|
81
|
+
return [{ typingsString, typings }, compactSchema];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Shapes may be nested. Put all to their root and give nested ones ids. */
|
|
85
|
+
function flattenSchema(shapes: Shape[]): ShapeSchema {
|
|
86
|
+
let schema: ShapeSchema = {};
|
|
87
|
+
|
|
88
|
+
for (const shape of shapes) {
|
|
89
|
+
schema[shape.iri] = shape;
|
|
90
|
+
|
|
91
|
+
// Find nested, unflattened (i.e. anonymous) schemas in predicates' dataTypes.
|
|
92
|
+
for (const pred of shape.predicates) {
|
|
93
|
+
for (let i = 0; i < pred.dataTypes.length; i++) {
|
|
94
|
+
const dt = pred.dataTypes[i];
|
|
95
|
+
if (
|
|
96
|
+
dt.valType === "shape" &&
|
|
97
|
+
typeof dt.shape === "object" &&
|
|
98
|
+
dt.shape !== null
|
|
99
|
+
) {
|
|
100
|
+
// create a deterministic id for the nested shape; include index if multiple shape entries exist
|
|
101
|
+
const shapeCount = pred.dataTypes.filter(
|
|
102
|
+
(d) => d.valType === "shape"
|
|
103
|
+
).length;
|
|
104
|
+
const newId =
|
|
105
|
+
shape.iri +
|
|
106
|
+
"||" +
|
|
107
|
+
pred.iri +
|
|
108
|
+
(shapeCount > 1 ? `||${i}` : "");
|
|
109
|
+
|
|
110
|
+
// Recurse
|
|
111
|
+
const flattened = flattenSchema([
|
|
112
|
+
{
|
|
113
|
+
...(dt.shape as Shape),
|
|
114
|
+
iri: newId,
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
// Replace the nested schema with its new id.
|
|
118
|
+
dt.shape = newId;
|
|
119
|
+
|
|
120
|
+
schema = { ...schema, ...flattened };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Flatten / Recurse
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return schema;
|
|
128
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Schema } from "@ng-org/shex-orm";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* =============================================================================
|
|
5
|
+
* <%- fileName %>Schema: Schema for <%- fileName %>
|
|
6
|
+
* =============================================================================
|
|
7
|
+
*/
|
|
8
|
+
export const <%- fileName %>Schema: Schema = <%- compactSchema %>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ShapeType } from "@ng-org/shex-orm";
|
|
2
|
+
import { <%- fileName %>Schema } from "./<%- fileName %>.schema";
|
|
3
|
+
import type {
|
|
4
|
+
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%>
|
|
5
|
+
<%- typing.dts.name %>,
|
|
6
|
+
<% } }); -%>} from "./<%- fileName %>.typings";
|
|
7
|
+
|
|
8
|
+
// ShapeTypes for <%- fileName %>
|
|
9
|
+
<% typings.forEach((typing)=> { if (!/Id$/.test(typing.dts.name)) { -%>
|
|
10
|
+
export const <%- typing.dts.name %>ShapeType: ShapeType<<%- typing.dts.name %>> = {
|
|
11
|
+
schema: <%- fileName %>Schema,
|
|
12
|
+
shape: "<%- typing.dts.shapeId %>",
|
|
13
|
+
};
|
|
14
|
+
<% } }); -%>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type IRI = string;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* =============================================================================
|
|
5
|
+
* Typescript Typings for <%- fileName %>
|
|
6
|
+
* =============================================================================
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
<% typings.forEach((typing)=> { -%>
|
|
10
|
+
/**
|
|
11
|
+
* <%- typing.dts.name %> Type
|
|
12
|
+
*/
|
|
13
|
+
export <%- typing.typingString -%>
|
|
14
|
+
<% }); -%>
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// Copyright (c) 2025 Laurin Weger, Par le Peuple, NextGraph.org developers
|
|
2
|
+
// All rights reserved.
|
|
3
|
+
// Licensed under the Apache License, Version 2.0
|
|
4
|
+
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
|
5
|
+
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
|
6
|
+
// at your option. All files in the project carrying such
|
|
7
|
+
// notice may not be copied, modified, or distributed except
|
|
8
|
+
// according to those terms.
|
|
9
|
+
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
10
|
+
|
|
11
|
+
import ShexJTraverser from "@ldo/traverser-shexj";
|
|
12
|
+
import type { Predicate, DataType, Shape } from "../../types.ts";
|
|
13
|
+
import type { ObjectLiteral } from "../../ShexJTypes.ts";
|
|
14
|
+
|
|
15
|
+
const rdfDataTypeToBasic = (dataType: string) => {
|
|
16
|
+
switch (dataType) {
|
|
17
|
+
case "http://www.w3.org/2001/XMLSchema#string":
|
|
18
|
+
case "http://www.w3.org/2001/XMLSchema#ENTITIES":
|
|
19
|
+
case "http://www.w3.org/2001/XMLSchema#ENTITY":
|
|
20
|
+
case "http://www.w3.org/2001/XMLSchema#ID":
|
|
21
|
+
case "http://www.w3.org/2001/XMLSchema#IDREF":
|
|
22
|
+
case "http://www.w3.org/2001/XMLSchema#IDREFS":
|
|
23
|
+
case "http://www.w3.org/2001/XMLSchema#language":
|
|
24
|
+
case "http://www.w3.org/2001/XMLSchema#Name":
|
|
25
|
+
case "http://www.w3.org/2001/XMLSchema#NCName":
|
|
26
|
+
case "http://www.w3.org/2001/XMLSchema#NMTOKEN":
|
|
27
|
+
case "http://www.w3.org/2001/XMLSchema#NMTOKENS":
|
|
28
|
+
case "http://www.w3.org/2001/XMLSchema#normalizedString":
|
|
29
|
+
case "http://www.w3.org/2001/XMLSchema#QName":
|
|
30
|
+
case "http://www.w3.org/2001/XMLSchema#token":
|
|
31
|
+
return "string";
|
|
32
|
+
case "http://www.w3.org/2001/XMLSchema#date":
|
|
33
|
+
case "http://www.w3.org/2001/XMLSchema#dateTime":
|
|
34
|
+
case "http://www.w3.org/2001/XMLSchema#duration":
|
|
35
|
+
case "http://www.w3.org/2001/XMLSchema#gDay":
|
|
36
|
+
case "http://www.w3.org/2001/XMLSchema#gMonth":
|
|
37
|
+
case "http://www.w3.org/2001/XMLSchema#gMonthDay":
|
|
38
|
+
case "http://www.w3.org/2001/XMLSchema#gYear":
|
|
39
|
+
case "http://www.w3.org/2001/XMLSchema#gYearMonth":
|
|
40
|
+
case "http://www.w3.org/2001/XMLSchema#time":
|
|
41
|
+
return "string";
|
|
42
|
+
case "http://www.w3.org/2001/XMLSchema#byte":
|
|
43
|
+
case "http://www.w3.org/2001/XMLSchema#decimal":
|
|
44
|
+
case "http://www.w3.org/2001/XMLSchema#double":
|
|
45
|
+
case "http://www.w3.org/2001/XMLSchema#float":
|
|
46
|
+
case "http://www.w3.org/2001/XMLSchema#int":
|
|
47
|
+
case "http://www.w3.org/2001/XMLSchema#integer":
|
|
48
|
+
case "http://www.w3.org/2001/XMLSchema#long":
|
|
49
|
+
case "http://www.w3.org/2001/XMLSchema#negativeInteger":
|
|
50
|
+
case "http://www.w3.org/2001/XMLSchema#nonNegativeInteger":
|
|
51
|
+
case "http://www.w3.org/2001/XMLSchema#nonPositiveInteger":
|
|
52
|
+
case "http://www.w3.org/2001/XMLSchema#positiveInteger":
|
|
53
|
+
case "http://www.w3.org/2001/XMLSchema#short":
|
|
54
|
+
case "http://www.w3.org/2001/XMLSchema#unsignedLong":
|
|
55
|
+
case "http://www.w3.org/2001/XMLSchema#unsignedInt":
|
|
56
|
+
case "http://www.w3.org/2001/XMLSchema#unsignedShort":
|
|
57
|
+
case "http://www.w3.org/2001/XMLSchema#unsignedByte":
|
|
58
|
+
return "number";
|
|
59
|
+
case "http://www.w3.org/2001/XMLSchema#boolean":
|
|
60
|
+
return "boolean";
|
|
61
|
+
case "http://www.w3.org/2001/XMLSchema#hexBinary":
|
|
62
|
+
return "string";
|
|
63
|
+
case "http://www.w3.org/2001/XMLSchema#anyURI":
|
|
64
|
+
return "iri";
|
|
65
|
+
default:
|
|
66
|
+
return "string";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const ShexJSchemaTransformerCompact = ShexJTraverser.createTransformer<
|
|
71
|
+
{
|
|
72
|
+
Schema: { return: Shape[] };
|
|
73
|
+
ShapeDecl: { return: Shape };
|
|
74
|
+
Shape: { return: Shape };
|
|
75
|
+
EachOf: { return: Shape };
|
|
76
|
+
TripleConstraint: { return: Predicate };
|
|
77
|
+
NodeConstraint: { return: DataType };
|
|
78
|
+
ShapeOr: { return: DataType[] };
|
|
79
|
+
ShapeAnd: { return: never };
|
|
80
|
+
ShapeNot: { return: never };
|
|
81
|
+
ShapeExternal: { return: never };
|
|
82
|
+
},
|
|
83
|
+
null
|
|
84
|
+
>({
|
|
85
|
+
Schema: {
|
|
86
|
+
transformer: async (_schema, getTransformedChildren) => {
|
|
87
|
+
const transformedChildren = await getTransformedChildren();
|
|
88
|
+
|
|
89
|
+
return transformedChildren.shapes || [];
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
ShapeDecl: {
|
|
94
|
+
transformer: async (shapeDecl, getTransformedChildren) => {
|
|
95
|
+
const schema = await getTransformedChildren();
|
|
96
|
+
const shape = schema.shapeExpr as Shape;
|
|
97
|
+
|
|
98
|
+
return { ...shape, iri: shapeDecl.id } as Shape;
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
Shape: {
|
|
103
|
+
transformer: async (_shape, getTransformedChildren) => {
|
|
104
|
+
// TODO: We don't handles those
|
|
105
|
+
_shape.closed;
|
|
106
|
+
|
|
107
|
+
const transformedChildren = await getTransformedChildren();
|
|
108
|
+
const compactShape = transformedChildren.expression as Shape;
|
|
109
|
+
|
|
110
|
+
for (const extra of _shape.extra || []) {
|
|
111
|
+
const extraPredicate = compactShape.predicates.find(
|
|
112
|
+
(p) => p.iri === extra
|
|
113
|
+
);
|
|
114
|
+
if (extraPredicate) extraPredicate.extra = true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return compactShape;
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// EachOf contains the `expressions` array of properties (TripleConstraint)
|
|
122
|
+
EachOf: {
|
|
123
|
+
transformer: async (eachOf, getTransformedChildren) => {
|
|
124
|
+
const transformedChildren = await getTransformedChildren();
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
iri: "",
|
|
128
|
+
predicates: transformedChildren.expressions.map(
|
|
129
|
+
// We disregard cases where properties are referenced (strings)
|
|
130
|
+
// or where they consist of Unions or Intersections (not supported).
|
|
131
|
+
(expr) => expr as Predicate
|
|
132
|
+
),
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
TripleConstraint: {
|
|
138
|
+
transformer: async (
|
|
139
|
+
tripleConstraint,
|
|
140
|
+
getTransformedChildren,
|
|
141
|
+
_setReturnPointer
|
|
142
|
+
) => {
|
|
143
|
+
const transformedChildren = await getTransformedChildren();
|
|
144
|
+
|
|
145
|
+
const commonProperties = {
|
|
146
|
+
maxCardinality: tripleConstraint.max ?? 1,
|
|
147
|
+
minCardinality: tripleConstraint.min ?? 1,
|
|
148
|
+
iri: tripleConstraint.predicate,
|
|
149
|
+
// @ts-expect-error The ldo library does not have our modded readablePredicate property.
|
|
150
|
+
readablePredicate: tripleConstraint.readablePredicate,
|
|
151
|
+
} satisfies Partial<Predicate>;
|
|
152
|
+
// Make property based on object type which is either a parsed schema, literal or type.
|
|
153
|
+
if (typeof transformedChildren.valueExpr === "string") {
|
|
154
|
+
// Reference to nested object
|
|
155
|
+
return {
|
|
156
|
+
dataTypes: [
|
|
157
|
+
{
|
|
158
|
+
valType: "shape",
|
|
159
|
+
shape: transformedChildren.valueExpr,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
...commonProperties,
|
|
163
|
+
};
|
|
164
|
+
} else if (
|
|
165
|
+
transformedChildren.valueExpr &&
|
|
166
|
+
(transformedChildren.valueExpr as Shape).predicates
|
|
167
|
+
) {
|
|
168
|
+
// Nested object
|
|
169
|
+
return {
|
|
170
|
+
dataTypes: [
|
|
171
|
+
{
|
|
172
|
+
valType: "shape",
|
|
173
|
+
shape: transformedChildren.valueExpr as Shape,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
...commonProperties,
|
|
177
|
+
};
|
|
178
|
+
} else if (Array.isArray(transformedChildren.valueExpr)) {
|
|
179
|
+
return {
|
|
180
|
+
dataTypes: transformedChildren.valueExpr, // DataType[]
|
|
181
|
+
...commonProperties,
|
|
182
|
+
};
|
|
183
|
+
} else {
|
|
184
|
+
// type or literal
|
|
185
|
+
const nodeConstraint =
|
|
186
|
+
transformedChildren.valueExpr as DataType;
|
|
187
|
+
return {
|
|
188
|
+
dataTypes: [
|
|
189
|
+
{
|
|
190
|
+
valType: nodeConstraint.valType,
|
|
191
|
+
literals: nodeConstraint.literals,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
...commonProperties,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
NodeConstraint: {
|
|
201
|
+
transformer: async (nodeConstraint) => {
|
|
202
|
+
if (nodeConstraint.datatype) {
|
|
203
|
+
return {
|
|
204
|
+
valType: rdfDataTypeToBasic(nodeConstraint.datatype),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (nodeConstraint.nodeKind) {
|
|
208
|
+
// Something reference-like.
|
|
209
|
+
return { valType: "iri" };
|
|
210
|
+
}
|
|
211
|
+
if (nodeConstraint.values) {
|
|
212
|
+
return {
|
|
213
|
+
valType: "literal",
|
|
214
|
+
literals: nodeConstraint.values.map(
|
|
215
|
+
// TODO: We do not convert them to number or boolean or lang tag.
|
|
216
|
+
// And we don't have an annotation of the literal's type.
|
|
217
|
+
(valueRecord) => {
|
|
218
|
+
// If valueRecord is a string (IRIREF), return it directly
|
|
219
|
+
if (typeof valueRecord === "string") {
|
|
220
|
+
return valueRecord;
|
|
221
|
+
}
|
|
222
|
+
// Handle ObjectLiteral (has .value property)
|
|
223
|
+
if ("value" in valueRecord) {
|
|
224
|
+
return valueRecord.value;
|
|
225
|
+
}
|
|
226
|
+
// Handle other types with .id property (if any)
|
|
227
|
+
if ("id" in valueRecord) {
|
|
228
|
+
return (valueRecord as any).id;
|
|
229
|
+
}
|
|
230
|
+
// Handle Language type (has .languageTag)
|
|
231
|
+
if ("languageTag" in valueRecord) {
|
|
232
|
+
return valueRecord.languageTag;
|
|
233
|
+
}
|
|
234
|
+
// Handle stem-based types (IriStem, LiteralStem, LanguageStem)
|
|
235
|
+
if ("stem" in valueRecord) {
|
|
236
|
+
return valueRecord.stem as string;
|
|
237
|
+
}
|
|
238
|
+
// Fallback - should not happen in well-formed ShEx
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Maybe we should throw instead...
|
|
246
|
+
throw {
|
|
247
|
+
error: new Error("Could not parse Node Constraint"),
|
|
248
|
+
nodeConstraint,
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// Transformer from ShapeOr
|
|
254
|
+
ShapeOr: {
|
|
255
|
+
transformer: async (shapeOr, getTransformedChildren) => {
|
|
256
|
+
const { shapeExprs } = await getTransformedChildren();
|
|
257
|
+
// Either a shape IRI, a nested shape or a node CompactSchemaValue (node constraint).
|
|
258
|
+
return (
|
|
259
|
+
Array.isArray(shapeExprs) ? shapeExprs : [shapeExprs]
|
|
260
|
+
) as DataType[];
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// Transformer from ShapeAnd
|
|
265
|
+
ShapeAnd: {
|
|
266
|
+
transformer: async () => {
|
|
267
|
+
throw new Error("ShapeAnd not supported (compact)");
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
// Transformer from ShapeNot - not supported.
|
|
272
|
+
ShapeNot: {
|
|
273
|
+
transformer: async () => {
|
|
274
|
+
throw new Error("ShapeNot not supported (compact)");
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
// Transformer from ShapeExternal - not supported.
|
|
279
|
+
ShapeExternal: {
|
|
280
|
+
transformer: async () => {
|
|
281
|
+
throw new Error("ShapeExternal not supported (compact)");
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
});
|