@ng-org/shex-orm 0.1.2-alpha.3 → 0.1.2-alpha.5
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 +46 -142
- package/dist/schema-converter/__tests__/typingTransformer.test.js +72 -2
- package/dist/schema-converter/transformers/ShexJTypingTransformer.js +1 -1
- package/dist/schema-converter/util/annotateReadablePredicates.d.ts.map +1 -1
- package/dist/schema-converter/util/annotateReadablePredicates.js +90 -61
- package/dist/types.d.ts +12 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +18 -31
- package/src/cli.ts +0 -23
- package/src/index.ts +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Schema Converter SHEX > TypeScript
|
|
2
2
|
|
|
3
|
-
CLI tool to convert SHEX shapes to schemas and TypeScript definitions ("shape types") that can be used for creating ORM objects.
|
|
3
|
+
CLI tool to convert SHEX shapes to schemas and TypeScript definitions ("shape types") that can be used for creating graph ORM objects.
|
|
4
|
+
|
|
5
|
+
## Reference documentation
|
|
6
|
+
|
|
7
|
+
[Reference documentation is available here on docs.nextgraph.org](https://docs.nextgraph.org/en/reference/shex-orm/).
|
|
4
8
|
|
|
5
9
|
## How to Use
|
|
6
10
|
|
|
@@ -16,7 +20,42 @@ Then run
|
|
|
16
20
|
npx rdf-orm build --input ./src/shapes/shex --output ./src/shapes/orm
|
|
17
21
|
```
|
|
18
22
|
|
|
19
|
-
The input directory needs to contain shex files with one or more shape definitions each
|
|
23
|
+
The input directory needs to contain shex files with one or more shape definitions each, for example:
|
|
24
|
+
|
|
25
|
+
```shex
|
|
26
|
+
PREFIX ex: <http://example.org/>
|
|
27
|
+
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
|
28
|
+
|
|
29
|
+
ex:ExpenseShape {
|
|
30
|
+
a [ex:Person] ; # Required type <http://example.org/Person>
|
|
31
|
+
ex:name xsd:string ; # Required string
|
|
32
|
+
ex:email xsd:string * ; # Zero or more strings (set)
|
|
33
|
+
ex:height xsd:float ; # Required number
|
|
34
|
+
ex:age xsd:integer ; # Required integer
|
|
35
|
+
ex:friends IRI * ; # Set of IRIs
|
|
36
|
+
ex:isRecurring xsd:boolean ; # A boolean value
|
|
37
|
+
ex:address @ex:AddressShape ; # A nested object shape.
|
|
38
|
+
ex:paymentStatus [ex:Paid ex:Pending ex:Overdue] ; # Enum
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# In the same or another file...
|
|
42
|
+
ex:AddressShape EXTRA a {
|
|
43
|
+
a [ ex:Address ] ;
|
|
44
|
+
ex:name xsd:string ;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**SHEX Cardinality Reference**
|
|
49
|
+
|
|
50
|
+
| Syntax | Meaning | TypeScript Type |
|
|
51
|
+
| ------------------- | --------------------------- | ------------------------- |
|
|
52
|
+
| `prop xsd:string` | Required, exactly one | `string` |
|
|
53
|
+
| `prop xsd:string ?` | Optional, zero or one | `string \| undefined` |
|
|
54
|
+
| `prop xsd:string *` | Zero or more | `Set<string>` |
|
|
55
|
+
| `prop xsd:string +` | One or more | `Set<string>` (non-empty) |
|
|
56
|
+
| `prop IRI` | Reference to another object | `string` (IRI) |
|
|
57
|
+
| `@ex:PersonShape` | nested object | `Person` |
|
|
58
|
+
|
|
20
59
|
The output directory will contain the typescript files with type definitions and the converted schema.
|
|
21
60
|
|
|
22
61
|
You will then pass the shape type of a shape definition to the ng sdk:
|
|
@@ -26,12 +65,12 @@ import { useShape } from "@ng-org/orm/react";
|
|
|
26
65
|
import { TestObjectShapeType } from "../shapes/orm/testShape.shapeTypes";
|
|
27
66
|
|
|
28
67
|
export function TestComponent() {
|
|
29
|
-
const testObjects = useShape(TestObjectShapeType);
|
|
68
|
+
const testObjects = useShape(TestObjectShapeType, {graphs: ["did:ng:i"]});
|
|
30
69
|
...
|
|
31
70
|
}
|
|
32
71
|
```
|
|
33
72
|
|
|
34
|
-
For an example, see the [
|
|
73
|
+
For an example, see the [expense-tracker-graph example application](https://git.nextgraph.org/NextGraph/expense-tracker-graph).
|
|
35
74
|
|
|
36
75
|
## Generated Output
|
|
37
76
|
|
|
@@ -39,149 +78,14 @@ For each SHEX file, the tool creates three TypeScript files:
|
|
|
39
78
|
|
|
40
79
|
- A schema file like `person.schema.ts`
|
|
41
80
|
- A typings file like `person.typings.ts`
|
|
42
|
-
- A shape type file like `person.shapeTypes.ts` which contains a `ShapeType` that consists of the schema, the type, and the IRI of the main shape
|
|
81
|
+
- A shape type file like `person.shapeTypes.ts` which contains a `ShapeType` that consists of the schema, the type, and the IRI of the main shape. This is what you pass to the ORM.
|
|
43
82
|
|
|
44
83
|
The transformers for converting SHEX to schema and typings files are based on `@ldo/traverser-shexj`.
|
|
45
84
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```ts
|
|
49
|
-
export const PersonShapeType: ShapeType<Person> = {
|
|
50
|
-
schema: personSchema,
|
|
51
|
-
shape: "http://example.org/PersonShape",
|
|
52
|
-
};
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Schema File
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
import type { Schema } from "@ng-org/shex-orm";
|
|
59
|
-
|
|
60
|
-
export const personSchema: Schema = {
|
|
61
|
-
"http://example.org/PersonShape": {
|
|
62
|
-
iri: "http://example.org/PersonShape",
|
|
63
|
-
predicates: [
|
|
64
|
-
{
|
|
65
|
-
dataTypes: [
|
|
66
|
-
{
|
|
67
|
-
valType: "literal",
|
|
68
|
-
literals: ["http://example.org/Person"],
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
maxCardinality: -1,
|
|
72
|
-
minCardinality: 1,
|
|
73
|
-
iri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
|
|
74
|
-
readablePredicate: "@type",
|
|
75
|
-
// `extra` here allows additional type values along with `http://example.org/Person`.
|
|
76
|
-
extra: true,
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
dataTypes: [{ valType: "string" }],
|
|
80
|
-
maxCardinality: 1,
|
|
81
|
-
minCardinality: 1,
|
|
82
|
-
iri: "http://example.org/name",
|
|
83
|
-
readablePredicate: "name",
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
dataTypes: [{ valType: "string" }],
|
|
87
|
-
maxCardinality: -1,
|
|
88
|
-
minCardinality: 0,
|
|
89
|
-
iri: "http://example.org/email",
|
|
90
|
-
readablePredicate: "email",
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
dataTypes: [
|
|
94
|
-
{
|
|
95
|
-
valType: "shape",
|
|
96
|
-
shape: "http://example.org/PersonShape||http://example.org/address",
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
maxCardinality: 1,
|
|
100
|
-
minCardinality: 0,
|
|
101
|
-
iri: "http://example.org/address",
|
|
102
|
-
readablePredicate: "address",
|
|
103
|
-
// `extra` here enables that if multiple children are present but only one is valid, the shape is still considered valid.
|
|
104
|
-
extra: true,
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
},
|
|
108
|
-
"http://example.org/PersonShape||http://example.org/address": {
|
|
109
|
-
iri: "http://example.org/PersonShape||http://example.org/address",
|
|
110
|
-
predicates: [
|
|
111
|
-
{
|
|
112
|
-
dataTypes: [{ valType: "string" }],
|
|
113
|
-
maxCardinality: 1,
|
|
114
|
-
minCardinality: 1,
|
|
115
|
-
iri: "http://example.org/city",
|
|
116
|
-
readablePredicate: "city",
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
#### Readable Predicate Names
|
|
124
|
-
|
|
125
|
-
The `readablePredicate` field is automatically generated from the predicate IRI and becomes the property name in the TypeScript interface.
|
|
126
|
-
|
|
127
|
-
**Generation Rules:**
|
|
128
|
-
|
|
129
|
-
1. **Special case**: `rdf:type` (`http://www.w3.org/1999/02/22-rdf-syntax-ns#type`) always becomes `@type`
|
|
130
|
-
|
|
131
|
-
2. **No conflicts**: If the last segment of the IRI is unique within the shape, it's used as-is:
|
|
132
|
-
- `http://example.org/name` → `name`
|
|
133
|
-
- `http://schema.org/email` → `email`
|
|
134
|
-
|
|
135
|
-
3. **Conflict resolution**: When multiple predicates in the same shape share the same last segment (local name), **all** predicates in that collision group are renamed using prefixes:
|
|
136
|
-
- The algorithm walks backward through IRI segments (right to left)
|
|
137
|
-
- For each predicate, it tries `{prefix}_{localName}` combinations until finding an unused name
|
|
138
|
-
- Example: Both `http://foaf.org/name` and `http://schema.org/name` would become `foaf_name` and `schema_name`
|
|
139
|
-
|
|
140
|
-
4. **Fallback**: If prefix combinations are exhausted, a composite name is generated from all IRI segments (excluding protocol) with incrementing numbers for uniqueness:
|
|
141
|
-
- Pattern: `{composite}_{localName}` or `{composite}_{localName}_1`, `{composite}_{localName}_2`, etc.
|
|
142
|
-
|
|
143
|
-
**Character sanitization**: Special characters (except dots and dashes) are replaced with underscores to ensure valid JavaScript identifiers.
|
|
144
|
-
|
|
145
|
-
**Note**: You can **manually edit** the `readablePredicate` values in the generated schema files if you prefer different property names. The schema acts as the single source of truth for property naming.
|
|
146
|
-
|
|
147
|
-
### Typings File
|
|
148
|
-
|
|
149
|
-
```ts
|
|
150
|
-
export type IRI = string;
|
|
151
|
-
|
|
152
|
-
export interface Person {
|
|
153
|
-
readonly "@id": IRI;
|
|
154
|
-
readonly "@graph": IRI;
|
|
155
|
-
/**
|
|
156
|
-
* Original IRI: http://www.w3.org/1999/02/22-rdf-syntax-ns#type
|
|
157
|
-
*/
|
|
158
|
-
"@type": "http://example.org/Person";
|
|
159
|
-
/**
|
|
160
|
-
* Original IRI: http://example.org/name
|
|
161
|
-
*/
|
|
162
|
-
name: string;
|
|
163
|
-
/**
|
|
164
|
-
* Original IRI: http://example.org/email
|
|
165
|
-
*/
|
|
166
|
-
email?: Set<string>;
|
|
167
|
-
/**
|
|
168
|
-
* Original IRI: http://example.org/address
|
|
169
|
-
*/
|
|
170
|
-
address?: {
|
|
171
|
-
readonly "@id": IRI;
|
|
172
|
-
readonly "@graph": IRI;
|
|
173
|
-
/**
|
|
174
|
-
* Original IRI: http://example.org/city
|
|
175
|
-
*/
|
|
176
|
-
city: string;
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
#### Standard Properties
|
|
85
|
+
#### Default Properties
|
|
182
86
|
|
|
183
87
|
- **`@type`**: The RDF type IRI (from `rdf:type`) is always converted to the property name `@type` by default
|
|
184
|
-
- **`@id` and `@graph
|
|
88
|
+
- **`@id` (subject IRI) and `@graph` (graph NURI)**: These properties are automatically added to all typed objects as readonly properties
|
|
185
89
|
|
|
186
90
|
### Cardinality Handling
|
|
187
91
|
|
|
@@ -55,8 +55,69 @@ function buildSchema() {
|
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
type: "ShapeDecl",
|
|
60
|
+
id: "http://example.org/CollisionTest",
|
|
61
|
+
shapeExpr: {
|
|
62
|
+
type: "Shape",
|
|
63
|
+
extra: [TYPE_IRI],
|
|
64
|
+
expression: {
|
|
65
|
+
type: "EachOf",
|
|
66
|
+
expressions: [
|
|
67
|
+
{
|
|
68
|
+
type: "TripleConstraint",
|
|
69
|
+
predicate: "http://example.org/collide1/foo",
|
|
70
|
+
valueExpr: {
|
|
71
|
+
type: "NodeConstraint",
|
|
72
|
+
datatype: "http://www.w3.org/2001/XMLSchema#string",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: "TripleConstraint",
|
|
77
|
+
predicate: "http://example.org/collide2/foo",
|
|
78
|
+
valueExpr: {
|
|
79
|
+
type: "NodeConstraint",
|
|
80
|
+
datatype: "http://www.w3.org/2001/XMLSchema#string",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: "TripleConstraint",
|
|
85
|
+
predicate: "http://example.org/collide3/foo",
|
|
86
|
+
valueExpr: {
|
|
87
|
+
type: "NodeConstraint",
|
|
88
|
+
datatype: "http://www.w3.org/2001/XMLSchema#string",
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: "TripleConstraint",
|
|
93
|
+
predicate: "http://example.org2/collide3/foo",
|
|
94
|
+
valueExpr: {
|
|
95
|
+
type: "NodeConstraint",
|
|
96
|
+
datatype: "http://www.w3.org/2001/XMLSchema#string",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
type: "TripleConstraint",
|
|
101
|
+
predicate: "http://example.org:collide4/foo",
|
|
102
|
+
valueExpr: {
|
|
103
|
+
type: "NodeConstraint",
|
|
104
|
+
datatype: "http://www.w3.org/2001/XMLSchema#string",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: "TripleConstraint",
|
|
109
|
+
predicate: "http://example.org/collide4/foo",
|
|
110
|
+
valueExpr: {
|
|
111
|
+
type: "NodeConstraint",
|
|
112
|
+
datatype: "http://www.w3.org/2001/XMLSchema#string",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
58
119
|
],
|
|
59
|
-
};
|
|
120
|
+
}; // satisfies Schema;
|
|
60
121
|
}
|
|
61
122
|
async function buildTypingsText() {
|
|
62
123
|
const schema = buildSchema();
|
|
@@ -71,6 +132,15 @@ describe("ShexJTypingTransformer", () => {
|
|
|
71
132
|
});
|
|
72
133
|
it("treats EXTRA rdf:type predicates as plural", async () => {
|
|
73
134
|
const typings = await buildTypingsText();
|
|
74
|
-
expect(typings).toMatch(
|
|
135
|
+
expect(typings).toMatch(/\"@type\": Set<\"http:\/\/example\.org\/ExpenseCategory\" | IRI & {}>;/);
|
|
136
|
+
});
|
|
137
|
+
it("handles property name collisions", async () => {
|
|
138
|
+
const typings = await buildTypingsText();
|
|
139
|
+
expect(typings).toMatch(/ collide1_foo: string/);
|
|
140
|
+
expect(typings).toMatch(/ collide2_foo: string/);
|
|
141
|
+
expect(typings).toMatch(/ org_collide3_foo: string/);
|
|
142
|
+
expect(typings).toMatch(/ org2_collide3_foo: string/);
|
|
143
|
+
expect(typings).toMatch(/ "0__http_example_org_collide4_foo"\: string/);
|
|
144
|
+
expect(typings).toMatch(/ "1__http_example_org_collide4_foo"\: string/);
|
|
75
145
|
});
|
|
76
146
|
});
|
|
@@ -293,7 +293,7 @@ export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer({
|
|
|
293
293
|
const propsToAdd = [];
|
|
294
294
|
if (!hasGraph) {
|
|
295
295
|
const graphProp = dom.create.property("@graph", dom.create.namedTypeReference("IRI"), dom.DeclarationFlags.ReadOnly);
|
|
296
|
-
graphProp.jsDocComment = "The graph
|
|
296
|
+
graphProp.jsDocComment = "The graph NURI.";
|
|
297
297
|
propsToAdd.push(graphProp);
|
|
298
298
|
}
|
|
299
299
|
if (!hasId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"annotateReadablePredicates.d.ts","sourceRoot":"","sources":["../../../src/schema-converter/util/annotateReadablePredicates.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,MAAM,EAA8C,MAAM,OAAO,CAAC;AAUhF;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"annotateReadablePredicates.d.ts","sourceRoot":"","sources":["../../../src/schema-converter/util/annotateReadablePredicates.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,MAAM,EAA8C,MAAM,OAAO,CAAC;AAUhF;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CA0MvE"}
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
// according to those terms.
|
|
9
9
|
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
10
10
|
// Split IRI by colon, slash and hash; drop empties
|
|
11
|
-
const splitIriTokens = (iri) => iri.split(/[
|
|
11
|
+
const splitIriTokens = (iri) => iri.split(/[:/#.]+/).filter(Boolean);
|
|
12
12
|
// Keep dots and dashes (so 0.1 stays as 0.1) but sanitize everything else
|
|
13
|
-
const sanitize = (s) => s.replace(/[^\w
|
|
13
|
+
const sanitize = (s) => s.replace(/[^\w.\-@]/g, "_");
|
|
14
14
|
/**
|
|
15
15
|
* Annotate EachOf-level TripleConstraints with a collision-free readablePredicate.
|
|
16
16
|
* Rule: for any group that shares the same local token, rename all members using
|
|
@@ -23,73 +23,102 @@ export default function annotateReadablePredicates(schema) {
|
|
|
23
23
|
eachOf.type !== "EachOf" ||
|
|
24
24
|
!Array.isArray(eachOf.expressions))
|
|
25
25
|
return;
|
|
26
|
-
const
|
|
26
|
+
const tripleConstraints = eachOf.expressions.filter((e) => typeof e === "object" &&
|
|
27
27
|
e !== null &&
|
|
28
28
|
e.type === "TripleConstraint");
|
|
29
|
-
if (
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
if (tripleConstraints.length > 0) {
|
|
30
|
+
// Add a triple constraint (tc) to parent tree node.
|
|
31
|
+
// The tree branches on name collisions.
|
|
32
|
+
const addToPreds = (depth, parent, iriElements, tc) => {
|
|
33
|
+
// Get the name of the next IRI part (e.g. the foaf from foaf_name).
|
|
34
|
+
// It can be that we are out of bounds. In that case we use "".
|
|
35
|
+
// That way the final name remains the same, unless we still collisions.
|
|
36
|
+
// In that case, we enumerate.
|
|
37
|
+
const key = iriElements[depth] ?? "";
|
|
38
|
+
// Case no collision: Add triple constraint as leaf.
|
|
39
|
+
if (!parent.children[key]) {
|
|
40
|
+
parent.children[key] = {
|
|
41
|
+
leaf: { tc, iriElements },
|
|
42
|
+
children: {},
|
|
43
|
+
};
|
|
39
44
|
}
|
|
40
|
-
else {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
continue;
|
|
56
|
-
const used = new Set();
|
|
57
|
-
const local = splitIriTokens(groupMembers[0].predicate).slice(-1)[0] ??
|
|
58
|
-
"";
|
|
59
|
-
for (const tc of groupMembers) {
|
|
60
|
-
const tokens = splitIriTokens(tc.predicate);
|
|
61
|
-
let localIdx = tokens.lastIndexOf(local);
|
|
62
|
-
if (localIdx === -1)
|
|
63
|
-
localIdx = Math.max(tokens.length - 1, 0);
|
|
64
|
-
let prefixIdx = localIdx - 1;
|
|
65
|
-
let assigned = false;
|
|
66
|
-
while (prefixIdx >= 0) {
|
|
67
|
-
const cand = `${sanitize(tokens[prefixIdx])}_${sanitize(tokens[localIdx])}`;
|
|
68
|
-
if (!used.has(cand)) {
|
|
69
|
-
tc.readablePredicate = cand;
|
|
70
|
-
used.add(cand);
|
|
71
|
-
assigned = true;
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
prefixIdx -= 1;
|
|
45
|
+
else if (key === "") {
|
|
46
|
+
// Case out of bounds but not the only one
|
|
47
|
+
// Add a counter prefix
|
|
48
|
+
const node = parent.children[key];
|
|
49
|
+
if (node.leaf) {
|
|
50
|
+
// If this child has a leaf, that means it didn't have children before.
|
|
51
|
+
// Add a __counter prefix__ now.
|
|
52
|
+
node.children = {
|
|
53
|
+
["0"]: {
|
|
54
|
+
leaf: node.leaf,
|
|
55
|
+
children: {},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
// Remove moved leaf from old node.
|
|
59
|
+
node.leaf = false;
|
|
75
60
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
61
|
+
// Add counter to iriElements -> will be picked up by recursion call.
|
|
62
|
+
iriElements[depth + 1] = Object.keys(node.children).length.toString();
|
|
63
|
+
addToPreds(depth + 1, node, iriElements, tc);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// Case collision: create a new child
|
|
67
|
+
const node = parent.children[key];
|
|
68
|
+
if (node.leaf) {
|
|
69
|
+
// If this child has a leaf, that means it didn't have children before.
|
|
70
|
+
// Move the leaf to the new children.
|
|
71
|
+
const childKey = node.leaf.iriElements[depth + 1] ?? "";
|
|
72
|
+
node.children = {
|
|
73
|
+
[childKey]: {
|
|
74
|
+
leaf: node.leaf,
|
|
75
|
+
children: {},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
// Remove moved leaf from old node.
|
|
79
|
+
node.leaf = false;
|
|
88
80
|
}
|
|
81
|
+
addToPreds(depth + 1, node, iriElements, tc);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
// Root structure to keep names for triple constraints.
|
|
85
|
+
// Keys are the readable names of the predicates
|
|
86
|
+
const rootPredTree = {
|
|
87
|
+
leaf: false,
|
|
88
|
+
children: {},
|
|
89
|
+
};
|
|
90
|
+
// Add all triple constraints to root tree.
|
|
91
|
+
for (const tripleConstraint of tripleConstraints) {
|
|
92
|
+
const iri = tripleConstraint.predicate;
|
|
93
|
+
if (iri === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type") {
|
|
94
|
+
// Special case: convert type predicate to @type
|
|
95
|
+
addToPreds(0, rootPredTree, ["@type"], tripleConstraint);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
addToPreds(0, rootPredTree,
|
|
99
|
+
// Divide IRI in sanitized parts, start from end (property name)
|
|
100
|
+
splitIriTokens(iri).map(sanitize).reverse(), tripleConstraint);
|
|
89
101
|
}
|
|
90
102
|
}
|
|
103
|
+
// Traverse tree and annotate
|
|
104
|
+
const annotatePreds = (parentTree, accumulatedName) => {
|
|
105
|
+
// If we reached the leaf, annotate with name
|
|
106
|
+
if (parentTree?.leaf) {
|
|
107
|
+
parentTree.leaf.tc.readablePredicate = accumulatedName;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Recurse for all children.
|
|
111
|
+
for (const key of Object.keys(parentTree.children ?? {})) {
|
|
112
|
+
const name = accumulatedName === ""
|
|
113
|
+
? key // Just use name
|
|
114
|
+
: `${key}_${accumulatedName}`; // Make composite.
|
|
115
|
+
// Annotate children
|
|
116
|
+
annotatePreds(parentTree.children[key], name);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
annotatePreds(rootPredTree, "");
|
|
91
120
|
// Recurse into nested valueExpr shapes of each TC
|
|
92
|
-
for (const tc of
|
|
121
|
+
for (const tc of tripleConstraints) {
|
|
93
122
|
const ve = tc.valueExpr;
|
|
94
123
|
if (ve && typeof ve === "object") {
|
|
95
124
|
const t = ve.type;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The ORM shape type generated from a [SHEX](https://shex.io/) schema with
|
|
3
|
+
* `rdf-orm build --input ./path/to/shex-files --output ./path/to/shape-types`
|
|
3
4
|
*/
|
|
4
5
|
export interface ShapeType<T extends BaseType> {
|
|
6
|
+
/** The schema object of the shape. */
|
|
5
7
|
schema: Schema;
|
|
8
|
+
/** The ID (IRI) of the shape. */
|
|
6
9
|
shape: string;
|
|
7
10
|
}
|
|
11
|
+
/** The base type that all generated objects inherit from. */
|
|
8
12
|
export interface BaseType extends Record<string, any> {
|
|
13
|
+
/** The IRI of the subject. */
|
|
9
14
|
"@id": string;
|
|
10
15
|
}
|
|
11
16
|
export type Schema = {
|
|
12
17
|
[id: string]: Shape;
|
|
13
18
|
};
|
|
19
|
+
/** Shape of an object. */
|
|
14
20
|
export interface Shape {
|
|
21
|
+
/** The ID (IRI) of the shape. */
|
|
15
22
|
iri: string;
|
|
23
|
+
/** The predicates (properties) of the shape. */
|
|
16
24
|
predicates: Predicate[];
|
|
17
25
|
}
|
|
26
|
+
/** An allowed data type or literal. */
|
|
18
27
|
export type DataType = {
|
|
19
28
|
/** The required literal value(s). Additional values are allowed, if `extra` is true. */
|
|
20
29
|
literals?: number[] | string[] | boolean;
|
|
@@ -23,6 +32,7 @@ export type DataType = {
|
|
|
23
32
|
/** The type of object value for a triple constraint. */
|
|
24
33
|
valType: "number" | "string" | "boolean" | "iri" | "shape";
|
|
25
34
|
};
|
|
35
|
+
/** The schema of a property. */
|
|
26
36
|
export interface Predicate {
|
|
27
37
|
/** Allowed type of object. If more than one is present, either of them is allowed. */
|
|
28
38
|
dataTypes: DataType[];
|
|
@@ -32,7 +42,7 @@ export interface Predicate {
|
|
|
32
42
|
readablePredicate: string;
|
|
33
43
|
/** Maximum allowed number of values. `-1` means infinite. */
|
|
34
44
|
maxCardinality: number;
|
|
35
|
-
/** Minimum required number of values */
|
|
45
|
+
/** Minimum required number of values. */
|
|
36
46
|
minCardinality: number;
|
|
37
47
|
/** If other (additional) values are permitted. Useful for literals. */
|
|
38
48
|
extra?: boolean;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAUA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,QAAQ;IACzC,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,QAAS,SAAQ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IACjD,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CAIjB;AAED,MAAM,MAAM,MAAM,GAAG;IACjB,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,CAAC;CACvB,CAAC;AAEF,0BAA0B;AAC1B,MAAM,WAAW,KAAK;IAClB,iCAAiC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,UAAU,EAAE,SAAS,EAAE,CAAC;CAC3B;AAED,uCAAuC;AACvC,MAAM,MAAM,QAAQ,GAAG;IACnB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IACzC,qGAAqG;IACrG,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,wDAAwD;IACxD,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;CAC9D,CAAC;AAEF,gCAAgC;AAChC,MAAM,WAAW,SAAS;IACtB,sFAAsF;IACtF,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB"}
|
package/package.json
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-org/shex-orm",
|
|
3
|
-
"version": "0.1.2-alpha.
|
|
3
|
+
"version": "0.1.2-alpha.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"rdf-orm": "./
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "pnpm run build:ts",
|
|
12
|
-
"build:ts": "rm -rf dist && tsc && pnpm run update-permission && pnpm copy-templates",
|
|
13
|
-
"copy-templates": "mkdir -p dist && rsync -qav --include=\"*/\" --include=\"*.ejs\" --exclude=\"*\" src/ dist/",
|
|
14
|
-
"update-permission": "chmod +x ./dist/cli.js",
|
|
15
|
-
"test": "vitest run",
|
|
16
|
-
"prepublishOnly": "pnpm run build",
|
|
17
|
-
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern",
|
|
18
|
-
"test:init": "rm -rf ./example-init && cp -R ./example-init-placeholder ./example-init && cd ./example-init && ../dist/index.js init",
|
|
19
|
-
"test:create": "rm -rf ./example-create && ./dist/index.js create ./example-create"
|
|
8
|
+
"rdf-orm": "./dist/cli.js"
|
|
20
9
|
},
|
|
21
10
|
"authors": [
|
|
22
11
|
"Laurin Weger",
|
|
@@ -28,39 +17,37 @@
|
|
|
28
17
|
"@types/ejs": "^3.1.1",
|
|
29
18
|
"@types/fs-extra": "^9.0.13",
|
|
30
19
|
"@types/jsonld": "^1.5.15",
|
|
31
|
-
"@types/prompts": "^2.4.9",
|
|
32
20
|
"@types/shexj": "^2.1.4",
|
|
33
21
|
"typescript": "^5.9.2",
|
|
34
22
|
"vitest": "^3.2.4"
|
|
35
23
|
},
|
|
36
24
|
"dependencies": {
|
|
37
|
-
"@jeswr/shacl2shex": "^1.1.0",
|
|
38
25
|
"@ldo/traverser-shexj": "1.0.0-alpha.28",
|
|
39
26
|
"@ldo/type-traverser": "1.0.0-alpha.28",
|
|
40
27
|
"@shexjs/parser": "^1.0.0-alpha.24",
|
|
41
|
-
"child-process-promise": "^2.2.1",
|
|
42
28
|
"commander": "^14.0.1",
|
|
43
29
|
"dts-dom": "~3.6.0",
|
|
44
30
|
"ejs": "^3.1.8",
|
|
45
31
|
"fs-extra": "^10.1.0",
|
|
46
32
|
"jsonld2graphobject": "^0.0.5",
|
|
47
33
|
"loading-cli": "^1.1.0",
|
|
48
|
-
"prettier": "^3.0.3"
|
|
49
|
-
"prompts": "^2.4.2",
|
|
50
|
-
"rdf-dereference-store": "^1.4.0",
|
|
51
|
-
"rdf-namespaces": "^1.13.1",
|
|
52
|
-
"ts-morph": "^24.0.0",
|
|
53
|
-
"type-fest": "^2.19.0"
|
|
34
|
+
"prettier": "^3.0.3"
|
|
54
35
|
},
|
|
55
36
|
"files": [
|
|
56
37
|
"dist"
|
|
57
38
|
],
|
|
58
39
|
"publishConfig": {
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "pnpm run build:ts",
|
|
44
|
+
"build:ts": "rm -rf dist && tsc && pnpm run update-permission && pnpm copy-templates",
|
|
45
|
+
"copy-templates": "mkdir -p dist && rsync -qav --include=\"*/\" --include=\"*.ejs\" --exclude=\"*\" src/ dist/",
|
|
46
|
+
"update-permission": "chmod +x ./dist/cli.js",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern",
|
|
49
|
+
"test:init": "rm -rf ./example-init && cp -R ./example-init-placeholder ./example-init && cd ./example-init && ../dist/index.js init",
|
|
50
|
+
"test:create": "rm -rf ./example-create && ./dist/index.js create ./example-create"
|
|
51
|
+
},
|
|
52
|
+
"types": "./dist/index.d.ts"
|
|
53
|
+
}
|
package/src/cli.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
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
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./types.ts";
|