@fedify/vocab-tools 2.1.0-dev.592 → 2.1.0-dev.600
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 +64 -14
- package/dist/mod.js +64 -14
- package/package.json +1 -1
- package/src/__snapshots__/class.test.ts.deno.snap +6 -2
- package/src/__snapshots__/class.test.ts.node.snap +6 -2
- package/src/__snapshots__/class.test.ts.snap +6 -2
- package/src/class.test.ts +78 -2
- package/src/class.ts +22 -14
- 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.600+bfa25994";
|
|
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,23 +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
|
-
|
|
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";
|
|
1918
1979
|
yield "import jsonld from \"@fedify/vocab-runtime/jsonld\";\n";
|
|
1919
1980
|
yield "import { getLogger } from \"@logtape/logtape\";\n";
|
|
1920
1981
|
yield `import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
1921
1982
|
from "@opentelemetry/api";\n`;
|
|
1922
|
-
yield `import {
|
|
1923
|
-
decodeMultibase,
|
|
1924
|
-
type DocumentLoader,
|
|
1925
|
-
encodeMultibase,
|
|
1926
|
-
exportMultibaseKey,
|
|
1927
|
-
exportSpki,
|
|
1928
|
-
getDocumentLoader,
|
|
1929
|
-
importMultibaseKey,
|
|
1930
|
-
importPem,
|
|
1931
|
-
LanguageString,
|
|
1932
|
-
type RemoteDocument,
|
|
1933
|
-
} from "@fedify/vocab-runtime";\n`;
|
|
1983
|
+
yield `import {\n ${runtimeImports.join(",\n ")}\n} from "@fedify/vocab-runtime";\n`;
|
|
1934
1984
|
yield "\n\n";
|
|
1935
1985
|
const sorted = sortTopologically(types);
|
|
1936
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.600+bfa25994";
|
|
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,23 +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
|
-
|
|
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";
|
|
1895
1956
|
yield "import jsonld from \"@fedify/vocab-runtime/jsonld\";\n";
|
|
1896
1957
|
yield "import { getLogger } from \"@logtape/logtape\";\n";
|
|
1897
1958
|
yield `import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
1898
1959
|
from "@opentelemetry/api";\n`;
|
|
1899
|
-
yield `import {
|
|
1900
|
-
decodeMultibase,
|
|
1901
|
-
type DocumentLoader,
|
|
1902
|
-
encodeMultibase,
|
|
1903
|
-
exportMultibaseKey,
|
|
1904
|
-
exportSpki,
|
|
1905
|
-
getDocumentLoader,
|
|
1906
|
-
importMultibaseKey,
|
|
1907
|
-
importPem,
|
|
1908
|
-
LanguageString,
|
|
1909
|
-
type RemoteDocument,
|
|
1910
|
-
} from "@fedify/vocab-runtime";\n`;
|
|
1960
|
+
yield `import {\n ${runtimeImports.join(",\n ")}\n} from "@fedify/vocab-runtime";\n`;
|
|
1911
1961
|
yield "\n\n";
|
|
1912
1962
|
const sorted = sortTopologically(types);
|
|
1913
1963
|
for (const typeUri of sorted) for await (const code of generateClass(typeUri, types)) yield code;
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export const snapshot = {};
|
|
2
2
|
|
|
3
3
|
snapshot[`generateClasses() 1`] = `
|
|
4
|
-
"// deno-lint-ignore-file ban-unused-ignore prefer-const
|
|
4
|
+
"// deno-lint-ignore-file ban-unused-ignore no-unused-vars prefer-const verbatim-module-syntax
|
|
5
5
|
import jsonld from \\"@fedify/vocab-runtime/jsonld\\";
|
|
6
6
|
import { getLogger } from \\"@logtape/logtape\\";
|
|
7
7
|
import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
8
8
|
from \\"@opentelemetry/api\\";
|
|
9
9
|
import {
|
|
10
|
+
canParseDecimal,
|
|
10
11
|
decodeMultibase,
|
|
12
|
+
type Decimal,
|
|
11
13
|
type DocumentLoader,
|
|
12
14
|
encodeMultibase,
|
|
13
15
|
exportMultibaseKey,
|
|
@@ -15,8 +17,10 @@ import {
|
|
|
15
17
|
getDocumentLoader,
|
|
16
18
|
importMultibaseKey,
|
|
17
19
|
importPem,
|
|
20
|
+
isDecimal,
|
|
18
21
|
LanguageString,
|
|
19
|
-
|
|
22
|
+
parseDecimal,
|
|
23
|
+
type RemoteDocument
|
|
20
24
|
} from \\"@fedify/vocab-runtime\\";
|
|
21
25
|
|
|
22
26
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
exports[`generateClasses() 1`] = `
|
|
2
|
-
"// deno-lint-ignore-file ban-unused-ignore prefer-const
|
|
2
|
+
"// deno-lint-ignore-file ban-unused-ignore no-unused-vars prefer-const verbatim-module-syntax
|
|
3
3
|
import jsonld from \\"@fedify/vocab-runtime/jsonld\\";
|
|
4
4
|
import { getLogger } from \\"@logtape/logtape\\";
|
|
5
5
|
import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
6
6
|
from \\"@opentelemetry/api\\";
|
|
7
7
|
import {
|
|
8
|
+
canParseDecimal,
|
|
8
9
|
decodeMultibase,
|
|
10
|
+
type Decimal,
|
|
9
11
|
type DocumentLoader,
|
|
10
12
|
encodeMultibase,
|
|
11
13
|
exportMultibaseKey,
|
|
@@ -13,8 +15,10 @@ import {
|
|
|
13
15
|
getDocumentLoader,
|
|
14
16
|
importMultibaseKey,
|
|
15
17
|
importPem,
|
|
18
|
+
isDecimal,
|
|
16
19
|
LanguageString,
|
|
17
|
-
|
|
20
|
+
parseDecimal,
|
|
21
|
+
type RemoteDocument
|
|
18
22
|
} from \\"@fedify/vocab-runtime\\";
|
|
19
23
|
|
|
20
24
|
|
|
@@ -1,13 +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
|
|
4
|
+
"// deno-lint-ignore-file ban-unused-ignore no-unused-vars prefer-const verbatim-module-syntax
|
|
5
5
|
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
8
8
|
from "@opentelemetry/api";
|
|
9
9
|
import {
|
|
10
|
+
canParseDecimal,
|
|
10
11
|
decodeMultibase,
|
|
12
|
+
type Decimal,
|
|
11
13
|
type DocumentLoader,
|
|
12
14
|
encodeMultibase,
|
|
13
15
|
exportMultibaseKey,
|
|
@@ -15,8 +17,10 @@ import {
|
|
|
15
17
|
getDocumentLoader,
|
|
16
18
|
importMultibaseKey,
|
|
17
19
|
importPem,
|
|
20
|
+
isDecimal,
|
|
18
21
|
LanguageString,
|
|
19
|
-
|
|
22
|
+
parseDecimal,
|
|
23
|
+
type RemoteDocument
|
|
20
24
|
} from "@fedify/vocab-runtime";
|
|
21
25
|
|
|
22
26
|
|
package/src/class.test.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { deepStrictEqual, match } 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({
|
|
@@ -69,6 +70,56 @@ test("generateClasses() imports the browser-safe jsonld entrypoint", async () =>
|
|
|
69
70
|
match(entireCode, /import jsonld from "@fedify\/vocab-runtime\/jsonld";/);
|
|
70
71
|
});
|
|
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
|
+
|
|
72
123
|
if ("Deno" in globalThis) {
|
|
73
124
|
const { assertSnapshot } = await import("@std/testing/snapshot");
|
|
74
125
|
Deno.test("generateClasses()", async (t) => {
|
|
@@ -101,6 +152,31 @@ async function getEntireCode() {
|
|
|
101
152
|
return entireCode;
|
|
102
153
|
}
|
|
103
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
|
+
|
|
104
180
|
async function changeNodeSnapshotPath() {
|
|
105
181
|
const { snapshot } = await import("node:test");
|
|
106
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,23 +117,31 @@ async function* generateClass(
|
|
|
117
117
|
export async function* generateClasses(
|
|
118
118
|
types: Record<string, TypeSchema>,
|
|
119
119
|
): AsyncIterable<string> {
|
|
120
|
-
|
|
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";
|
|
121
138
|
yield 'import jsonld from "@fedify/vocab-runtime/jsonld";\n';
|
|
122
139
|
yield 'import { getLogger } from "@logtape/logtape";\n';
|
|
123
140
|
yield `import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
124
141
|
from "@opentelemetry/api";\n`;
|
|
125
|
-
yield `import {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
encodeMultibase,
|
|
129
|
-
exportMultibaseKey,
|
|
130
|
-
exportSpki,
|
|
131
|
-
getDocumentLoader,
|
|
132
|
-
importMultibaseKey,
|
|
133
|
-
importPem,
|
|
134
|
-
LanguageString,
|
|
135
|
-
type RemoteDocument,
|
|
136
|
-
} from "@fedify/vocab-runtime";\n`;
|
|
142
|
+
yield `import {\n ${
|
|
143
|
+
runtimeImports.join(",\n ")
|
|
144
|
+
}\n} from "@fedify/vocab-runtime";\n`;
|
|
137
145
|
yield "\n\n";
|
|
138
146
|
const sorted = sortTopologically(types);
|
|
139
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) {
|