@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
|
@@ -0,0 +1,807 @@
|
|
|
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
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
13
|
+
|
|
14
|
+
import ShexJTraverser from "@ldo/traverser-shexj";
|
|
15
|
+
import type { Annotation } from "shexj";
|
|
16
|
+
import * as dom from "dts-dom";
|
|
17
|
+
import type { InterfaceDeclaration } from "dts-dom";
|
|
18
|
+
|
|
19
|
+
export interface ShapeInterfaceDeclaration extends InterfaceDeclaration {
|
|
20
|
+
shapeId?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Collected enum alias names (e.g., AuthenticatedAgentId) to emit at end
|
|
24
|
+
export const additionalCompactEnumAliases = new Set<string>();
|
|
25
|
+
|
|
26
|
+
export interface CompactTransformerContext {
|
|
27
|
+
getNameFromIri: (iri: string, rdfType?: string) => string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function commentFromAnnotations(
|
|
31
|
+
annotations?: Annotation[]
|
|
32
|
+
): string | undefined {
|
|
33
|
+
const commentAnnotationObject = annotations?.find(
|
|
34
|
+
(annotation) =>
|
|
35
|
+
annotation.predicate ===
|
|
36
|
+
"http://www.w3.org/2000/01/rdf-schema#comment"
|
|
37
|
+
)?.object;
|
|
38
|
+
if (typeof commentAnnotationObject === "string")
|
|
39
|
+
return commentAnnotationObject;
|
|
40
|
+
return commentAnnotationObject?.value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function toCamelCase(text: string) {
|
|
44
|
+
return text
|
|
45
|
+
.replace(/([-_ ]){1,}/g, " ")
|
|
46
|
+
.split(/[-_ ]/)
|
|
47
|
+
.reduce((cur, acc) => {
|
|
48
|
+
return cur + acc[0].toUpperCase() + acc.substring(1);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Name functions
|
|
54
|
+
*/
|
|
55
|
+
export function iriToName(iri: string): string {
|
|
56
|
+
try {
|
|
57
|
+
const url = new URL(iri);
|
|
58
|
+
let name: string;
|
|
59
|
+
if (url.hash) {
|
|
60
|
+
name = url.hash.slice(1);
|
|
61
|
+
} else {
|
|
62
|
+
const splitPathname = url.pathname.split("/");
|
|
63
|
+
name = splitPathname[splitPathname.length - 1];
|
|
64
|
+
}
|
|
65
|
+
return name.replace(/(?<!^)Shape$/, "");
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return iri;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function nameFromAnnotationOrId(obj: {
|
|
72
|
+
id?: string;
|
|
73
|
+
annotations?: Annotation[];
|
|
74
|
+
}): string | undefined {
|
|
75
|
+
const labelAnnotationObject = obj.annotations?.find(
|
|
76
|
+
(annotation) =>
|
|
77
|
+
annotation.predicate ===
|
|
78
|
+
"http://www.w3.org/2000/01/rdf-schema#label"
|
|
79
|
+
)?.object;
|
|
80
|
+
if (labelAnnotationObject && typeof labelAnnotationObject === "string") {
|
|
81
|
+
return toCamelCase(iriToName(labelAnnotationObject));
|
|
82
|
+
} else if (
|
|
83
|
+
labelAnnotationObject &&
|
|
84
|
+
typeof labelAnnotationObject !== "string"
|
|
85
|
+
) {
|
|
86
|
+
return toCamelCase(labelAnnotationObject.value);
|
|
87
|
+
} else if (obj.id) {
|
|
88
|
+
return toCamelCase(iriToName(obj.id));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Helper: classify a dom.Type into categories we care about.
|
|
93
|
+
function isObjectLike(t: dom.Type): boolean {
|
|
94
|
+
return (
|
|
95
|
+
(t as dom.ObjectType).kind === "object" ||
|
|
96
|
+
(t as dom.InterfaceDeclaration).kind === "interface"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isPrimitiveLike(t: dom.Type): boolean {
|
|
101
|
+
const kind = (t as any)?.kind;
|
|
102
|
+
if (kind === "name") return true; // named references and intrinsic tokens
|
|
103
|
+
if (kind === "union") {
|
|
104
|
+
return (t as dom.UnionType).members.every(isPrimitiveLike);
|
|
105
|
+
}
|
|
106
|
+
if (kind === "type-parameter") return true;
|
|
107
|
+
// Fallback: treat scalar intrinsic tokens as primitive
|
|
108
|
+
const intrinsicKinds = new Set([
|
|
109
|
+
"string",
|
|
110
|
+
"number",
|
|
111
|
+
"boolean",
|
|
112
|
+
"undefined",
|
|
113
|
+
]);
|
|
114
|
+
return intrinsicKinds.has(kind || "");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Small helpers for unions and alias naming
|
|
118
|
+
function isUnionType(t: dom.Type): t is dom.UnionType {
|
|
119
|
+
return (t as any)?.kind === "union";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function unionOf(types: dom.Type[]): dom.Type {
|
|
123
|
+
const flat: dom.Type[] = [];
|
|
124
|
+
const collect = (tt: dom.Type) => {
|
|
125
|
+
if (isUnionType(tt)) tt.members.forEach(collect);
|
|
126
|
+
else flat.push(tt);
|
|
127
|
+
};
|
|
128
|
+
types.forEach(collect);
|
|
129
|
+
const seen = new Set<string>();
|
|
130
|
+
const unique: dom.Type[] = [];
|
|
131
|
+
flat.forEach((m) => {
|
|
132
|
+
const key =
|
|
133
|
+
(m as any).name ||
|
|
134
|
+
(m as any).value ||
|
|
135
|
+
(m as any).kind + JSON.stringify(m);
|
|
136
|
+
if (!seen.has(key)) {
|
|
137
|
+
seen.add(key);
|
|
138
|
+
unique.push(m);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
if (unique.length === 0) return dom.type.any as unknown as dom.Type;
|
|
142
|
+
if (unique.length === 1) return unique[0];
|
|
143
|
+
return dom.create.union(unique);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function setOf(inner: dom.Type): dom.NamedTypeReference {
|
|
147
|
+
return {
|
|
148
|
+
kind: "name",
|
|
149
|
+
name: "Set",
|
|
150
|
+
typeArguments: [inner],
|
|
151
|
+
} as any;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function recordOf(key: dom.Type, value: dom.Type): dom.NamedTypeReference {
|
|
155
|
+
return {
|
|
156
|
+
kind: "name",
|
|
157
|
+
name: "Record",
|
|
158
|
+
typeArguments: [key, value],
|
|
159
|
+
} as any;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
type ValueSetLiteralCandidate = {
|
|
163
|
+
value?: string;
|
|
164
|
+
id?: string;
|
|
165
|
+
languageTag?: string;
|
|
166
|
+
stem?: string;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
function literalFromValueSetValue(value: unknown): string | undefined {
|
|
170
|
+
if (typeof value === "string") return value;
|
|
171
|
+
if (value && typeof value === "object") {
|
|
172
|
+
const candidate = value as ValueSetLiteralCandidate;
|
|
173
|
+
if (typeof candidate.value === "string") return candidate.value;
|
|
174
|
+
if (typeof candidate.id === "string") return candidate.id;
|
|
175
|
+
if (typeof candidate.languageTag === "string")
|
|
176
|
+
return candidate.languageTag;
|
|
177
|
+
if (typeof candidate.stem === "string") return candidate.stem;
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const extraTripleConstraints = new WeakSet<object>();
|
|
183
|
+
|
|
184
|
+
function tagExtraPredicatesFromExpression(
|
|
185
|
+
expression: any,
|
|
186
|
+
extras: Set<string>
|
|
187
|
+
): void {
|
|
188
|
+
if (!expression || typeof expression !== "object") return;
|
|
189
|
+
const exprType = expression.type;
|
|
190
|
+
if (exprType === "TripleConstraint") {
|
|
191
|
+
if (extras.has(expression.predicate)) {
|
|
192
|
+
extraTripleConstraints.add(expression);
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (exprType === "EachOf" || exprType === "OneOf") {
|
|
197
|
+
(expression.expressions || []).forEach((child: any) =>
|
|
198
|
+
tagExtraPredicatesFromExpression(child, extras)
|
|
199
|
+
);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (exprType === "Shape") {
|
|
203
|
+
tagExtraPredicatesFromExpression(expression.expression, extras);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Note: aliasing helpers previously used in earlier versions were removed.
|
|
208
|
+
|
|
209
|
+
// Property name collision resolution using predicate IRI mapping
|
|
210
|
+
const predicateIriByProp = new WeakMap<dom.PropertyDeclaration, string>();
|
|
211
|
+
|
|
212
|
+
// Note: collisions are handled by annotateReadablePredicates pre-pass.
|
|
213
|
+
|
|
214
|
+
// Merge duplicate properties without introducing LdSet. If a property appears multiple
|
|
215
|
+
// times (e.g., via EXTENDS or grouped expressions) we:
|
|
216
|
+
// - union the types (flattening existing unions)
|
|
217
|
+
// - if one side is Set<T> and the other is plain U, produce Set<T|U>
|
|
218
|
+
// - if both are Set<A>, Set<B> -> Set<A|B>
|
|
219
|
+
// - preserve optional flag if any occurrence optional
|
|
220
|
+
function dedupeCompactProperties(
|
|
221
|
+
props: dom.PropertyDeclaration[]
|
|
222
|
+
): dom.PropertyDeclaration[] {
|
|
223
|
+
const isSetRef = (t: dom.Type): t is dom.NamedTypeReference =>
|
|
224
|
+
(t as any).kind === "name" && (t as any).name === "Set";
|
|
225
|
+
const getSetInner = (t: dom.Type): dom.Type =>
|
|
226
|
+
isSetRef(t) ? (t as any).typeArguments[0] : t;
|
|
227
|
+
|
|
228
|
+
// Group by composite key (name + predicate IRI)
|
|
229
|
+
const groups = new Map<string, dom.PropertyDeclaration[]>();
|
|
230
|
+
for (const p of props) {
|
|
231
|
+
const pred = predicateIriByProp.get(p) || "";
|
|
232
|
+
const key = `${p.name}\u0000${pred}`;
|
|
233
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
234
|
+
groups.get(key)!.push(p);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const merged: dom.PropertyDeclaration[] = [];
|
|
238
|
+
for (const [, group] of groups) {
|
|
239
|
+
if (group.length === 1) {
|
|
240
|
+
merged.push(group[0]);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
let acc = group[0];
|
|
244
|
+
for (let i = 1; i < group.length; i++) {
|
|
245
|
+
const next = group[i];
|
|
246
|
+
const accSet = isSetRef(acc.type);
|
|
247
|
+
const nextSet = isSetRef(next.type);
|
|
248
|
+
let mergedType: dom.Type;
|
|
249
|
+
if (accSet && nextSet) {
|
|
250
|
+
mergedType = setOf(
|
|
251
|
+
unionOf([getSetInner(acc.type), getSetInner(next.type)])
|
|
252
|
+
);
|
|
253
|
+
} else if (accSet && !nextSet) {
|
|
254
|
+
mergedType = setOf(unionOf([getSetInner(acc.type), next.type]));
|
|
255
|
+
} else if (!accSet && nextSet) {
|
|
256
|
+
mergedType = setOf(unionOf([acc.type, getSetInner(next.type)]));
|
|
257
|
+
} else {
|
|
258
|
+
mergedType = unionOf([acc.type, next.type]);
|
|
259
|
+
}
|
|
260
|
+
const optional =
|
|
261
|
+
acc.flags === dom.DeclarationFlags.Optional ||
|
|
262
|
+
next.flags === dom.DeclarationFlags.Optional
|
|
263
|
+
? dom.DeclarationFlags.Optional
|
|
264
|
+
: dom.DeclarationFlags.None;
|
|
265
|
+
const mergedProp = dom.create.property(
|
|
266
|
+
acc.name,
|
|
267
|
+
mergedType,
|
|
268
|
+
optional
|
|
269
|
+
);
|
|
270
|
+
mergedProp.jsDocComment =
|
|
271
|
+
acc.jsDocComment && next.jsDocComment
|
|
272
|
+
? `${acc.jsDocComment} | ${next.jsDocComment}`
|
|
273
|
+
: acc.jsDocComment || next.jsDocComment;
|
|
274
|
+
const pred =
|
|
275
|
+
predicateIriByProp.get(acc) || predicateIriByProp.get(next);
|
|
276
|
+
if (pred) predicateIriByProp.set(mergedProp, pred);
|
|
277
|
+
acc = mergedProp;
|
|
278
|
+
}
|
|
279
|
+
merged.push(acc);
|
|
280
|
+
}
|
|
281
|
+
return merged;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Add `@id` and `@graph` optional readonly props for nested objects */
|
|
285
|
+
function addIdAndGraphProperties(t: dom.Type): dom.Type {
|
|
286
|
+
if ((t as any)?.kind === "object") {
|
|
287
|
+
const members = (t as any).members as
|
|
288
|
+
| dom.PropertyDeclaration[]
|
|
289
|
+
| undefined;
|
|
290
|
+
if (!members) return t;
|
|
291
|
+
|
|
292
|
+
const props = (members.filter?.((m: any) => m?.kind === "property") ||
|
|
293
|
+
[]) as dom.PropertyDeclaration[];
|
|
294
|
+
if (!props.some((m) => m.name === "@id")) {
|
|
295
|
+
members.unshift(
|
|
296
|
+
dom.create.property(
|
|
297
|
+
"@id",
|
|
298
|
+
dom.create.namedTypeReference("IRI"),
|
|
299
|
+
dom.DeclarationFlags.Optional |
|
|
300
|
+
dom.DeclarationFlags.ReadOnly
|
|
301
|
+
),
|
|
302
|
+
dom.create.property(
|
|
303
|
+
"@graph",
|
|
304
|
+
dom.create.namedTypeReference("IRI"),
|
|
305
|
+
dom.DeclarationFlags.Optional |
|
|
306
|
+
dom.DeclarationFlags.ReadOnly
|
|
307
|
+
)
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return t;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function addIdAndGraphIriToUnionObjects(t: dom.Type): dom.Type {
|
|
315
|
+
if (!isUnionType(t)) return t;
|
|
316
|
+
const members = (t as dom.UnionType).members.map((m) =>
|
|
317
|
+
(m as any)?.kind === "object" ? addIdAndGraphProperties(m) : m
|
|
318
|
+
);
|
|
319
|
+
return dom.create.union(members);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Create property and attach predicate IRI and annotations consistently
|
|
323
|
+
function createProperty(
|
|
324
|
+
name: string,
|
|
325
|
+
type: dom.Type,
|
|
326
|
+
flags: dom.DeclarationFlags,
|
|
327
|
+
predicateIri?: string,
|
|
328
|
+
annotations?: Annotation[]
|
|
329
|
+
): dom.PropertyDeclaration {
|
|
330
|
+
const prop = dom.create.property(name, type, flags);
|
|
331
|
+
if (predicateIri) predicateIriByProp.set(prop, predicateIri);
|
|
332
|
+
const cmt = commentFromAnnotations(annotations) || "";
|
|
333
|
+
prop.jsDocComment = cmt
|
|
334
|
+
? `${cmt}\n\nOriginal IRI: ${predicateIri ?? ""}`.trim()
|
|
335
|
+
: `Original IRI: ${predicateIri ?? ""}`;
|
|
336
|
+
return prop;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export const ShexJTypingTransformerCompact = ShexJTraverser.createTransformer<
|
|
340
|
+
{
|
|
341
|
+
Schema: { return: dom.TopLevelDeclaration[] };
|
|
342
|
+
ShapeDecl: { return: dom.InterfaceDeclaration };
|
|
343
|
+
Shape: { return: dom.InterfaceDeclaration };
|
|
344
|
+
EachOf: { return: dom.ObjectType | dom.InterfaceDeclaration };
|
|
345
|
+
TripleConstraint: { return: dom.PropertyDeclaration };
|
|
346
|
+
NodeConstraint: { return: dom.Type };
|
|
347
|
+
ShapeOr: { return: dom.UnionType };
|
|
348
|
+
ShapeAnd: { return: dom.IntersectionType };
|
|
349
|
+
ShapeNot: { return: never };
|
|
350
|
+
ShapeExternal: { return: never };
|
|
351
|
+
},
|
|
352
|
+
null
|
|
353
|
+
>({
|
|
354
|
+
// Transformer from Schema to interfaces
|
|
355
|
+
Schema: {
|
|
356
|
+
transformer: async (_schema, getTransformedChildren) => {
|
|
357
|
+
const transformedChildren = await getTransformedChildren();
|
|
358
|
+
const interfaces: dom.TopLevelDeclaration[] = [];
|
|
359
|
+
transformedChildren.shapes?.forEach((shape) => {
|
|
360
|
+
if (
|
|
361
|
+
typeof shape !== "string" &&
|
|
362
|
+
(shape as dom.InterfaceDeclaration).kind === "interface"
|
|
363
|
+
) {
|
|
364
|
+
interfaces.push(shape as dom.InterfaceDeclaration);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
return interfaces;
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
// Transformer from ShapeDecl to interface
|
|
372
|
+
ShapeDecl: {
|
|
373
|
+
transformer: async (shapeDecl, getTransformedChildren) => {
|
|
374
|
+
const shapeName = nameFromAnnotationOrId(shapeDecl) || "Shape";
|
|
375
|
+
const { shapeExpr } = await getTransformedChildren();
|
|
376
|
+
if ((shapeExpr as dom.InterfaceDeclaration).kind === "interface") {
|
|
377
|
+
const shapeInterface = shapeExpr as ShapeInterfaceDeclaration;
|
|
378
|
+
shapeInterface.name = shapeName;
|
|
379
|
+
// Preserve shape id for downstream shapeTypes generation
|
|
380
|
+
shapeInterface.shapeId = shapeDecl.id;
|
|
381
|
+
|
|
382
|
+
// Ensure root-level @id and @graph are present as readonly (mandatory)
|
|
383
|
+
const hasId = shapeInterface.members.find(
|
|
384
|
+
(m) => m.kind === "property" && m.name === "@id"
|
|
385
|
+
);
|
|
386
|
+
const hasGraph = shapeInterface.members.find(
|
|
387
|
+
(m) => m.kind === "property" && m.name === "@graph"
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
if (!hasId || !hasGraph) {
|
|
391
|
+
const propsToAdd: dom.PropertyDeclaration[] = [];
|
|
392
|
+
if (!hasGraph) {
|
|
393
|
+
const graphProp = dom.create.property(
|
|
394
|
+
"@graph",
|
|
395
|
+
dom.create.namedTypeReference("IRI"),
|
|
396
|
+
dom.DeclarationFlags.ReadOnly
|
|
397
|
+
);
|
|
398
|
+
graphProp.jsDocComment = "The graph IRI.";
|
|
399
|
+
propsToAdd.push(graphProp);
|
|
400
|
+
}
|
|
401
|
+
if (!hasId) {
|
|
402
|
+
const idProp = dom.create.property(
|
|
403
|
+
"@id",
|
|
404
|
+
dom.create.namedTypeReference("IRI"),
|
|
405
|
+
dom.DeclarationFlags.ReadOnly
|
|
406
|
+
);
|
|
407
|
+
idProp.jsDocComment = "The subject IRI.";
|
|
408
|
+
propsToAdd.push(idProp);
|
|
409
|
+
}
|
|
410
|
+
shapeInterface.members.unshift(...propsToAdd);
|
|
411
|
+
}
|
|
412
|
+
return shapeInterface;
|
|
413
|
+
}
|
|
414
|
+
throw new Error(
|
|
415
|
+
"Unsupported direct shape expression on ShapeDecl for compact format."
|
|
416
|
+
);
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
// Transformer from Shape to interface
|
|
421
|
+
Shape: {
|
|
422
|
+
transformer: async (
|
|
423
|
+
_shape,
|
|
424
|
+
getTransformedChildren,
|
|
425
|
+
setReturnPointer
|
|
426
|
+
) => {
|
|
427
|
+
const newInterface: ShapeInterfaceDeclaration =
|
|
428
|
+
dom.create.interface("");
|
|
429
|
+
setReturnPointer(newInterface);
|
|
430
|
+
if (_shape.extra?.length ?? 0 > 0) {
|
|
431
|
+
tagExtraPredicatesFromExpression(
|
|
432
|
+
_shape.expression,
|
|
433
|
+
new Set(_shape.extra)
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
const transformedChildren = await getTransformedChildren();
|
|
437
|
+
if (
|
|
438
|
+
typeof transformedChildren.expression !== "string" &&
|
|
439
|
+
transformedChildren.expression &&
|
|
440
|
+
((transformedChildren.expression as dom.ObjectType).kind ===
|
|
441
|
+
"object" ||
|
|
442
|
+
(transformedChildren.expression as dom.InterfaceDeclaration)
|
|
443
|
+
.kind === "interface")
|
|
444
|
+
) {
|
|
445
|
+
newInterface.members.push(
|
|
446
|
+
...(transformedChildren.expression as dom.ObjectType)
|
|
447
|
+
.members
|
|
448
|
+
);
|
|
449
|
+
} else if (
|
|
450
|
+
(transformedChildren.expression as dom.PropertyDeclaration)
|
|
451
|
+
?.kind === "property"
|
|
452
|
+
) {
|
|
453
|
+
newInterface.members.push(
|
|
454
|
+
transformedChildren.expression as dom.PropertyDeclaration
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
if (transformedChildren.extends) {
|
|
458
|
+
transformedChildren.extends.forEach((ext) => {
|
|
459
|
+
const extInt = ext as dom.InterfaceDeclaration;
|
|
460
|
+
if (extInt.kind === "interface") {
|
|
461
|
+
const merged = [
|
|
462
|
+
...extInt.members.filter(
|
|
463
|
+
(m) =>
|
|
464
|
+
!(m.kind === "property" && m.name === "@id")
|
|
465
|
+
),
|
|
466
|
+
...newInterface.members,
|
|
467
|
+
].filter(
|
|
468
|
+
(m): m is dom.PropertyDeclaration =>
|
|
469
|
+
m.kind === "property"
|
|
470
|
+
);
|
|
471
|
+
newInterface.members = dedupeCompactProperties(merged);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
// Final pass: ensure only a single @id and a single @graph property, normalize to readonly
|
|
476
|
+
const idSeen = new Set<number>();
|
|
477
|
+
const graphSeen = new Set<number>();
|
|
478
|
+
newInterface.members = newInterface.members.filter((m, idx) => {
|
|
479
|
+
if (m.kind !== "property") return true;
|
|
480
|
+
|
|
481
|
+
if (m.name === "@id") {
|
|
482
|
+
if (idSeen.size === 0) {
|
|
483
|
+
idSeen.add(idx);
|
|
484
|
+
// normalize id type to IRI and make readonly
|
|
485
|
+
m.type = dom.create.namedTypeReference("IRI");
|
|
486
|
+
m.flags = dom.DeclarationFlags.ReadOnly;
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (m.name === "@graph") {
|
|
493
|
+
if (graphSeen.size === 0) {
|
|
494
|
+
graphSeen.add(idx);
|
|
495
|
+
// normalize graph type to IRI and make readonly
|
|
496
|
+
m.type = dom.create.namedTypeReference("IRI");
|
|
497
|
+
m.flags = dom.DeclarationFlags.ReadOnly;
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return true;
|
|
504
|
+
});
|
|
505
|
+
return newInterface;
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
// Transformer from EachOf to object type. EachOf contains the `expressions` array of properties (TripleConstraint)
|
|
510
|
+
EachOf: {
|
|
511
|
+
transformer: async (
|
|
512
|
+
eachOf,
|
|
513
|
+
getTransformedChildren,
|
|
514
|
+
setReturnPointer
|
|
515
|
+
) => {
|
|
516
|
+
const transformedChildren = await getTransformedChildren();
|
|
517
|
+
const name = nameFromAnnotationOrId(eachOf);
|
|
518
|
+
|
|
519
|
+
const objectType = name
|
|
520
|
+
? dom.create.interface(name)
|
|
521
|
+
: dom.create.objectType([]);
|
|
522
|
+
setReturnPointer(objectType);
|
|
523
|
+
const inputProps: dom.PropertyDeclaration[] = [];
|
|
524
|
+
transformedChildren.expressions.forEach((expr) => {
|
|
525
|
+
if (!expr || typeof expr === "string") return;
|
|
526
|
+
const kind = (expr as any).kind;
|
|
527
|
+
if (kind === "property") {
|
|
528
|
+
inputProps.push(expr as dom.PropertyDeclaration);
|
|
529
|
+
} else if (kind === "object" || kind === "interface") {
|
|
530
|
+
const mlist = (
|
|
531
|
+
expr as dom.ObjectType | dom.InterfaceDeclaration
|
|
532
|
+
).members;
|
|
533
|
+
mlist.forEach((m) => {
|
|
534
|
+
if ((m as any).kind === "property") {
|
|
535
|
+
inputProps.push(m as dom.PropertyDeclaration);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
const deduped = dedupeCompactProperties(inputProps);
|
|
541
|
+
objectType.members.push(...deduped);
|
|
542
|
+
return objectType;
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
// Transformer from triple constraints to type properties.
|
|
547
|
+
TripleConstraint: {
|
|
548
|
+
transformer: async (
|
|
549
|
+
tripleConstraint,
|
|
550
|
+
getTransformedChildren,
|
|
551
|
+
_setReturnPointer,
|
|
552
|
+
_node
|
|
553
|
+
) => {
|
|
554
|
+
const transformedChildren = await getTransformedChildren();
|
|
555
|
+
const baseName = (tripleConstraint as any)
|
|
556
|
+
.readablePredicate as string;
|
|
557
|
+
|
|
558
|
+
const max = tripleConstraint.max;
|
|
559
|
+
const isExtra = extraTripleConstraints.has(tripleConstraint);
|
|
560
|
+
const isPlural =
|
|
561
|
+
isExtra || max === -1 || (max !== undefined && max !== 1);
|
|
562
|
+
const isOptional =
|
|
563
|
+
tripleConstraint.min === 0 && tripleConstraint.max == 1;
|
|
564
|
+
|
|
565
|
+
let valueType: dom.Type = dom.type.any;
|
|
566
|
+
if (transformedChildren.valueExpr)
|
|
567
|
+
valueType = transformedChildren.valueExpr as dom.Type;
|
|
568
|
+
|
|
569
|
+
// Generic: If valueExpr is a NodeConstraint with concrete `values`,
|
|
570
|
+
// build a union of named alias references derived from those values.
|
|
571
|
+
// Works for any predicate (not only rdf:type).
|
|
572
|
+
const originalValueExpr: any = (tripleConstraint as any)?.valueExpr;
|
|
573
|
+
if (
|
|
574
|
+
originalValueExpr &&
|
|
575
|
+
typeof originalValueExpr === "object" &&
|
|
576
|
+
originalValueExpr.type === "NodeConstraint" &&
|
|
577
|
+
Array.isArray(originalValueExpr.values) &&
|
|
578
|
+
originalValueExpr.values.length > 0
|
|
579
|
+
) {
|
|
580
|
+
const aliasRefs: dom.Type[] = [];
|
|
581
|
+
for (const v of originalValueExpr.values) {
|
|
582
|
+
const literalVal = literalFromValueSetValue(v);
|
|
583
|
+
if (literalVal !== undefined) {
|
|
584
|
+
aliasRefs.push(dom.type.stringLiteral(literalVal));
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (aliasRefs.length > 0) {
|
|
588
|
+
let union = unionOf(aliasRefs);
|
|
589
|
+
if (isExtra) {
|
|
590
|
+
// Type like (string & {}), to preserve literal values.
|
|
591
|
+
const intersectedString = dom.create.intersection([
|
|
592
|
+
dom.create.namedTypeReference("IRI"),
|
|
593
|
+
dom.create.objectType([]),
|
|
594
|
+
]);
|
|
595
|
+
union = unionOf([union, intersectedString]);
|
|
596
|
+
}
|
|
597
|
+
const final = isPlural ? setOf(union) : union;
|
|
598
|
+
return createProperty(
|
|
599
|
+
baseName,
|
|
600
|
+
final,
|
|
601
|
+
isOptional
|
|
602
|
+
? dom.DeclarationFlags.Optional
|
|
603
|
+
: dom.DeclarationFlags.None,
|
|
604
|
+
tripleConstraint.predicate,
|
|
605
|
+
tripleConstraint.annotations
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (
|
|
611
|
+
(valueType as dom.InterfaceDeclaration).kind === "interface" &&
|
|
612
|
+
!(valueType as dom.InterfaceDeclaration).name
|
|
613
|
+
) {
|
|
614
|
+
valueType = dom.create.objectType(
|
|
615
|
+
(valueType as dom.InterfaceDeclaration)
|
|
616
|
+
.members as dom.PropertyDeclaration[]
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Normalize NodeConstraint returned object forms for IRIs into IRI
|
|
621
|
+
// Heuristic: existing transformer (compact) returns string/number/boolean OR object/interface.
|
|
622
|
+
// We treat any simple string/number/boolean/name as primitive.
|
|
623
|
+
|
|
624
|
+
// Determine category
|
|
625
|
+
const objLike = isObjectLike(valueType);
|
|
626
|
+
const isUnion =
|
|
627
|
+
(valueType as unknown as { kind?: string })?.kind === "union";
|
|
628
|
+
const unionMembers: dom.Type[] = isUnion
|
|
629
|
+
? (valueType as dom.UnionType).members
|
|
630
|
+
: [];
|
|
631
|
+
const unionAllObjLike =
|
|
632
|
+
isUnion &&
|
|
633
|
+
unionMembers.length > 0 &&
|
|
634
|
+
unionMembers.every(isObjectLike);
|
|
635
|
+
const primLike = isPrimitiveLike(valueType);
|
|
636
|
+
if (
|
|
637
|
+
!primLike &&
|
|
638
|
+
!objLike &&
|
|
639
|
+
(valueType as dom.UnionType).kind === "union"
|
|
640
|
+
) {
|
|
641
|
+
const u = valueType as dom.UnionType;
|
|
642
|
+
const hasObj = u.members.some(isObjectLike);
|
|
643
|
+
const hasPrim = u.members.some(isPrimitiveLike);
|
|
644
|
+
if (isPlural && hasObj && hasPrim) {
|
|
645
|
+
throw new Error(
|
|
646
|
+
`Mixed plural union (object + primitive) not supported for predicate ${tripleConstraint.predicate}`
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
let finalType: dom.Type;
|
|
652
|
+
if (isPlural) {
|
|
653
|
+
if (objLike || unionAllObjLike) {
|
|
654
|
+
if (
|
|
655
|
+
(valueType as dom.InterfaceDeclaration).kind ===
|
|
656
|
+
"interface" &&
|
|
657
|
+
(valueType as dom.InterfaceDeclaration).name
|
|
658
|
+
) {
|
|
659
|
+
const ifaceName = (
|
|
660
|
+
valueType as dom.InterfaceDeclaration
|
|
661
|
+
).name;
|
|
662
|
+
// Set of full object instances
|
|
663
|
+
finalType = setOf(
|
|
664
|
+
dom.create.namedTypeReference(ifaceName)
|
|
665
|
+
);
|
|
666
|
+
} else {
|
|
667
|
+
// Anonymous object or union of anonymous/interface objects
|
|
668
|
+
let valueForSet: dom.Type = valueType;
|
|
669
|
+
if (unionAllObjLike) {
|
|
670
|
+
// Ensure each union member has @id and @graph as optional readonly
|
|
671
|
+
valueForSet =
|
|
672
|
+
addIdAndGraphIriToUnionObjects(valueType);
|
|
673
|
+
} else {
|
|
674
|
+
valueForSet = addIdAndGraphProperties(valueType);
|
|
675
|
+
}
|
|
676
|
+
finalType = setOf(valueForSet);
|
|
677
|
+
}
|
|
678
|
+
} else {
|
|
679
|
+
finalType = setOf(valueType);
|
|
680
|
+
}
|
|
681
|
+
} else {
|
|
682
|
+
// Singular
|
|
683
|
+
// If anonymous object or union of object-like types, ensure id: IRI is present (mandatory)
|
|
684
|
+
if (objLike) {
|
|
685
|
+
if ((valueType as dom.ObjectType).kind === "object") {
|
|
686
|
+
valueType = addIdAndGraphProperties(valueType);
|
|
687
|
+
}
|
|
688
|
+
} else if (isUnion && unionAllObjLike) {
|
|
689
|
+
valueType = addIdAndGraphIriToUnionObjects(valueType);
|
|
690
|
+
}
|
|
691
|
+
// Singular: always the interface/object type itself (never Id union)
|
|
692
|
+
if (
|
|
693
|
+
(valueType as dom.InterfaceDeclaration).kind ===
|
|
694
|
+
"interface" &&
|
|
695
|
+
(valueType as dom.InterfaceDeclaration).name
|
|
696
|
+
) {
|
|
697
|
+
finalType = dom.create.namedTypeReference(
|
|
698
|
+
(valueType as dom.InterfaceDeclaration).name
|
|
699
|
+
);
|
|
700
|
+
} else {
|
|
701
|
+
finalType = valueType;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return createProperty(
|
|
705
|
+
baseName,
|
|
706
|
+
finalType,
|
|
707
|
+
isOptional
|
|
708
|
+
? dom.DeclarationFlags.Optional
|
|
709
|
+
: dom.DeclarationFlags.None,
|
|
710
|
+
tripleConstraint.predicate,
|
|
711
|
+
tripleConstraint.annotations
|
|
712
|
+
);
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
|
|
716
|
+
// Transformer from node constraint to type
|
|
717
|
+
NodeConstraint: {
|
|
718
|
+
transformer: async (nodeConstraint) => {
|
|
719
|
+
if (nodeConstraint.datatype) {
|
|
720
|
+
switch (nodeConstraint.datatype) {
|
|
721
|
+
case "http://www.w3.org/2001/XMLSchema#boolean":
|
|
722
|
+
return dom.type.boolean;
|
|
723
|
+
case "http://www.w3.org/2001/XMLSchema#byte":
|
|
724
|
+
case "http://www.w3.org/2001/XMLSchema#decimal":
|
|
725
|
+
case "http://www.w3.org/2001/XMLSchema#double":
|
|
726
|
+
case "http://www.w3.org/2001/XMLSchema#float":
|
|
727
|
+
case "http://www.w3.org/2001/XMLSchema#int":
|
|
728
|
+
case "http://www.w3.org/2001/XMLSchema#integer":
|
|
729
|
+
case "http://www.w3.org/2001/XMLSchema#long":
|
|
730
|
+
case "http://www.w3.org/2001/XMLSchema#negativeInteger":
|
|
731
|
+
case "http://www.w3.org/2001/XMLSchema#nonNegativeInteger":
|
|
732
|
+
case "http://www.w3.org/2001/XMLSchema#nonPositiveInteger":
|
|
733
|
+
case "http://www.w3.org/2001/XMLSchema#positiveInteger":
|
|
734
|
+
case "http://www.w3.org/2001/XMLSchema#short":
|
|
735
|
+
case "http://www.w3.org/2001/XMLSchema#unsignedLong":
|
|
736
|
+
case "http://www.w3.org/2001/XMLSchema#unsignedInt":
|
|
737
|
+
case "http://www.w3.org/2001/XMLSchema#unsignedShort":
|
|
738
|
+
case "http://www.w3.org/2001/XMLSchema#unsignedByte":
|
|
739
|
+
return dom.type.number;
|
|
740
|
+
default:
|
|
741
|
+
return dom.type.string; // treat most as string
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (nodeConstraint.nodeKind) {
|
|
745
|
+
switch (nodeConstraint.nodeKind) {
|
|
746
|
+
case "iri":
|
|
747
|
+
return dom.create.namedTypeReference("IRI");
|
|
748
|
+
case "bnode":
|
|
749
|
+
return dom.type.string; // opaque id as string
|
|
750
|
+
case "nonliteral":
|
|
751
|
+
return dom.create.namedTypeReference("IRI");
|
|
752
|
+
case "literal":
|
|
753
|
+
default:
|
|
754
|
+
return dom.type.string;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
if (nodeConstraint.values) {
|
|
758
|
+
const union = dom.create.union([]);
|
|
759
|
+
nodeConstraint.values.forEach((v) => {
|
|
760
|
+
const literalVal = literalFromValueSetValue(v);
|
|
761
|
+
if (literalVal !== undefined) {
|
|
762
|
+
union.members.push(dom.type.stringLiteral(literalVal));
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
if (!union.members.length) return dom.type.string;
|
|
766
|
+
if (union.members.length === 1) return union.members[0];
|
|
767
|
+
return union;
|
|
768
|
+
}
|
|
769
|
+
return dom.type.any;
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
|
|
773
|
+
// Transformer from ShapeOr to union type
|
|
774
|
+
ShapeOr: {
|
|
775
|
+
transformer: async (_shapeOr, getTransformedChildren) => {
|
|
776
|
+
const tc = await getTransformedChildren();
|
|
777
|
+
|
|
778
|
+
return dom.create.union(tc.shapeExprs as dom.Type[]);
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
|
|
782
|
+
// Transformer from ShapeAnd to intersection type
|
|
783
|
+
ShapeAnd: {
|
|
784
|
+
transformer: async (_shapeAnd, getTransformedChildren) => {
|
|
785
|
+
const tc = await getTransformedChildren();
|
|
786
|
+
const valid: dom.Type[] = [];
|
|
787
|
+
tc.shapeExprs.forEach((t) => {
|
|
788
|
+
if (typeof t === "object") valid.push(t);
|
|
789
|
+
});
|
|
790
|
+
return dom.create.intersection(valid);
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
|
|
794
|
+
// Transformer from ShapeNot to type - not supported.
|
|
795
|
+
ShapeNot: {
|
|
796
|
+
transformer: async () => {
|
|
797
|
+
throw new Error("ShapeNot not supported (compact)");
|
|
798
|
+
},
|
|
799
|
+
},
|
|
800
|
+
|
|
801
|
+
// Transformer from ShapeExternal to type - not supported.
|
|
802
|
+
ShapeExternal: {
|
|
803
|
+
transformer: async () => {
|
|
804
|
+
throw new Error("ShapeExternal not supported (compact)");
|
|
805
|
+
},
|
|
806
|
+
},
|
|
807
|
+
});
|