@fedify/vocab-tools 2.0.0-pr.458.1785
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/LICENSE +20 -0
- package/README.md +26 -0
- package/deno.json +28 -0
- package/dist/mod.cjs +1941 -0
- package/dist/mod.d.cts +190 -0
- package/dist/mod.d.ts +190 -0
- package/dist/mod.js +1916 -0
- package/package.json +49 -0
- package/src/__snapshots__/class.test.ts.deno.snap +81555 -0
- package/src/__snapshots__/class.test.ts.node.snap +81553 -0
- package/src/__snapshots__/class.test.ts.snap +81555 -0
- package/src/class.test.ts +122 -0
- package/src/class.ts +140 -0
- package/src/codec.ts +485 -0
- package/src/constructor.ts +337 -0
- package/src/field.ts +46 -0
- package/src/fs.test.ts +31 -0
- package/src/fs.ts +21 -0
- package/src/generate.ts +24 -0
- package/src/inspector.ts +96 -0
- package/src/mod.ts +7 -0
- package/src/property.ts +397 -0
- package/src/schema.test.ts +203 -0
- package/src/schema.ts +321 -0
- package/src/schema.yaml +247 -0
- package/src/type.ts +643 -0
- package/tsdown.config.ts +9 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { getFieldName } from "./field.ts";
|
|
2
|
+
import { hasSingularAccessor, isNonFunctionalProperty } from "./schema.ts";
|
|
3
|
+
import type { PropertySchema, TypeSchema } from "./schema.ts";
|
|
4
|
+
import {
|
|
5
|
+
areAllScalarTypes,
|
|
6
|
+
emitOverride,
|
|
7
|
+
getTypeGuards,
|
|
8
|
+
getTypeNames,
|
|
9
|
+
} from "./type.ts";
|
|
10
|
+
|
|
11
|
+
function generateParameterType(
|
|
12
|
+
property: PropertySchema,
|
|
13
|
+
types: Record<string, TypeSchema>,
|
|
14
|
+
): string {
|
|
15
|
+
const range = property.range;
|
|
16
|
+
const scalar = areAllScalarTypes(range, types);
|
|
17
|
+
const code: string[] = [];
|
|
18
|
+
if (hasSingularAccessor(property)) {
|
|
19
|
+
if (scalar) {
|
|
20
|
+
code.push(
|
|
21
|
+
`${property.singularName}?: ${getTypeNames(range, types)} | null;`,
|
|
22
|
+
);
|
|
23
|
+
} else {
|
|
24
|
+
code.push(
|
|
25
|
+
`${property.singularName}?: ${
|
|
26
|
+
getTypeNames(range, types)
|
|
27
|
+
} | URL | null;`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (isNonFunctionalProperty(property)) {
|
|
32
|
+
if (scalar) {
|
|
33
|
+
code.push(
|
|
34
|
+
`${property.pluralName}?: (${getTypeNames(range, types, true)})[];`,
|
|
35
|
+
);
|
|
36
|
+
} else {
|
|
37
|
+
code.push(
|
|
38
|
+
`${property.pluralName}?: (${getTypeNames(range, types)} | URL)[];`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return code.join("\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function* generateParametersType(
|
|
46
|
+
typeUri: string,
|
|
47
|
+
types: Record<string, TypeSchema>,
|
|
48
|
+
parentheses = true,
|
|
49
|
+
excludeProperties: string[] = [],
|
|
50
|
+
): AsyncIterable<string> {
|
|
51
|
+
const type = types[typeUri];
|
|
52
|
+
if (parentheses) yield "{\n";
|
|
53
|
+
if (type.extends == null) {
|
|
54
|
+
yield `id?: URL | null;\n`;
|
|
55
|
+
} else {
|
|
56
|
+
for await (
|
|
57
|
+
const code of generateParametersType(type.extends, types, false, [
|
|
58
|
+
...excludeProperties,
|
|
59
|
+
...type.properties.map((p) => p.singularName),
|
|
60
|
+
])
|
|
61
|
+
) {
|
|
62
|
+
yield code;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const property of type.properties) {
|
|
66
|
+
if (excludeProperties.includes(property.singularName)) continue;
|
|
67
|
+
yield generateParameterType(property, types);
|
|
68
|
+
}
|
|
69
|
+
if (parentheses) yield "}\n";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function* generateConstructor(
|
|
73
|
+
typeUri: string,
|
|
74
|
+
types: Record<string, TypeSchema>,
|
|
75
|
+
): AsyncIterable<string> {
|
|
76
|
+
const type = types[typeUri];
|
|
77
|
+
yield `
|
|
78
|
+
/**
|
|
79
|
+
* Constructs a new instance of ${type.name} with the given values.
|
|
80
|
+
* @param values The values to initialize the instance with.
|
|
81
|
+
* @param options The options to use for initialization.
|
|
82
|
+
*/
|
|
83
|
+
constructor(
|
|
84
|
+
values:
|
|
85
|
+
`;
|
|
86
|
+
for await (const code of generateParametersType(typeUri, types)) yield code;
|
|
87
|
+
yield `,
|
|
88
|
+
options: {
|
|
89
|
+
documentLoader?: DocumentLoader,
|
|
90
|
+
contextLoader?: DocumentLoader,
|
|
91
|
+
tracerProvider?: TracerProvider,
|
|
92
|
+
} = {},
|
|
93
|
+
) {
|
|
94
|
+
`;
|
|
95
|
+
if (type.extends == null) {
|
|
96
|
+
yield `
|
|
97
|
+
this.#documentLoader = options.documentLoader;
|
|
98
|
+
this.#contextLoader = options.contextLoader;
|
|
99
|
+
this.#tracerProvider = options.tracerProvider;
|
|
100
|
+
if ("$warning" in options) {
|
|
101
|
+
this.#warning = options.$warning as unknown as {
|
|
102
|
+
category: string[];
|
|
103
|
+
message: string;
|
|
104
|
+
values?: Record<string, unknown>;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (values.id == null || values.id instanceof URL) {
|
|
108
|
+
this.id = values.id ?? null;
|
|
109
|
+
} else {
|
|
110
|
+
throw new TypeError("The id must be a URL.");
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
} else {
|
|
114
|
+
yield "super(values, options);";
|
|
115
|
+
}
|
|
116
|
+
for (const property of type.properties) {
|
|
117
|
+
const fieldName = await getFieldName(property.uri);
|
|
118
|
+
const trustFieldName = await getFieldName(property.uri, "#_trust");
|
|
119
|
+
const allScalarTypes = areAllScalarTypes(property.range, types);
|
|
120
|
+
if (hasSingularAccessor(property)) {
|
|
121
|
+
let typeGuards = getTypeGuards(
|
|
122
|
+
property.range,
|
|
123
|
+
types,
|
|
124
|
+
`values.${property.singularName}`,
|
|
125
|
+
);
|
|
126
|
+
let typeNames = getTypeNames(property.range, types);
|
|
127
|
+
const scalar = areAllScalarTypes(property.range, types);
|
|
128
|
+
if (!scalar) {
|
|
129
|
+
typeGuards =
|
|
130
|
+
`${typeGuards} || values.${property.singularName} instanceof URL`;
|
|
131
|
+
typeNames = `${typeNames} | URL`;
|
|
132
|
+
}
|
|
133
|
+
yield `
|
|
134
|
+
if ("${property.singularName}" in values && \
|
|
135
|
+
values.${property.singularName} != null) {
|
|
136
|
+
if (${typeGuards}) {
|
|
137
|
+
// @ts-ignore: type is checked above.
|
|
138
|
+
this.${fieldName} = [values.${property.singularName}];
|
|
139
|
+
`;
|
|
140
|
+
if (!allScalarTypes) yield `this.${trustFieldName}.add(0);`;
|
|
141
|
+
yield `
|
|
142
|
+
} else {
|
|
143
|
+
throw new TypeError(
|
|
144
|
+
"The ${property.singularName} must be of type " +
|
|
145
|
+
${JSON.stringify(typeNames)} + ".",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
if (isNonFunctionalProperty(property)) {
|
|
152
|
+
let typeGuards = getTypeGuards(property.range, types, `v`);
|
|
153
|
+
let typeNames = getTypeNames(property.range, types);
|
|
154
|
+
const scalar = areAllScalarTypes(property.range, types);
|
|
155
|
+
if (!scalar) {
|
|
156
|
+
typeGuards = `${typeGuards} || v instanceof URL`;
|
|
157
|
+
typeNames = `${typeNames} | URL`;
|
|
158
|
+
}
|
|
159
|
+
yield `
|
|
160
|
+
if ("${property.pluralName}" in values && \
|
|
161
|
+
values.${property.pluralName} != null) {
|
|
162
|
+
`;
|
|
163
|
+
if (property && property.singularAccessor) {
|
|
164
|
+
yield `
|
|
165
|
+
if ("${property.singularName}" in values &&
|
|
166
|
+
values.${property.singularName} != null) {
|
|
167
|
+
throw new TypeError(
|
|
168
|
+
"Cannot initialize both ${property.singularName} and " +
|
|
169
|
+
"${property.pluralName} at the same time.",
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
yield `
|
|
175
|
+
if (Array.isArray(values.${property.pluralName}) &&
|
|
176
|
+
values.${property.pluralName}.every(v => ${typeGuards})) {
|
|
177
|
+
// @ts-ignore: type is checked above.
|
|
178
|
+
this.${fieldName} = values.${property.pluralName};
|
|
179
|
+
`;
|
|
180
|
+
if (!allScalarTypes) {
|
|
181
|
+
yield `
|
|
182
|
+
for (let i = 0; i < values.${property.pluralName}.length; i++) {
|
|
183
|
+
this.${trustFieldName}.add(i);
|
|
184
|
+
}
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
yield `
|
|
188
|
+
} else {
|
|
189
|
+
throw new TypeError(
|
|
190
|
+
"The ${property.pluralName} must be an array of type " +
|
|
191
|
+
${JSON.stringify(typeNames)} + ".",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
yield "}\n";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function* generateCloner(
|
|
202
|
+
typeUri: string,
|
|
203
|
+
types: Record<string, TypeSchema>,
|
|
204
|
+
): AsyncIterable<string> {
|
|
205
|
+
const type = types[typeUri];
|
|
206
|
+
yield `
|
|
207
|
+
/**
|
|
208
|
+
* Clones this instance, optionally updating it with the given values.
|
|
209
|
+
* @param values The values to update the clone with.
|
|
210
|
+
* @options The options to use for cloning.
|
|
211
|
+
* @returns The cloned instance.
|
|
212
|
+
*/
|
|
213
|
+
${emitOverride(typeUri, types)} clone(
|
|
214
|
+
values:
|
|
215
|
+
`;
|
|
216
|
+
for await (const code of generateParametersType(typeUri, types)) yield code;
|
|
217
|
+
yield `
|
|
218
|
+
= {},
|
|
219
|
+
options: {
|
|
220
|
+
documentLoader?: DocumentLoader,
|
|
221
|
+
contextLoader?: DocumentLoader,
|
|
222
|
+
} = {}
|
|
223
|
+
): ${type.name} {
|
|
224
|
+
if (this._warning != null) {
|
|
225
|
+
getLogger(this._warning.category).warn(
|
|
226
|
+
this._warning.message,
|
|
227
|
+
this._warning.values
|
|
228
|
+
);
|
|
229
|
+
// @ts-ignore: $warning is not recognized as a property, but it is.
|
|
230
|
+
options = { ...options, $warning: this._warning };
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
233
|
+
if (type.extends == null) {
|
|
234
|
+
yield `
|
|
235
|
+
// @ts-ignore: this.constructor is not recognized as a constructor, but it is.
|
|
236
|
+
const clone: ${type.name} = new this.constructor(
|
|
237
|
+
{ id: values.id ?? this.id },
|
|
238
|
+
options
|
|
239
|
+
);
|
|
240
|
+
`;
|
|
241
|
+
} else {
|
|
242
|
+
yield `const clone = super.clone(values, options) as unknown as ${type.name};`;
|
|
243
|
+
}
|
|
244
|
+
for (const property of type.properties) {
|
|
245
|
+
const fieldName = await getFieldName(property.uri);
|
|
246
|
+
const trustFieldName = await getFieldName(property.uri, "#_trust");
|
|
247
|
+
const allScalarTypes = areAllScalarTypes(property.range, types);
|
|
248
|
+
yield `clone.${fieldName} = this.${fieldName};`;
|
|
249
|
+
if (!allScalarTypes) {
|
|
250
|
+
yield `clone.${trustFieldName} = new Set(this.${trustFieldName});`;
|
|
251
|
+
}
|
|
252
|
+
if (hasSingularAccessor(property)) {
|
|
253
|
+
let typeGuards = getTypeGuards(
|
|
254
|
+
property.range,
|
|
255
|
+
types,
|
|
256
|
+
`values.${property.singularName}`,
|
|
257
|
+
);
|
|
258
|
+
let typeNames = getTypeNames(property.range, types);
|
|
259
|
+
const scalar = areAllScalarTypes(property.range, types);
|
|
260
|
+
if (!scalar) {
|
|
261
|
+
typeGuards =
|
|
262
|
+
`${typeGuards} || values.${property.singularName} instanceof URL`;
|
|
263
|
+
typeNames = `${typeNames} | URL`;
|
|
264
|
+
}
|
|
265
|
+
yield `
|
|
266
|
+
if ("${property.singularName}" in values && \
|
|
267
|
+
values.${property.singularName} != null) {
|
|
268
|
+
if (${typeGuards}) {
|
|
269
|
+
// @ts-ignore: type is checked above.
|
|
270
|
+
clone.${fieldName} = [values.${property.singularName}];
|
|
271
|
+
`;
|
|
272
|
+
if (!allScalarTypes) {
|
|
273
|
+
yield `clone.${trustFieldName} = new Set([0]);`;
|
|
274
|
+
}
|
|
275
|
+
yield `
|
|
276
|
+
} else {
|
|
277
|
+
throw new TypeError(
|
|
278
|
+
"The ${property.singularName} must be of type " +
|
|
279
|
+
${JSON.stringify(typeNames)} + ".",
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
`;
|
|
284
|
+
}
|
|
285
|
+
if (isNonFunctionalProperty(property)) {
|
|
286
|
+
let typeGuards = getTypeGuards(property.range, types, `v`);
|
|
287
|
+
let typeNames = getTypeNames(property.range, types);
|
|
288
|
+
const scalar = areAllScalarTypes(property.range, types);
|
|
289
|
+
if (!scalar) {
|
|
290
|
+
typeGuards = `${typeGuards} || v instanceof URL`;
|
|
291
|
+
typeNames = `${typeNames} | URL`;
|
|
292
|
+
}
|
|
293
|
+
yield `
|
|
294
|
+
if ("${property.pluralName}" in values && \
|
|
295
|
+
values.${property.pluralName} != null) {
|
|
296
|
+
`;
|
|
297
|
+
if (property.singularAccessor) {
|
|
298
|
+
yield `
|
|
299
|
+
if ("${property.singularName}" in values &&
|
|
300
|
+
values.${property.singularName} != null) {
|
|
301
|
+
throw new TypeError(
|
|
302
|
+
"Cannot update both ${property.singularName} and " +
|
|
303
|
+
"${property.pluralName} at the same time.",
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
yield `
|
|
309
|
+
if (Array.isArray(values.${property.pluralName}) &&
|
|
310
|
+
values.${property.pluralName}.every(v => ${typeGuards})) {
|
|
311
|
+
// @ts-ignore: type is checked above.
|
|
312
|
+
clone.${fieldName} = values.${property.pluralName};
|
|
313
|
+
`;
|
|
314
|
+
if (!allScalarTypes) {
|
|
315
|
+
yield `
|
|
316
|
+
clone.${trustFieldName} = new Set();
|
|
317
|
+
for (let i = 0; i < values.${property.pluralName}.length; i++) {
|
|
318
|
+
clone.${trustFieldName}.add(i);
|
|
319
|
+
}
|
|
320
|
+
`;
|
|
321
|
+
}
|
|
322
|
+
yield `
|
|
323
|
+
} else {
|
|
324
|
+
throw new TypeError(
|
|
325
|
+
"The ${property.pluralName} must be an array of type " +
|
|
326
|
+
${JSON.stringify(typeNames)} + ".",
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
yield `
|
|
334
|
+
return clone;
|
|
335
|
+
}
|
|
336
|
+
`;
|
|
337
|
+
}
|
package/src/field.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { encodeBase58 } from "byte-encodings/base58";
|
|
2
|
+
import type { PropertySchema, TypeSchema } from "./schema.ts";
|
|
3
|
+
import { areAllScalarTypes, getTypeNames } from "./type.ts";
|
|
4
|
+
|
|
5
|
+
export async function getFieldName(
|
|
6
|
+
propertyUri: string,
|
|
7
|
+
prefix = "#",
|
|
8
|
+
): Promise<string> {
|
|
9
|
+
const hashedUri = await crypto.subtle.digest(
|
|
10
|
+
"SHA-1",
|
|
11
|
+
new TextEncoder().encode(propertyUri),
|
|
12
|
+
);
|
|
13
|
+
const match = propertyUri.match(/#([A-Za-z0-9_]+)$/);
|
|
14
|
+
const suffix = match == null ? "" : `_${match[1]}`;
|
|
15
|
+
return `${prefix}_${encodeBase58(hashedUri)}${suffix}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function generateField(
|
|
19
|
+
property: PropertySchema,
|
|
20
|
+
types: Record<string, TypeSchema>,
|
|
21
|
+
prefix = "#",
|
|
22
|
+
): Promise<string> {
|
|
23
|
+
const fieldName = await getFieldName(property.uri, prefix);
|
|
24
|
+
if (areAllScalarTypes(property.range, types)) {
|
|
25
|
+
return `${fieldName}: (${
|
|
26
|
+
getTypeNames(property.range, types, true)
|
|
27
|
+
})[] = [];\n`;
|
|
28
|
+
} else {
|
|
29
|
+
const typeNames = getTypeNames(property.range, types);
|
|
30
|
+
const trustFieldName = await getFieldName(property.uri, `${prefix}_trust`);
|
|
31
|
+
return `
|
|
32
|
+
${fieldName}: (${typeNames} | URL)[] = [];
|
|
33
|
+
${trustFieldName}: Set<number> = new Set();
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function* generateFields(
|
|
39
|
+
typeUri: string,
|
|
40
|
+
types: Record<string, TypeSchema>,
|
|
41
|
+
): AsyncIterable<string> {
|
|
42
|
+
const type = types[typeUri];
|
|
43
|
+
for (const property of type.properties) {
|
|
44
|
+
yield await generateField(property, types);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/fs.test.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { deepStrictEqual } from "node:assert";
|
|
2
|
+
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { readDirRecursive } from "./fs.ts";
|
|
7
|
+
|
|
8
|
+
test("readDirRecursive()", async () => {
|
|
9
|
+
// Create a temporary directory that has fixtures in it:
|
|
10
|
+
const dir = await mkdtemp(join(tmpdir(), "fedify-test-"));
|
|
11
|
+
await mkdir(join(dir, "a"));
|
|
12
|
+
await writeFile(join(dir, "a", "aa.txt"), "aa");
|
|
13
|
+
await writeFile(join(dir, "a", "ab.txt"), "aa");
|
|
14
|
+
await mkdir(join(dir, "a", "aa"));
|
|
15
|
+
await writeFile(join(dir, "a", "aa", "aaa.txt"), "aaa");
|
|
16
|
+
await mkdir(join(dir, "b"));
|
|
17
|
+
await writeFile(join(dir, "b", "ba.txt"), "ba");
|
|
18
|
+
await writeFile(join(dir, "b", "bb.txt"), "bb");
|
|
19
|
+
|
|
20
|
+
// Read the directory recursively:
|
|
21
|
+
deepStrictEqual(
|
|
22
|
+
new Set(await Array.fromAsync(readDirRecursive(dir))),
|
|
23
|
+
new Set([
|
|
24
|
+
join("a", "aa", "aaa.txt"),
|
|
25
|
+
join("a", "aa.txt"),
|
|
26
|
+
join("a", "ab.txt"),
|
|
27
|
+
join("b", "ba.txt"),
|
|
28
|
+
join("b", "bb.txt"),
|
|
29
|
+
]),
|
|
30
|
+
);
|
|
31
|
+
});
|
package/src/fs.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Recursively read a directory, yielding the paths of all files. File paths
|
|
6
|
+
* are relative to the directory, and directories are not yielded.
|
|
7
|
+
* @param dir The directory to read.
|
|
8
|
+
* @returns An async iterable of file paths.
|
|
9
|
+
*/
|
|
10
|
+
export async function* readDirRecursive(dir: string): AsyncIterable<string> {
|
|
11
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
12
|
+
if (entry.isDirectory()) {
|
|
13
|
+
const path = join(dir, entry.name);
|
|
14
|
+
for await (const subentry of readDirRecursive(path)) {
|
|
15
|
+
yield join(entry.name, subentry);
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
yield entry.name;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/generate.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { open } from "node:fs/promises";
|
|
2
|
+
import { generateClasses } from "./class.ts";
|
|
3
|
+
import { loadSchemaFiles } from "./schema.ts";
|
|
4
|
+
|
|
5
|
+
export default async function generateVocab(
|
|
6
|
+
schemaDir: string,
|
|
7
|
+
generatedPath: string,
|
|
8
|
+
) {
|
|
9
|
+
const types = await loadSchemaFiles(schemaDir);
|
|
10
|
+
const encoder = new TextEncoder();
|
|
11
|
+
|
|
12
|
+
const file = await open(generatedPath, "w");
|
|
13
|
+
const writer = file.createWriteStream();
|
|
14
|
+
|
|
15
|
+
for await (const code of generateClasses(types)) {
|
|
16
|
+
writer.write(encoder.encode(code));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
await new Promise<void>((resolve, reject) => {
|
|
20
|
+
writer.once("finish", () => file.close().then(resolve, reject));
|
|
21
|
+
writer.once("error", reject);
|
|
22
|
+
writer.end();
|
|
23
|
+
});
|
|
24
|
+
}
|
package/src/inspector.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { getFieldName } from "./field.ts";
|
|
2
|
+
import type { TypeSchema } from "./schema.ts";
|
|
3
|
+
import { hasSingularAccessor, isNonFunctionalProperty } from "./schema.ts";
|
|
4
|
+
import { emitOverride } from "./type.ts";
|
|
5
|
+
|
|
6
|
+
export async function* generateInspector(
|
|
7
|
+
typeUri: string,
|
|
8
|
+
types: Record<string, TypeSchema>,
|
|
9
|
+
): AsyncIterable<string> {
|
|
10
|
+
const type = types[typeUri];
|
|
11
|
+
yield `
|
|
12
|
+
protected ${
|
|
13
|
+
emitOverride(typeUri, types)
|
|
14
|
+
} _getCustomInspectProxy(): Record<string, unknown> {
|
|
15
|
+
`;
|
|
16
|
+
if (type.extends == null) {
|
|
17
|
+
yield `
|
|
18
|
+
const proxy: Record<string, unknown> = {};
|
|
19
|
+
if (this.id != null) {
|
|
20
|
+
proxy.id = {
|
|
21
|
+
[Symbol.for("Deno.customInspect")]: (
|
|
22
|
+
inspect: typeof Deno.inspect,
|
|
23
|
+
options: Deno.InspectOptions,
|
|
24
|
+
): string => "URL " + inspect(this.id!.href, options),
|
|
25
|
+
[Symbol.for("nodejs.util.inspect.custom")]: (
|
|
26
|
+
_depth: number,
|
|
27
|
+
options: unknown,
|
|
28
|
+
inspect: (value: unknown, options: unknown) => string,
|
|
29
|
+
): string => "URL " + inspect(this.id!.href, options),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
} else {
|
|
34
|
+
yield "const proxy: Record<string, unknown> = super._getCustomInspectProxy();";
|
|
35
|
+
}
|
|
36
|
+
for (const property of type.properties) {
|
|
37
|
+
const fieldName = await getFieldName(property.uri);
|
|
38
|
+
const localName = await getFieldName(property.uri, "");
|
|
39
|
+
yield `
|
|
40
|
+
const ${localName} = this.${fieldName}
|
|
41
|
+
// deno-lint-ignore no-explicit-any
|
|
42
|
+
.map((v: any) => v instanceof URL
|
|
43
|
+
? {
|
|
44
|
+
[Symbol.for("Deno.customInspect")]: (
|
|
45
|
+
inspect: typeof Deno.inspect,
|
|
46
|
+
options: Deno.InspectOptions,
|
|
47
|
+
): string => "URL " + inspect(v.href, options),
|
|
48
|
+
[Symbol.for("nodejs.util.inspect.custom")]: (
|
|
49
|
+
_depth: number,
|
|
50
|
+
options: unknown,
|
|
51
|
+
inspect: (value: unknown, options: unknown) => string,
|
|
52
|
+
): string => "URL " + inspect(v.href, options),
|
|
53
|
+
}
|
|
54
|
+
: v);
|
|
55
|
+
`;
|
|
56
|
+
if (hasSingularAccessor(property)) {
|
|
57
|
+
yield `
|
|
58
|
+
if (${localName}.length == 1) {
|
|
59
|
+
proxy.${property.singularName} = ${localName}[0];
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
if (isNonFunctionalProperty(property)) {
|
|
64
|
+
yield `
|
|
65
|
+
if (${localName}.length > 1
|
|
66
|
+
|| !(${JSON.stringify(property.singularName)} in proxy)
|
|
67
|
+
&& ${localName}.length > 0) {
|
|
68
|
+
proxy.${property.pluralName} = ${localName};
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
yield `
|
|
74
|
+
return proxy;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// @ts-ignore: suppressing TS4127
|
|
78
|
+
${emitOverride(typeUri, types)} [Symbol.for("Deno.customInspect")](
|
|
79
|
+
inspect: typeof Deno.inspect,
|
|
80
|
+
options: Deno.InspectOptions,
|
|
81
|
+
): string {
|
|
82
|
+
const proxy = this._getCustomInspectProxy();
|
|
83
|
+
return ${JSON.stringify(type.name + " ")} + inspect(proxy, options);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// @ts-ignore: suppressing TS4127
|
|
87
|
+
${emitOverride(typeUri, types)} [Symbol.for("nodejs.util.inspect.custom")](
|
|
88
|
+
_depth: number,
|
|
89
|
+
options: unknown,
|
|
90
|
+
inspect: (value: unknown, options: unknown) => string,
|
|
91
|
+
): string {
|
|
92
|
+
const proxy = this._getCustomInspectProxy();
|
|
93
|
+
return ${JSON.stringify(type.name + " ")} + inspect(proxy, options);
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
}
|