@fedify/vocab-tools 2.1.0-dev.565 → 2.1.0-dev.599
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 +65 -16
- package/dist/mod.js +65 -16
- package/package.json +1 -1
- package/src/__snapshots__/class.test.ts.deno.snap +7 -4
- package/src/__snapshots__/class.test.ts.node.snap +7 -4
- package/src/__snapshots__/class.test.ts.snap +7 -4
- package/src/class.test.ts +83 -2
- package/src/class.ts +23 -16
- package/src/schema.ts +32 -0
- package/src/type.ts +24 -0
package/deno.json
CHANGED
package/dist/mod.cjs
CHANGED
|
@@ -31,7 +31,7 @@ const es_toolkit = __toESM(require("es-toolkit"));
|
|
|
31
31
|
|
|
32
32
|
//#region deno.json
|
|
33
33
|
var name = "@fedify/vocab-tools";
|
|
34
|
-
var version = "2.1.0-dev.
|
|
34
|
+
var version = "2.1.0-dev.599+1ff26884";
|
|
35
35
|
var license = "MIT";
|
|
36
36
|
var exports$1 = { ".": "./src/mod.ts" };
|
|
37
37
|
var author = {
|
|
@@ -163,6 +163,30 @@ const scalarTypes = {
|
|
|
163
163
|
return `${v}["@value"]`;
|
|
164
164
|
}
|
|
165
165
|
},
|
|
166
|
+
"http://www.w3.org/2001/XMLSchema#decimal": {
|
|
167
|
+
name: "Decimal",
|
|
168
|
+
typeGuard(v) {
|
|
169
|
+
return `typeof ${v} === "string" && isDecimal(${v})`;
|
|
170
|
+
},
|
|
171
|
+
encoder(v) {
|
|
172
|
+
return `{
|
|
173
|
+
"@type": "http://www.w3.org/2001/XMLSchema#decimal",
|
|
174
|
+
"@value": ${v},
|
|
175
|
+
}`;
|
|
176
|
+
},
|
|
177
|
+
compactEncoder(v) {
|
|
178
|
+
return v;
|
|
179
|
+
},
|
|
180
|
+
dataCheck(v) {
|
|
181
|
+
return `typeof ${v} === "object" && "@type" in ${v}
|
|
182
|
+
&& ${v}["@type"] === "http://www.w3.org/2001/XMLSchema#decimal"
|
|
183
|
+
&& "@value" in ${v} && typeof ${v}["@value"] === "string"
|
|
184
|
+
&& canParseDecimal(${v}["@value"])`;
|
|
185
|
+
},
|
|
186
|
+
decoder(v) {
|
|
187
|
+
return `parseDecimal(${v}["@value"])`;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
166
190
|
"http://www.w3.org/2001/XMLSchema#string": {
|
|
167
191
|
name: "string",
|
|
168
192
|
typeGuard(v) {
|
|
@@ -632,6 +656,26 @@ function hasSingularAccessor(property) {
|
|
|
632
656
|
if (property.functional === true) return true;
|
|
633
657
|
return isNonFunctionalProperty(property) && property.singularAccessor === true;
|
|
634
658
|
}
|
|
659
|
+
const XSD_STRING_URI = "http://www.w3.org/2001/XMLSchema#string";
|
|
660
|
+
const XSD_DECIMAL_URI = "http://www.w3.org/2001/XMLSchema#decimal";
|
|
661
|
+
/**
|
|
662
|
+
* Validates schema combinations that cannot be represented safely by the
|
|
663
|
+
* generated code.
|
|
664
|
+
*
|
|
665
|
+
* In particular, `xsd:string` and `xsd:decimal` cannot coexist in the same
|
|
666
|
+
* property range because both are represented as runtime strings, which makes
|
|
667
|
+
* JSON-LD serialization ambiguous and order-dependent.
|
|
668
|
+
*
|
|
669
|
+
* @param types The loaded type schemas to validate.
|
|
670
|
+
* @throws {TypeError} Thrown when an unsupported range combination is found.
|
|
671
|
+
*/
|
|
672
|
+
function validateTypeSchemas(types) {
|
|
673
|
+
for (const type of Object.values(types)) for (const property of type.properties) {
|
|
674
|
+
const hasString = property.range.includes(XSD_STRING_URI);
|
|
675
|
+
const hasDecimal = property.range.includes(XSD_DECIMAL_URI);
|
|
676
|
+
if (hasString && hasDecimal) throw new TypeError(`The property ${type.name}.${property.singularName} cannot have both xsd:string and xsd:decimal in its range because the generated encoder cannot disambiguate them at runtime.`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
635
679
|
/**
|
|
636
680
|
* An error that occurred while loading a schema file.
|
|
637
681
|
*/
|
|
@@ -1914,24 +1958,29 @@ async function* generateClass(typeUri, types) {
|
|
|
1914
1958
|
* @returns The source code of the generated classes.
|
|
1915
1959
|
*/
|
|
1916
1960
|
async function* generateClasses(types) {
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1961
|
+
validateTypeSchemas(types);
|
|
1962
|
+
const runtimeImports = [
|
|
1963
|
+
"canParseDecimal",
|
|
1964
|
+
"decodeMultibase",
|
|
1965
|
+
"type Decimal",
|
|
1966
|
+
"type DocumentLoader",
|
|
1967
|
+
"encodeMultibase",
|
|
1968
|
+
"exportMultibaseKey",
|
|
1969
|
+
"exportSpki",
|
|
1970
|
+
"getDocumentLoader",
|
|
1971
|
+
"importMultibaseKey",
|
|
1972
|
+
"importPem",
|
|
1973
|
+
"isDecimal",
|
|
1974
|
+
"LanguageString",
|
|
1975
|
+
"parseDecimal",
|
|
1976
|
+
"type RemoteDocument"
|
|
1977
|
+
];
|
|
1978
|
+
yield "// deno-lint-ignore-file ban-unused-ignore no-unused-vars prefer-const verbatim-module-syntax\n";
|
|
1979
|
+
yield "import jsonld from \"@fedify/vocab-runtime/jsonld\";\n";
|
|
1920
1980
|
yield "import { getLogger } from \"@logtape/logtape\";\n";
|
|
1921
1981
|
yield `import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
1922
1982
|
from "@opentelemetry/api";\n`;
|
|
1923
|
-
yield `import {
|
|
1924
|
-
decodeMultibase,
|
|
1925
|
-
type DocumentLoader,
|
|
1926
|
-
encodeMultibase,
|
|
1927
|
-
exportMultibaseKey,
|
|
1928
|
-
exportSpki,
|
|
1929
|
-
getDocumentLoader,
|
|
1930
|
-
importMultibaseKey,
|
|
1931
|
-
importPem,
|
|
1932
|
-
LanguageString,
|
|
1933
|
-
type RemoteDocument,
|
|
1934
|
-
} from "@fedify/vocab-runtime";\n`;
|
|
1983
|
+
yield `import {\n ${runtimeImports.join(",\n ")}\n} from "@fedify/vocab-runtime";\n`;
|
|
1935
1984
|
yield "\n\n";
|
|
1936
1985
|
const sorted = sortTopologically(types);
|
|
1937
1986
|
for (const typeUri of sorted) for await (const code of generateClass(typeUri, types)) yield code;
|
package/dist/mod.js
CHANGED
|
@@ -8,7 +8,7 @@ import { pascalCase } from "es-toolkit";
|
|
|
8
8
|
|
|
9
9
|
//#region deno.json
|
|
10
10
|
var name = "@fedify/vocab-tools";
|
|
11
|
-
var version = "2.1.0-dev.
|
|
11
|
+
var version = "2.1.0-dev.599+1ff26884";
|
|
12
12
|
var license = "MIT";
|
|
13
13
|
var exports = { ".": "./src/mod.ts" };
|
|
14
14
|
var author = {
|
|
@@ -140,6 +140,30 @@ const scalarTypes = {
|
|
|
140
140
|
return `${v}["@value"]`;
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
|
+
"http://www.w3.org/2001/XMLSchema#decimal": {
|
|
144
|
+
name: "Decimal",
|
|
145
|
+
typeGuard(v) {
|
|
146
|
+
return `typeof ${v} === "string" && isDecimal(${v})`;
|
|
147
|
+
},
|
|
148
|
+
encoder(v) {
|
|
149
|
+
return `{
|
|
150
|
+
"@type": "http://www.w3.org/2001/XMLSchema#decimal",
|
|
151
|
+
"@value": ${v},
|
|
152
|
+
}`;
|
|
153
|
+
},
|
|
154
|
+
compactEncoder(v) {
|
|
155
|
+
return v;
|
|
156
|
+
},
|
|
157
|
+
dataCheck(v) {
|
|
158
|
+
return `typeof ${v} === "object" && "@type" in ${v}
|
|
159
|
+
&& ${v}["@type"] === "http://www.w3.org/2001/XMLSchema#decimal"
|
|
160
|
+
&& "@value" in ${v} && typeof ${v}["@value"] === "string"
|
|
161
|
+
&& canParseDecimal(${v}["@value"])`;
|
|
162
|
+
},
|
|
163
|
+
decoder(v) {
|
|
164
|
+
return `parseDecimal(${v}["@value"])`;
|
|
165
|
+
}
|
|
166
|
+
},
|
|
143
167
|
"http://www.w3.org/2001/XMLSchema#string": {
|
|
144
168
|
name: "string",
|
|
145
169
|
typeGuard(v) {
|
|
@@ -609,6 +633,26 @@ function hasSingularAccessor(property) {
|
|
|
609
633
|
if (property.functional === true) return true;
|
|
610
634
|
return isNonFunctionalProperty(property) && property.singularAccessor === true;
|
|
611
635
|
}
|
|
636
|
+
const XSD_STRING_URI = "http://www.w3.org/2001/XMLSchema#string";
|
|
637
|
+
const XSD_DECIMAL_URI = "http://www.w3.org/2001/XMLSchema#decimal";
|
|
638
|
+
/**
|
|
639
|
+
* Validates schema combinations that cannot be represented safely by the
|
|
640
|
+
* generated code.
|
|
641
|
+
*
|
|
642
|
+
* In particular, `xsd:string` and `xsd:decimal` cannot coexist in the same
|
|
643
|
+
* property range because both are represented as runtime strings, which makes
|
|
644
|
+
* JSON-LD serialization ambiguous and order-dependent.
|
|
645
|
+
*
|
|
646
|
+
* @param types The loaded type schemas to validate.
|
|
647
|
+
* @throws {TypeError} Thrown when an unsupported range combination is found.
|
|
648
|
+
*/
|
|
649
|
+
function validateTypeSchemas(types) {
|
|
650
|
+
for (const type of Object.values(types)) for (const property of type.properties) {
|
|
651
|
+
const hasString = property.range.includes(XSD_STRING_URI);
|
|
652
|
+
const hasDecimal = property.range.includes(XSD_DECIMAL_URI);
|
|
653
|
+
if (hasString && hasDecimal) throw new TypeError(`The property ${type.name}.${property.singularName} cannot have both xsd:string and xsd:decimal in its range because the generated encoder cannot disambiguate them at runtime.`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
612
656
|
/**
|
|
613
657
|
* An error that occurred while loading a schema file.
|
|
614
658
|
*/
|
|
@@ -1891,24 +1935,29 @@ async function* generateClass(typeUri, types) {
|
|
|
1891
1935
|
* @returns The source code of the generated classes.
|
|
1892
1936
|
*/
|
|
1893
1937
|
async function* generateClasses(types) {
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1938
|
+
validateTypeSchemas(types);
|
|
1939
|
+
const runtimeImports = [
|
|
1940
|
+
"canParseDecimal",
|
|
1941
|
+
"decodeMultibase",
|
|
1942
|
+
"type Decimal",
|
|
1943
|
+
"type DocumentLoader",
|
|
1944
|
+
"encodeMultibase",
|
|
1945
|
+
"exportMultibaseKey",
|
|
1946
|
+
"exportSpki",
|
|
1947
|
+
"getDocumentLoader",
|
|
1948
|
+
"importMultibaseKey",
|
|
1949
|
+
"importPem",
|
|
1950
|
+
"isDecimal",
|
|
1951
|
+
"LanguageString",
|
|
1952
|
+
"parseDecimal",
|
|
1953
|
+
"type RemoteDocument"
|
|
1954
|
+
];
|
|
1955
|
+
yield "// deno-lint-ignore-file ban-unused-ignore no-unused-vars prefer-const verbatim-module-syntax\n";
|
|
1956
|
+
yield "import jsonld from \"@fedify/vocab-runtime/jsonld\";\n";
|
|
1897
1957
|
yield "import { getLogger } from \"@logtape/logtape\";\n";
|
|
1898
1958
|
yield `import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
1899
1959
|
from "@opentelemetry/api";\n`;
|
|
1900
|
-
yield `import {
|
|
1901
|
-
decodeMultibase,
|
|
1902
|
-
type DocumentLoader,
|
|
1903
|
-
encodeMultibase,
|
|
1904
|
-
exportMultibaseKey,
|
|
1905
|
-
exportSpki,
|
|
1906
|
-
getDocumentLoader,
|
|
1907
|
-
importMultibaseKey,
|
|
1908
|
-
importPem,
|
|
1909
|
-
LanguageString,
|
|
1910
|
-
type RemoteDocument,
|
|
1911
|
-
} from "@fedify/vocab-runtime";\n`;
|
|
1960
|
+
yield `import {\n ${runtimeImports.join(",\n ")}\n} from "@fedify/vocab-runtime";\n`;
|
|
1912
1961
|
yield "\n\n";
|
|
1913
1962
|
const sorted = sortTopologically(types);
|
|
1914
1963
|
for (const typeUri of sorted) for await (const code of generateClass(typeUri, types)) yield code;
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export const snapshot = {};
|
|
2
2
|
|
|
3
3
|
snapshot[`generateClasses() 1`] = `
|
|
4
|
-
"// deno-lint-ignore-file ban-unused-ignore prefer-const
|
|
5
|
-
|
|
6
|
-
import jsonld from \\"jsonld\\";
|
|
4
|
+
"// deno-lint-ignore-file ban-unused-ignore no-unused-vars prefer-const verbatim-module-syntax
|
|
5
|
+
import jsonld from \\"@fedify/vocab-runtime/jsonld\\";
|
|
7
6
|
import { getLogger } from \\"@logtape/logtape\\";
|
|
8
7
|
import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
9
8
|
from \\"@opentelemetry/api\\";
|
|
10
9
|
import {
|
|
10
|
+
canParseDecimal,
|
|
11
11
|
decodeMultibase,
|
|
12
|
+
type Decimal,
|
|
12
13
|
type DocumentLoader,
|
|
13
14
|
encodeMultibase,
|
|
14
15
|
exportMultibaseKey,
|
|
@@ -16,8 +17,10 @@ import {
|
|
|
16
17
|
getDocumentLoader,
|
|
17
18
|
importMultibaseKey,
|
|
18
19
|
importPem,
|
|
20
|
+
isDecimal,
|
|
19
21
|
LanguageString,
|
|
20
|
-
|
|
22
|
+
parseDecimal,
|
|
23
|
+
type RemoteDocument
|
|
21
24
|
} from \\"@fedify/vocab-runtime\\";
|
|
22
25
|
|
|
23
26
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
exports[`generateClasses() 1`] = `
|
|
2
|
-
"// deno-lint-ignore-file ban-unused-ignore prefer-const
|
|
3
|
-
|
|
4
|
-
import jsonld from \\"jsonld\\";
|
|
2
|
+
"// deno-lint-ignore-file ban-unused-ignore no-unused-vars prefer-const verbatim-module-syntax
|
|
3
|
+
import jsonld from \\"@fedify/vocab-runtime/jsonld\\";
|
|
5
4
|
import { getLogger } from \\"@logtape/logtape\\";
|
|
6
5
|
import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
7
6
|
from \\"@opentelemetry/api\\";
|
|
8
7
|
import {
|
|
8
|
+
canParseDecimal,
|
|
9
9
|
decodeMultibase,
|
|
10
|
+
type Decimal,
|
|
10
11
|
type DocumentLoader,
|
|
11
12
|
encodeMultibase,
|
|
12
13
|
exportMultibaseKey,
|
|
@@ -14,8 +15,10 @@ import {
|
|
|
14
15
|
getDocumentLoader,
|
|
15
16
|
importMultibaseKey,
|
|
16
17
|
importPem,
|
|
18
|
+
isDecimal,
|
|
17
19
|
LanguageString,
|
|
18
|
-
|
|
20
|
+
parseDecimal,
|
|
21
|
+
type RemoteDocument
|
|
19
22
|
} from \\"@fedify/vocab-runtime\\";
|
|
20
23
|
|
|
21
24
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
|
|
2
2
|
|
|
3
3
|
exports[`generateClasses() 1`] = `
|
|
4
|
-
"// deno-lint-ignore-file ban-unused-ignore prefer-const
|
|
5
|
-
|
|
6
|
-
import jsonld from "jsonld";
|
|
4
|
+
"// deno-lint-ignore-file ban-unused-ignore no-unused-vars prefer-const verbatim-module-syntax
|
|
5
|
+
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
7
6
|
import { getLogger } from "@logtape/logtape";
|
|
8
7
|
import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
9
8
|
from "@opentelemetry/api";
|
|
10
9
|
import {
|
|
10
|
+
canParseDecimal,
|
|
11
11
|
decodeMultibase,
|
|
12
|
+
type Decimal,
|
|
12
13
|
type DocumentLoader,
|
|
13
14
|
encodeMultibase,
|
|
14
15
|
exportMultibaseKey,
|
|
@@ -16,8 +17,10 @@ import {
|
|
|
16
17
|
getDocumentLoader,
|
|
17
18
|
importMultibaseKey,
|
|
18
19
|
importPem,
|
|
20
|
+
isDecimal,
|
|
19
21
|
LanguageString,
|
|
20
|
-
|
|
22
|
+
parseDecimal,
|
|
23
|
+
type RemoteDocument
|
|
21
24
|
} from "@fedify/vocab-runtime";
|
|
22
25
|
|
|
23
26
|
|
package/src/class.test.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { deepStrictEqual } from "node:assert";
|
|
1
|
+
import { deepStrictEqual, match, rejects } from "node:assert";
|
|
2
2
|
import { basename, dirname, extname, join } from "node:path";
|
|
3
3
|
import { test } from "node:test";
|
|
4
4
|
import metadata from "../deno.json" with { type: "json" };
|
|
5
5
|
import { generateClasses, sortTopologically } from "./class.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { getDataCheck } from "./type.ts";
|
|
7
|
+
import { loadSchemaFiles, type TypeSchema } from "./schema.ts";
|
|
7
8
|
|
|
8
9
|
test("sortTopologically()", () => {
|
|
9
10
|
const sorted = sortTopologically({
|
|
@@ -64,6 +65,61 @@ test("sortTopologically()", () => {
|
|
|
64
65
|
);
|
|
65
66
|
});
|
|
66
67
|
|
|
68
|
+
test("generateClasses() imports the browser-safe jsonld entrypoint", async () => {
|
|
69
|
+
const entireCode = await getEntireCode();
|
|
70
|
+
match(entireCode, /import jsonld from "@fedify\/vocab-runtime\/jsonld";/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("generateClasses() imports Decimal helpers for xsd:decimal", async () => {
|
|
74
|
+
const entireCode = await getDecimalFixtureCode();
|
|
75
|
+
match(entireCode, /canParseDecimal,/);
|
|
76
|
+
match(entireCode, /isDecimal,/);
|
|
77
|
+
match(entireCode, /type Decimal,/);
|
|
78
|
+
match(entireCode, /parseDecimal/);
|
|
79
|
+
match(entireCode, /amount\?: Decimal \| null;/);
|
|
80
|
+
match(entireCode, /isDecimal\(values\.amount\)/);
|
|
81
|
+
match(entireCode, /parseDecimal\(v\["@value"\]\)/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("getDataCheck() uses canParseDecimal() for xsd:decimal", () => {
|
|
85
|
+
const check = getDataCheck(
|
|
86
|
+
"http://www.w3.org/2001/XMLSchema#decimal",
|
|
87
|
+
{},
|
|
88
|
+
"v",
|
|
89
|
+
);
|
|
90
|
+
match(check, /canParseDecimal\(v\["@value"\]\)/);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("generateClasses() rejects xsd:string and xsd:decimal unions", async () => {
|
|
94
|
+
await rejects(
|
|
95
|
+
Array.fromAsync(generateClasses({
|
|
96
|
+
"https://example.com/measure": {
|
|
97
|
+
name: "Measure",
|
|
98
|
+
uri: "https://example.com/measure",
|
|
99
|
+
compactName: "Measure",
|
|
100
|
+
entity: false,
|
|
101
|
+
description: "A measure.",
|
|
102
|
+
properties: [
|
|
103
|
+
{
|
|
104
|
+
singularName: "amount",
|
|
105
|
+
functional: true,
|
|
106
|
+
compactName: "amount",
|
|
107
|
+
uri: "https://example.com/amount",
|
|
108
|
+
description: "An exact decimal amount.",
|
|
109
|
+
range: [
|
|
110
|
+
"http://www.w3.org/2001/XMLSchema#decimal",
|
|
111
|
+
"http://www.w3.org/2001/XMLSchema#string",
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
defaultContext:
|
|
116
|
+
"https://example.com/context" as TypeSchema["defaultContext"],
|
|
117
|
+
},
|
|
118
|
+
})),
|
|
119
|
+
/cannot have both xsd:string and xsd:decimal in its range/,
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
67
123
|
if ("Deno" in globalThis) {
|
|
68
124
|
const { assertSnapshot } = await import("@std/testing/snapshot");
|
|
69
125
|
Deno.test("generateClasses()", async (t) => {
|
|
@@ -96,6 +152,31 @@ async function getEntireCode() {
|
|
|
96
152
|
return entireCode;
|
|
97
153
|
}
|
|
98
154
|
|
|
155
|
+
async function getDecimalFixtureCode() {
|
|
156
|
+
const types: Record<string, TypeSchema> = {
|
|
157
|
+
"https://example.com/measure": {
|
|
158
|
+
name: "Measure",
|
|
159
|
+
uri: "https://example.com/measure",
|
|
160
|
+
compactName: "Measure",
|
|
161
|
+
entity: false,
|
|
162
|
+
description: "A measure.",
|
|
163
|
+
properties: [
|
|
164
|
+
{
|
|
165
|
+
singularName: "amount",
|
|
166
|
+
functional: true,
|
|
167
|
+
compactName: "amount",
|
|
168
|
+
uri: "https://example.com/amount",
|
|
169
|
+
description: "An exact decimal amount.",
|
|
170
|
+
range: ["http://www.w3.org/2001/XMLSchema#decimal"],
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
defaultContext:
|
|
174
|
+
"https://example.com/context" as TypeSchema["defaultContext"],
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
return (await Array.fromAsync(generateClasses(types))).join("");
|
|
178
|
+
}
|
|
179
|
+
|
|
99
180
|
async function changeNodeSnapshotPath() {
|
|
100
181
|
const { snapshot } = await import("node:test");
|
|
101
182
|
snapshot.setResolveSnapshotPath(
|
package/src/class.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { generateCloner, generateConstructor } from "./constructor.ts";
|
|
|
3
3
|
import { generateFields } from "./field.ts";
|
|
4
4
|
import { generateInspector, generateInspectorPostClass } from "./inspector.ts";
|
|
5
5
|
import { generateProperties } from "./property.ts";
|
|
6
|
-
import type
|
|
6
|
+
import { type TypeSchema, validateTypeSchemas } from "./schema.ts";
|
|
7
7
|
import { emitOverride } from "./type.ts";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -117,24 +117,31 @@ async function* generateClass(
|
|
|
117
117
|
export async function* generateClasses(
|
|
118
118
|
types: Record<string, TypeSchema>,
|
|
119
119
|
): AsyncIterable<string> {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
validateTypeSchemas(types);
|
|
121
|
+
const runtimeImports = [
|
|
122
|
+
"canParseDecimal",
|
|
123
|
+
"decodeMultibase",
|
|
124
|
+
"type Decimal",
|
|
125
|
+
"type DocumentLoader",
|
|
126
|
+
"encodeMultibase",
|
|
127
|
+
"exportMultibaseKey",
|
|
128
|
+
"exportSpki",
|
|
129
|
+
"getDocumentLoader",
|
|
130
|
+
"importMultibaseKey",
|
|
131
|
+
"importPem",
|
|
132
|
+
"isDecimal",
|
|
133
|
+
"LanguageString",
|
|
134
|
+
"parseDecimal",
|
|
135
|
+
"type RemoteDocument",
|
|
136
|
+
];
|
|
137
|
+
yield "// deno-lint-ignore-file ban-unused-ignore no-unused-vars prefer-const verbatim-module-syntax\n";
|
|
138
|
+
yield 'import jsonld from "@fedify/vocab-runtime/jsonld";\n';
|
|
123
139
|
yield 'import { getLogger } from "@logtape/logtape";\n';
|
|
124
140
|
yield `import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
125
141
|
from "@opentelemetry/api";\n`;
|
|
126
|
-
yield `import {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
encodeMultibase,
|
|
130
|
-
exportMultibaseKey,
|
|
131
|
-
exportSpki,
|
|
132
|
-
getDocumentLoader,
|
|
133
|
-
importMultibaseKey,
|
|
134
|
-
importPem,
|
|
135
|
-
LanguageString,
|
|
136
|
-
type RemoteDocument,
|
|
137
|
-
} from "@fedify/vocab-runtime";\n`;
|
|
142
|
+
yield `import {\n ${
|
|
143
|
+
runtimeImports.join(",\n ")
|
|
144
|
+
}\n} from "@fedify/vocab-runtime";\n`;
|
|
138
145
|
yield "\n\n";
|
|
139
146
|
const sorted = sortTopologically(types);
|
|
140
147
|
for (const typeUri of sorted) {
|
package/src/schema.ts
CHANGED
|
@@ -240,6 +240,38 @@ export function hasSingularAccessor(property: PropertySchema): boolean {
|
|
|
240
240
|
property.singularAccessor === true;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
const XSD_STRING_URI = "http://www.w3.org/2001/XMLSchema#string";
|
|
244
|
+
const XSD_DECIMAL_URI = "http://www.w3.org/2001/XMLSchema#decimal";
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Validates schema combinations that cannot be represented safely by the
|
|
248
|
+
* generated code.
|
|
249
|
+
*
|
|
250
|
+
* In particular, `xsd:string` and `xsd:decimal` cannot coexist in the same
|
|
251
|
+
* property range because both are represented as runtime strings, which makes
|
|
252
|
+
* JSON-LD serialization ambiguous and order-dependent.
|
|
253
|
+
*
|
|
254
|
+
* @param types The loaded type schemas to validate.
|
|
255
|
+
* @throws {TypeError} Thrown when an unsupported range combination is found.
|
|
256
|
+
*/
|
|
257
|
+
export function validateTypeSchemas(
|
|
258
|
+
types: Record<string, TypeSchema>,
|
|
259
|
+
): void {
|
|
260
|
+
for (const type of Object.values(types)) {
|
|
261
|
+
for (const property of type.properties) {
|
|
262
|
+
const hasString = property.range.includes(XSD_STRING_URI);
|
|
263
|
+
const hasDecimal = property.range.includes(XSD_DECIMAL_URI);
|
|
264
|
+
if (hasString && hasDecimal) {
|
|
265
|
+
throw new TypeError(
|
|
266
|
+
`The property ${type.name}.${property.singularName} cannot have ` +
|
|
267
|
+
`both xsd:string and xsd:decimal in its range because the ` +
|
|
268
|
+
`generated encoder cannot disambiguate them at runtime.`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
243
275
|
/**
|
|
244
276
|
* An error that occurred while loading a schema file.
|
|
245
277
|
*/
|
package/src/type.ts
CHANGED
|
@@ -108,6 +108,30 @@ const scalarTypes: Record<string, ScalarType> = {
|
|
|
108
108
|
return `${v}["@value"]`;
|
|
109
109
|
},
|
|
110
110
|
},
|
|
111
|
+
"http://www.w3.org/2001/XMLSchema#decimal": {
|
|
112
|
+
name: "Decimal",
|
|
113
|
+
typeGuard(v) {
|
|
114
|
+
return `typeof ${v} === "string" && isDecimal(${v})`;
|
|
115
|
+
},
|
|
116
|
+
encoder(v) {
|
|
117
|
+
return `{
|
|
118
|
+
"@type": "http://www.w3.org/2001/XMLSchema#decimal",
|
|
119
|
+
"@value": ${v},
|
|
120
|
+
}`;
|
|
121
|
+
},
|
|
122
|
+
compactEncoder(v) {
|
|
123
|
+
return v;
|
|
124
|
+
},
|
|
125
|
+
dataCheck(v) {
|
|
126
|
+
return `typeof ${v} === "object" && "@type" in ${v}
|
|
127
|
+
&& ${v}["@type"] === "http://www.w3.org/2001/XMLSchema#decimal"
|
|
128
|
+
&& "@value" in ${v} && typeof ${v}["@value"] === "string"
|
|
129
|
+
&& canParseDecimal(${v}["@value"])`;
|
|
130
|
+
},
|
|
131
|
+
decoder(v) {
|
|
132
|
+
return `parseDecimal(${v}["@value"])`;
|
|
133
|
+
},
|
|
134
|
+
},
|
|
111
135
|
"http://www.w3.org/2001/XMLSchema#string": {
|
|
112
136
|
name: "string",
|
|
113
137
|
typeGuard(v) {
|