@fedify/vocab-tools 2.4.0-dev.1570 → 2.4.0-dev.1573
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/deno.json +1 -1
- package/dist/mod.cjs +108 -72
- package/dist/mod.js +108 -72
- package/package.json +1 -1
- package/src/__snapshots__/class.test.ts.deno.snap +34561 -28340
- package/src/__snapshots__/class.test.ts.node.snap +34561 -28340
- package/src/__snapshots__/class.test.ts.snap +34561 -28340
- package/src/class.ts +79 -21
- package/src/codec.ts +39 -17
- package/src/property.ts +17 -15
- package/src/type.ts +6 -21
package/dist/mod.js
CHANGED
|
@@ -7,7 +7,7 @@ import { parse } from "yaml";
|
|
|
7
7
|
import { pascalCase } from "es-toolkit";
|
|
8
8
|
//#region deno.json
|
|
9
9
|
var name = "@fedify/vocab-tools";
|
|
10
|
-
var version = "2.4.0-dev.
|
|
10
|
+
var version = "2.4.0-dev.1573+246bd0b2";
|
|
11
11
|
//#endregion
|
|
12
12
|
//#region src/type.ts
|
|
13
13
|
const HEURISTICS_CONTEXTS = [
|
|
@@ -155,10 +155,10 @@ const scalarTypes = {
|
|
|
155
155
|
return `${v} instanceof URL`;
|
|
156
156
|
},
|
|
157
157
|
encoder(v) {
|
|
158
|
-
return `{ "@id": ${v}
|
|
158
|
+
return `{ "@id": formatIri(${v}) }`;
|
|
159
159
|
},
|
|
160
160
|
compactEncoder(v) {
|
|
161
|
-
return
|
|
161
|
+
return `formatIri(${v})`;
|
|
162
162
|
},
|
|
163
163
|
dataCheck(v) {
|
|
164
164
|
return `${v} != null && typeof ${v} === "object" && "@id" in ${v}
|
|
@@ -166,22 +166,7 @@ const scalarTypes = {
|
|
|
166
166
|
&& ${v}["@id"] !== ""`;
|
|
167
167
|
},
|
|
168
168
|
decoder(v, baseUrlVar) {
|
|
169
|
-
return
|
|
170
|
-
? new URL("at://" +
|
|
171
|
-
encodeURIComponent(
|
|
172
|
-
${v}["@id"].includes("/", 5)
|
|
173
|
-
? ${v}["@id"].slice(5, ${v}["@id"].indexOf("/", 5))
|
|
174
|
-
: ${v}["@id"].slice(5)
|
|
175
|
-
) +
|
|
176
|
-
(
|
|
177
|
-
${v}["@id"].includes("/", 5)
|
|
178
|
-
? ${v}["@id"].slice(${v}["@id"].indexOf("/", 5))
|
|
179
|
-
: ""
|
|
180
|
-
)
|
|
181
|
-
)
|
|
182
|
-
: URL.canParse(${v}["@id"]) && ${baseUrlVar}
|
|
183
|
-
? new URL(${v}["@id"])
|
|
184
|
-
: new URL(${v}["@id"], ${baseUrlVar})`;
|
|
169
|
+
return `parseIri(${v}["@id"], ${baseUrlVar})`;
|
|
185
170
|
}
|
|
186
171
|
},
|
|
187
172
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString": {
|
|
@@ -323,10 +308,10 @@ const scalarTypes = {
|
|
|
323
308
|
return `${v} instanceof URL`;
|
|
324
309
|
},
|
|
325
310
|
encoder(v) {
|
|
326
|
-
return `{ "@value": ${v}
|
|
311
|
+
return `{ "@value": formatIri(${v}) }`;
|
|
327
312
|
},
|
|
328
313
|
compactEncoder(v) {
|
|
329
|
-
return
|
|
314
|
+
return `formatIri(${v})`;
|
|
330
315
|
},
|
|
331
316
|
dataCheck(v) {
|
|
332
317
|
return `typeof ${v} === "object" && "@value" in ${v}
|
|
@@ -334,7 +319,7 @@ const scalarTypes = {
|
|
|
334
319
|
&& ${v}["@value"] !== "" && ${v}["@value"] !== "/"`;
|
|
335
320
|
},
|
|
336
321
|
decoder(v) {
|
|
337
|
-
return `
|
|
322
|
+
return `parseIri(${v}["@value"])`;
|
|
338
323
|
}
|
|
339
324
|
},
|
|
340
325
|
"fedify:publicKey": {
|
|
@@ -804,7 +789,7 @@ async function* generateEncoder(typeUri, types) {
|
|
|
804
789
|
for (const v of this.${await getFieldName(property.uri)}) {
|
|
805
790
|
const item = (
|
|
806
791
|
`;
|
|
807
|
-
if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? v
|
|
792
|
+
if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? formatIri(v) : ";
|
|
808
793
|
const encoders = getEncoders(property.range, types, "v", "options", true);
|
|
809
794
|
for (const code of encoders) yield code;
|
|
810
795
|
yield `
|
|
@@ -835,7 +820,7 @@ async function* generateEncoder(typeUri, types) {
|
|
|
835
820
|
}
|
|
836
821
|
yield `
|
|
837
822
|
${type.typeless ? "" : `result["type"] = ${JSON.stringify(type.compactName ?? type.uri)};`}
|
|
838
|
-
if (this.id != null) result["id"] = this.id
|
|
823
|
+
if (this.id != null) result["id"] = formatIri(this.id);
|
|
839
824
|
result["@context"] = ${JSON.stringify(type.defaultContext)};
|
|
840
825
|
return result;
|
|
841
826
|
}
|
|
@@ -863,7 +848,7 @@ async function* generateEncoder(typeUri, types) {
|
|
|
863
848
|
for (const v of this.${await getFieldName(property.uri)}) {
|
|
864
849
|
const element = (
|
|
865
850
|
`;
|
|
866
|
-
if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? { \"@id\": v
|
|
851
|
+
if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? { \"@id\": formatIri(v) } : ";
|
|
867
852
|
for (const code of getEncoders(property.range, types, "v", "options")) yield code;
|
|
868
853
|
yield `
|
|
869
854
|
);
|
|
@@ -890,7 +875,7 @@ async function* generateEncoder(typeUri, types) {
|
|
|
890
875
|
}
|
|
891
876
|
yield `
|
|
892
877
|
${type.typeless ? "" : `values["@type"] = [${JSON.stringify(type.uri)}];`}
|
|
893
|
-
if (this.id != null) values["@id"] = this.id
|
|
878
|
+
if (this.id != null) values["@id"] = formatIri(this.id);
|
|
894
879
|
if (options.format === "expand") {
|
|
895
880
|
return await jsonld.expand(
|
|
896
881
|
values,
|
|
@@ -1016,10 +1001,12 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1016
1001
|
};
|
|
1017
1002
|
// deno-lint-ignore no-explicit-any
|
|
1018
1003
|
let values: Record<string, any[]> & { "@id"?: string };
|
|
1004
|
+
let expanded: unknown[];
|
|
1019
1005
|
if (globalThis.Object.keys(json).length == 0) {
|
|
1020
1006
|
values = {};
|
|
1007
|
+
expanded = [values];
|
|
1021
1008
|
} else {
|
|
1022
|
-
|
|
1009
|
+
expanded = await jsonld.expand(json, {
|
|
1023
1010
|
documentLoader: options.contextLoader,
|
|
1024
1011
|
keepFreeFloatingNodes: true,
|
|
1025
1012
|
});
|
|
@@ -1027,11 +1014,9 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1027
1014
|
// deno-lint-ignore no-explicit-any
|
|
1028
1015
|
(expanded[0] ?? {}) as (Record<string, any[]> & { "@id"?: string });
|
|
1029
1016
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
if (options.baseUrl == null && values["@id"] != null && !values["@id"].startsWith("_:") && URL.canParse(values["@id"])) {
|
|
1034
|
-
options = { ...options, baseUrl: new URL(values["@id"]) };
|
|
1017
|
+
const id = parseJsonLdId(values["@id"], options.baseUrl);
|
|
1018
|
+
if (id != null && options.baseUrl == null) {
|
|
1019
|
+
options = { ...options, baseUrl: id };
|
|
1035
1020
|
}
|
|
1036
1021
|
`;
|
|
1037
1022
|
const subtypes = getSubtypes(typeUri, types, true);
|
|
@@ -1052,10 +1037,24 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1052
1037
|
throw new TypeError("Invalid type: " + values["@type"]);
|
|
1053
1038
|
}
|
|
1054
1039
|
}
|
|
1040
|
+
`;
|
|
1041
|
+
yield `
|
|
1042
|
+
let cacheJsonLd = !("_fromSubclass" in options) || !options._fromSubclass
|
|
1043
|
+
? normalizeJsonLdIris(
|
|
1044
|
+
expanded,
|
|
1045
|
+
PORTABLE_IRI_KEYS,
|
|
1046
|
+
PORTABLE_IRI_PATTERN,
|
|
1047
|
+
)
|
|
1048
|
+
: undefined;
|
|
1049
|
+
if (cacheJsonLd != null && cacheJsonLd !== expanded) {
|
|
1050
|
+
cacheJsonLd = structuredClone(cacheJsonLd);
|
|
1051
|
+
}
|
|
1055
1052
|
`;
|
|
1056
1053
|
if (type.extends == null) yield `
|
|
1057
1054
|
const instance = new this(
|
|
1058
|
-
{
|
|
1055
|
+
{
|
|
1056
|
+
id
|
|
1057
|
+
},
|
|
1059
1058
|
options,
|
|
1060
1059
|
);
|
|
1061
1060
|
`;
|
|
@@ -1098,11 +1097,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1098
1097
|
if (typeof v === "object" && "@id" in v && !("@type" in v)
|
|
1099
1098
|
&& globalThis.Object.keys(v).length === 1) {
|
|
1100
1099
|
if (v["@id"].startsWith("_:")) continue;
|
|
1101
|
-
${variable}.push(
|
|
1102
|
-
!URL.canParse(v["@id"], ${propertyBaseUrl}) && v["@id"].startsWith("at://")
|
|
1103
|
-
? new URL("at://" + encodeURIComponent(v["@id"].substring(5)))
|
|
1104
|
-
: new URL(v["@id"], ${propertyBaseUrl})
|
|
1105
|
-
);
|
|
1100
|
+
${variable}.push(parseIri(v["@id"], ${propertyBaseUrl}));
|
|
1106
1101
|
continue;
|
|
1107
1102
|
}
|
|
1108
1103
|
`;
|
|
@@ -1130,7 +1125,19 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
|
|
|
1130
1125
|
yield `
|
|
1131
1126
|
if (!("_fromSubclass" in options) || !options._fromSubclass) {
|
|
1132
1127
|
try {
|
|
1133
|
-
|
|
1128
|
+
if (cacheJsonLd != null && cacheJsonLd !== expanded) {
|
|
1129
|
+
const compactArray = Array.isArray(json) && json.length === 1;
|
|
1130
|
+
const jsonLd = compactArray ? json[0] : json;
|
|
1131
|
+
const normalized = cacheJsonLd;
|
|
1132
|
+
const cachedJsonLd = await compactJsonLdCache(
|
|
1133
|
+
normalized,
|
|
1134
|
+
jsonLd,
|
|
1135
|
+
options.contextLoader,
|
|
1136
|
+
);
|
|
1137
|
+
instance._cachedJsonLd = compactArray ? [cachedJsonLd] : cachedJsonLd;
|
|
1138
|
+
} else {
|
|
1139
|
+
instance._cachedJsonLd = structuredClone(json);
|
|
1140
|
+
}
|
|
1134
1141
|
} catch {
|
|
1135
1142
|
getLogger(["fedify", "vocab"]).warn(
|
|
1136
1143
|
"Failed to cache JSON-LD: {json}",
|
|
@@ -1557,9 +1564,10 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1557
1564
|
${JSON.stringify(version)},
|
|
1558
1565
|
);
|
|
1559
1566
|
return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
|
|
1567
|
+
const lookupUrl = formatIri(url);
|
|
1560
1568
|
let fetchResult: RemoteDocument;
|
|
1561
1569
|
try {
|
|
1562
|
-
fetchResult = await documentLoader(
|
|
1570
|
+
fetchResult = await documentLoader(lookupUrl);
|
|
1563
1571
|
} catch (error) {
|
|
1564
1572
|
span.setStatus({
|
|
1565
1573
|
code: SpanStatusCode.ERROR,
|
|
@@ -1569,21 +1577,20 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1569
1577
|
if (options.suppressError) {
|
|
1570
1578
|
getLogger(["fedify", "vocab"]).error(
|
|
1571
1579
|
"Failed to fetch {url}: {error}",
|
|
1572
|
-
{ error, url:
|
|
1580
|
+
{ error, url: lookupUrl }
|
|
1573
1581
|
);
|
|
1574
1582
|
return null;
|
|
1575
1583
|
}
|
|
1576
1584
|
throw error;
|
|
1577
1585
|
}
|
|
1578
1586
|
const { document, documentUrl } = fetchResult;
|
|
1579
|
-
const baseUrl =
|
|
1587
|
+
const baseUrl = parseIri(documentUrl);
|
|
1580
1588
|
try {
|
|
1581
1589
|
const obj = await this.#${property.singularName}_fromJsonLd(
|
|
1582
1590
|
document,
|
|
1583
1591
|
{ documentLoader, contextLoader, tracerProvider, baseUrl }
|
|
1584
1592
|
);
|
|
1585
|
-
if (
|
|
1586
|
-
obj.id.origin !== baseUrl.origin) {
|
|
1593
|
+
if (obj?.id != null && !isTrustedIriOrigin(options, obj.id, baseUrl)) {
|
|
1587
1594
|
if (options.crossOrigin === "throw") {
|
|
1588
1595
|
throw new Error(
|
|
1589
1596
|
"The object's @id (" + obj.id.href + ") has a different origin " +
|
|
@@ -1612,7 +1619,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1612
1619
|
if (options.suppressError) {
|
|
1613
1620
|
getLogger(["fedify", "vocab"]).error(
|
|
1614
1621
|
"Failed to parse {url}: {error}",
|
|
1615
|
-
{ error: e, url:
|
|
1622
|
+
{ error: e, url: lookupUrl }
|
|
1616
1623
|
);
|
|
1617
1624
|
return null;
|
|
1618
1625
|
}
|
|
@@ -1732,8 +1739,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1732
1739
|
}
|
|
1733
1740
|
if (this.${await getFieldName(property.uri)}.length < 1) return null;
|
|
1734
1741
|
let v = this.${await getFieldName(property.uri)}[0];
|
|
1735
|
-
if (
|
|
1736
|
-
v.id != null &&
|
|
1742
|
+
if (!(v instanceof URL) &&
|
|
1743
|
+
v.id != null &&
|
|
1744
|
+
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
1737
1745
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
1738
1746
|
v = v.id;
|
|
1739
1747
|
}
|
|
@@ -1772,8 +1780,8 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1772
1780
|
`;
|
|
1773
1781
|
}
|
|
1774
1782
|
yield `
|
|
1775
|
-
if (
|
|
1776
|
-
this.id != null && v.id
|
|
1783
|
+
if (v?.id != null &&
|
|
1784
|
+
this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
|
|
1777
1785
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
1778
1786
|
if (options.crossOrigin === "throw") {
|
|
1779
1787
|
throw new Error(
|
|
@@ -1837,8 +1845,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1837
1845
|
const vs = this.${await getFieldName(property.uri)};
|
|
1838
1846
|
for (let i = 0; i < vs.length; i++) {
|
|
1839
1847
|
let v = vs[i];
|
|
1840
|
-
if (
|
|
1841
|
-
v.id != null &&
|
|
1848
|
+
if (!(v instanceof URL) &&
|
|
1849
|
+
v.id != null &&
|
|
1850
|
+
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
1842
1851
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
1843
1852
|
v = v.id;
|
|
1844
1853
|
}
|
|
@@ -1878,9 +1887,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
|
|
|
1878
1887
|
`;
|
|
1879
1888
|
}
|
|
1880
1889
|
yield `
|
|
1881
|
-
if (
|
|
1882
|
-
this.id != null && v.id
|
|
1883
|
-
!this.${await getFieldName(property.uri, "#_trust")}.has(
|
|
1890
|
+
if (v?.id != null &&
|
|
1891
|
+
this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
|
|
1892
|
+
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
1884
1893
|
if (options.crossOrigin === "throw") {
|
|
1885
1894
|
throw new Error(
|
|
1886
1895
|
"The property object's @id (" + v.id.href + ") has a different " +
|
|
@@ -1913,6 +1922,33 @@ async function* generateProperties(typeUri, types, moduleVarNames) {
|
|
|
1913
1922
|
}
|
|
1914
1923
|
//#endregion
|
|
1915
1924
|
//#region src/class.ts
|
|
1925
|
+
const XSD_ANY_URI = "http://www.w3.org/2001/XMLSchema#anyURI";
|
|
1926
|
+
const FEDIFY_URL = "fedify:url";
|
|
1927
|
+
const INTERNAL_RUNTIME_IMPORTS = [
|
|
1928
|
+
"compactJsonLdCache",
|
|
1929
|
+
"getJsonLdContext",
|
|
1930
|
+
"isTrustedIriOrigin",
|
|
1931
|
+
"normalizeJsonLdIris"
|
|
1932
|
+
].join(",\n ");
|
|
1933
|
+
const RUNTIME_IMPORTS = [
|
|
1934
|
+
"canParseDecimal",
|
|
1935
|
+
"decodeMultibase",
|
|
1936
|
+
"type Decimal",
|
|
1937
|
+
"type DocumentLoader",
|
|
1938
|
+
"encodeMultibase",
|
|
1939
|
+
"exportMultibaseKey",
|
|
1940
|
+
"exportSpki",
|
|
1941
|
+
"formatIri",
|
|
1942
|
+
"getDocumentLoader",
|
|
1943
|
+
"importMultibaseKey",
|
|
1944
|
+
"importPem",
|
|
1945
|
+
"isDecimal",
|
|
1946
|
+
"LanguageString",
|
|
1947
|
+
"parseDecimal",
|
|
1948
|
+
"parseIri",
|
|
1949
|
+
"parseJsonLdId",
|
|
1950
|
+
"type RemoteDocument"
|
|
1951
|
+
].join(",\n ");
|
|
1916
1952
|
/**
|
|
1917
1953
|
* Sorts the given types topologically so that the base types come before the
|
|
1918
1954
|
* extended types.
|
|
@@ -2046,6 +2082,18 @@ export function getEntityTypeById(id: string | URL): $EntityType | undefined {
|
|
|
2046
2082
|
|
|
2047
2083
|
`;
|
|
2048
2084
|
}
|
|
2085
|
+
function canContainIriValue(property, types) {
|
|
2086
|
+
return property.range.some((typeUri) => typeUri === XSD_ANY_URI || typeUri === FEDIFY_URL || types[typeUri]?.entity);
|
|
2087
|
+
}
|
|
2088
|
+
function addKeys(keys, property) {
|
|
2089
|
+
keys.add(property.uri);
|
|
2090
|
+
if (property.compactName != null) keys.add(property.compactName);
|
|
2091
|
+
}
|
|
2092
|
+
function addPortableIriKeys(keys, property, types) {
|
|
2093
|
+
if (!canContainIriValue(property, types)) return;
|
|
2094
|
+
addKeys(keys, property);
|
|
2095
|
+
if ("redundantProperties" in property && property.redundantProperties != null) for (const redundantProperty of property.redundantProperties) addKeys(keys, redundantProperty);
|
|
2096
|
+
}
|
|
2049
2097
|
/**
|
|
2050
2098
|
* Generates the TypeScript classes from the given types.
|
|
2051
2099
|
* @param types The types to generate classes from.
|
|
@@ -2053,33 +2101,21 @@ export function getEntityTypeById(id: string | URL): $EntityType | undefined {
|
|
|
2053
2101
|
*/
|
|
2054
2102
|
async function* generateClasses(types) {
|
|
2055
2103
|
validateTypeSchemas(types);
|
|
2056
|
-
const runtimeImports = [
|
|
2057
|
-
"canParseDecimal",
|
|
2058
|
-
"decodeMultibase",
|
|
2059
|
-
"type Decimal",
|
|
2060
|
-
"type DocumentLoader",
|
|
2061
|
-
"encodeMultibase",
|
|
2062
|
-
"exportMultibaseKey",
|
|
2063
|
-
"exportSpki",
|
|
2064
|
-
"getDocumentLoader",
|
|
2065
|
-
"importMultibaseKey",
|
|
2066
|
-
"importPem",
|
|
2067
|
-
"isDecimal",
|
|
2068
|
-
"LanguageString",
|
|
2069
|
-
"parseDecimal",
|
|
2070
|
-
"type RemoteDocument"
|
|
2071
|
-
];
|
|
2072
2104
|
yield "// deno-lint-ignore-file ban-unused-ignore no-explicit-any no-unused-vars prefer-const verbatim-module-syntax\n";
|
|
2073
2105
|
yield "import jsonld from \"@fedify/vocab-runtime/jsonld\";\n";
|
|
2074
2106
|
yield "import { getLogger } from \"@logtape/logtape\";\n";
|
|
2075
2107
|
yield `import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
2076
2108
|
from "@opentelemetry/api";\n`;
|
|
2077
|
-
yield `import {\n ${
|
|
2109
|
+
yield `import {\n ${RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime";\n`;
|
|
2110
|
+
yield `import {\n ${INTERNAL_RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime/internal/jsonld-cache";\n`;
|
|
2078
2111
|
yield `import {
|
|
2079
2112
|
isTemporalDuration,
|
|
2080
2113
|
isTemporalInstant,
|
|
2081
2114
|
} from "@fedify/vocab-runtime/temporal";\n`;
|
|
2082
|
-
|
|
2115
|
+
const portableIriKeys = new Set(["@id", "id"]);
|
|
2116
|
+
for (const type of Object.values(types)) for (const property of type.properties) addPortableIriKeys(portableIriKeys, property, types);
|
|
2117
|
+
yield "const PORTABLE_IRI_PATTERN = /^ap(?:\\+ef61)?:\\/\\//i;\n";
|
|
2118
|
+
yield `const PORTABLE_IRI_KEYS: ReadonlySet<string> = new Set(${JSON.stringify([...portableIriKeys].sort())});\n\n`;
|
|
2083
2119
|
const moduleVarNames = /* @__PURE__ */ new Map();
|
|
2084
2120
|
const sorted = sortTopologically(types);
|
|
2085
2121
|
for (const typeUri of sorted) for (const property of types[typeUri].properties) {
|