@fedify/vocab-tools 2.4.0-dev.1508 → 2.4.0-dev.1528
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 +4820 -3515
- package/src/__snapshots__/class.test.ts.node.snap +4820 -3515
- package/src/__snapshots__/class.test.ts.snap +4820 -3515
- 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/src/class.ts
CHANGED
|
@@ -3,9 +3,41 @@ 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 {
|
|
6
|
+
import {
|
|
7
|
+
type PropertySchema,
|
|
8
|
+
type TypeSchema,
|
|
9
|
+
validateTypeSchemas,
|
|
10
|
+
} from "./schema.ts";
|
|
7
11
|
import { emitOverride } from "./type.ts";
|
|
8
12
|
|
|
13
|
+
const XSD_ANY_URI = "http://www.w3.org/2001/XMLSchema#anyURI";
|
|
14
|
+
const FEDIFY_URL = "fedify:url";
|
|
15
|
+
const INTERNAL_RUNTIME_IMPORTS = [
|
|
16
|
+
"compactJsonLdCache",
|
|
17
|
+
"getJsonLdContext",
|
|
18
|
+
"isTrustedIriOrigin",
|
|
19
|
+
"normalizeJsonLdIris",
|
|
20
|
+
].join(",\n ");
|
|
21
|
+
const RUNTIME_IMPORTS = [
|
|
22
|
+
"canParseDecimal",
|
|
23
|
+
"decodeMultibase",
|
|
24
|
+
"type Decimal",
|
|
25
|
+
"type DocumentLoader",
|
|
26
|
+
"encodeMultibase",
|
|
27
|
+
"exportMultibaseKey",
|
|
28
|
+
"exportSpki",
|
|
29
|
+
"formatIri",
|
|
30
|
+
"getDocumentLoader",
|
|
31
|
+
"importMultibaseKey",
|
|
32
|
+
"importPem",
|
|
33
|
+
"isDecimal",
|
|
34
|
+
"LanguageString",
|
|
35
|
+
"parseDecimal",
|
|
36
|
+
"parseIri",
|
|
37
|
+
"parseJsonLdId",
|
|
38
|
+
"type RemoteDocument",
|
|
39
|
+
].join(",\n ");
|
|
40
|
+
|
|
9
41
|
/**
|
|
10
42
|
* Sorts the given types topologically so that the base types come before the
|
|
11
43
|
* extended types.
|
|
@@ -169,6 +201,40 @@ export function getEntityTypeById(id: string | URL): $EntityType | undefined {
|
|
|
169
201
|
`;
|
|
170
202
|
}
|
|
171
203
|
|
|
204
|
+
function canContainIriValue(
|
|
205
|
+
property: PropertySchema,
|
|
206
|
+
types: Record<string, TypeSchema>,
|
|
207
|
+
): boolean {
|
|
208
|
+
return property.range.some((typeUri) =>
|
|
209
|
+
typeUri === XSD_ANY_URI || typeUri === FEDIFY_URL || types[typeUri]?.entity
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function addKeys(
|
|
214
|
+
keys: Set<string>,
|
|
215
|
+
property: { uri: string; compactName?: string },
|
|
216
|
+
): void {
|
|
217
|
+
keys.add(property.uri);
|
|
218
|
+
if (property.compactName != null) keys.add(property.compactName);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function addPortableIriKeys(
|
|
222
|
+
keys: Set<string>,
|
|
223
|
+
property: PropertySchema,
|
|
224
|
+
types: Record<string, TypeSchema>,
|
|
225
|
+
): void {
|
|
226
|
+
if (!canContainIriValue(property, types)) return;
|
|
227
|
+
addKeys(keys, property);
|
|
228
|
+
if (
|
|
229
|
+
"redundantProperties" in property &&
|
|
230
|
+
property.redundantProperties != null
|
|
231
|
+
) {
|
|
232
|
+
for (const redundantProperty of property.redundantProperties) {
|
|
233
|
+
addKeys(keys, redundantProperty);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
172
238
|
/**
|
|
173
239
|
* Generates the TypeScript classes from the given types.
|
|
174
240
|
* @param types The types to generate classes from.
|
|
@@ -178,35 +244,27 @@ export async function* generateClasses(
|
|
|
178
244
|
types: Record<string, TypeSchema>,
|
|
179
245
|
): AsyncIterable<string> {
|
|
180
246
|
validateTypeSchemas(types);
|
|
181
|
-
const runtimeImports = [
|
|
182
|
-
"canParseDecimal",
|
|
183
|
-
"decodeMultibase",
|
|
184
|
-
"type Decimal",
|
|
185
|
-
"type DocumentLoader",
|
|
186
|
-
"encodeMultibase",
|
|
187
|
-
"exportMultibaseKey",
|
|
188
|
-
"exportSpki",
|
|
189
|
-
"getDocumentLoader",
|
|
190
|
-
"importMultibaseKey",
|
|
191
|
-
"importPem",
|
|
192
|
-
"isDecimal",
|
|
193
|
-
"LanguageString",
|
|
194
|
-
"parseDecimal",
|
|
195
|
-
"type RemoteDocument",
|
|
196
|
-
];
|
|
197
247
|
yield "// deno-lint-ignore-file ban-unused-ignore no-explicit-any no-unused-vars prefer-const verbatim-module-syntax\n";
|
|
198
248
|
yield 'import jsonld from "@fedify/vocab-runtime/jsonld";\n';
|
|
199
249
|
yield 'import { getLogger } from "@logtape/logtape";\n';
|
|
200
250
|
yield `import { type Span, SpanStatusCode, type TracerProvider, trace }
|
|
201
251
|
from "@opentelemetry/api";\n`;
|
|
202
|
-
yield `import {\n ${
|
|
203
|
-
|
|
204
|
-
}\n} from "@fedify/vocab-runtime";\n`;
|
|
252
|
+
yield `import {\n ${RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime";\n`;
|
|
253
|
+
yield `import {\n ${INTERNAL_RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime/internal/jsonld-cache";\n`;
|
|
205
254
|
yield `import {
|
|
206
255
|
isTemporalDuration,
|
|
207
256
|
isTemporalInstant,
|
|
208
257
|
} from "@fedify/vocab-runtime/temporal";\n`;
|
|
209
|
-
|
|
258
|
+
const portableIriKeys = new Set(["@id", "id"]);
|
|
259
|
+
for (const type of Object.values(types)) {
|
|
260
|
+
for (const property of type.properties) {
|
|
261
|
+
addPortableIriKeys(portableIriKeys, property, types);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
yield "const PORTABLE_IRI_PATTERN = /^ap(?:\\+ef61)?:\\/\\//i;\n";
|
|
265
|
+
yield `const PORTABLE_IRI_KEYS: ReadonlySet<string> = new Set(${
|
|
266
|
+
JSON.stringify([...portableIriKeys].sort())
|
|
267
|
+
});\n\n`;
|
|
210
268
|
const moduleVarNames = new Map<string, string>();
|
|
211
269
|
const sorted = sortTopologically(types);
|
|
212
270
|
for (const typeUri of sorted) {
|
package/src/codec.ts
CHANGED
|
@@ -127,7 +127,7 @@ export async function* generateEncoder(
|
|
|
127
127
|
const item = (
|
|
128
128
|
`;
|
|
129
129
|
if (!areAllScalarTypes(property.range, types)) {
|
|
130
|
-
yield "v instanceof URL ? v
|
|
130
|
+
yield "v instanceof URL ? formatIri(v) : ";
|
|
131
131
|
}
|
|
132
132
|
const encoders = getEncoders(
|
|
133
133
|
property.range,
|
|
@@ -178,7 +178,7 @@ export async function* generateEncoder(
|
|
|
178
178
|
? ""
|
|
179
179
|
: `result["type"] = ${JSON.stringify(type.compactName ?? type.uri)};`
|
|
180
180
|
}
|
|
181
|
-
if (this.id != null) result["id"] = this.id
|
|
181
|
+
if (this.id != null) result["id"] = formatIri(this.id);
|
|
182
182
|
result["@context"] = ${JSON.stringify(type.defaultContext)};
|
|
183
183
|
return result;
|
|
184
184
|
}
|
|
@@ -210,7 +210,7 @@ export async function* generateEncoder(
|
|
|
210
210
|
const element = (
|
|
211
211
|
`;
|
|
212
212
|
if (!areAllScalarTypes(property.range, types)) {
|
|
213
|
-
yield 'v instanceof URL ? { "@id": v
|
|
213
|
+
yield 'v instanceof URL ? { "@id": formatIri(v) } : ';
|
|
214
214
|
}
|
|
215
215
|
for (const code of getEncoders(property.range, types, "v", "options")) {
|
|
216
216
|
yield code;
|
|
@@ -250,7 +250,7 @@ export async function* generateEncoder(
|
|
|
250
250
|
}
|
|
251
251
|
yield `
|
|
252
252
|
${type.typeless ? "" : `values["@type"] = [${JSON.stringify(type.uri)}];`}
|
|
253
|
-
if (this.id != null) values["@id"] = this.id
|
|
253
|
+
if (this.id != null) values["@id"] = formatIri(this.id);
|
|
254
254
|
if (options.format === "expand") {
|
|
255
255
|
return await jsonld.expand(
|
|
256
256
|
values,
|
|
@@ -393,10 +393,12 @@ export async function* generateDecoder(
|
|
|
393
393
|
};
|
|
394
394
|
// deno-lint-ignore no-explicit-any
|
|
395
395
|
let values: Record<string, any[]> & { "@id"?: string };
|
|
396
|
+
let expanded: unknown[];
|
|
396
397
|
if (globalThis.Object.keys(json).length == 0) {
|
|
397
398
|
values = {};
|
|
399
|
+
expanded = [values];
|
|
398
400
|
} else {
|
|
399
|
-
|
|
401
|
+
expanded = await jsonld.expand(json, {
|
|
400
402
|
documentLoader: options.contextLoader,
|
|
401
403
|
keepFreeFloatingNodes: true,
|
|
402
404
|
});
|
|
@@ -404,11 +406,9 @@ export async function* generateDecoder(
|
|
|
404
406
|
// deno-lint-ignore no-explicit-any
|
|
405
407
|
(expanded[0] ?? {}) as (Record<string, any[]> & { "@id"?: string });
|
|
406
408
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (options.baseUrl == null && values["@id"] != null && !values["@id"].startsWith("_:") && URL.canParse(values["@id"])) {
|
|
411
|
-
options = { ...options, baseUrl: new URL(values["@id"]) };
|
|
409
|
+
const id = parseJsonLdId(values["@id"], options.baseUrl);
|
|
410
|
+
if (id != null && options.baseUrl == null) {
|
|
411
|
+
options = { ...options, baseUrl: id };
|
|
412
412
|
}
|
|
413
413
|
`;
|
|
414
414
|
const subtypes = getSubtypes(typeUri, types, true);
|
|
@@ -432,10 +432,24 @@ export async function* generateDecoder(
|
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
434
|
`;
|
|
435
|
+
yield `
|
|
436
|
+
let cacheJsonLd = !("_fromSubclass" in options) || !options._fromSubclass
|
|
437
|
+
? normalizeJsonLdIris(
|
|
438
|
+
expanded,
|
|
439
|
+
PORTABLE_IRI_KEYS,
|
|
440
|
+
PORTABLE_IRI_PATTERN,
|
|
441
|
+
)
|
|
442
|
+
: undefined;
|
|
443
|
+
if (cacheJsonLd != null && cacheJsonLd !== expanded) {
|
|
444
|
+
cacheJsonLd = structuredClone(cacheJsonLd);
|
|
445
|
+
}
|
|
446
|
+
`;
|
|
435
447
|
if (type.extends == null) {
|
|
436
448
|
yield `
|
|
437
449
|
const instance = new this(
|
|
438
|
-
{
|
|
450
|
+
{
|
|
451
|
+
id
|
|
452
|
+
},
|
|
439
453
|
options,
|
|
440
454
|
);
|
|
441
455
|
`;
|
|
@@ -491,11 +505,7 @@ export async function* generateDecoder(
|
|
|
491
505
|
if (typeof v === "object" && "@id" in v && !("@type" in v)
|
|
492
506
|
&& globalThis.Object.keys(v).length === 1) {
|
|
493
507
|
if (v["@id"].startsWith("_:")) continue;
|
|
494
|
-
${variable}.push(
|
|
495
|
-
!URL.canParse(v["@id"], ${propertyBaseUrl}) && v["@id"].startsWith("at://")
|
|
496
|
-
? new URL("at://" + encodeURIComponent(v["@id"].substring(5)))
|
|
497
|
-
: new URL(v["@id"], ${propertyBaseUrl})
|
|
498
|
-
);
|
|
508
|
+
${variable}.push(parseIri(v["@id"], ${propertyBaseUrl}));
|
|
499
509
|
continue;
|
|
500
510
|
}
|
|
501
511
|
`;
|
|
@@ -539,7 +549,19 @@ export async function* generateDecoder(
|
|
|
539
549
|
yield `
|
|
540
550
|
if (!("_fromSubclass" in options) || !options._fromSubclass) {
|
|
541
551
|
try {
|
|
542
|
-
|
|
552
|
+
if (cacheJsonLd != null && cacheJsonLd !== expanded) {
|
|
553
|
+
const compactArray = Array.isArray(json) && json.length === 1;
|
|
554
|
+
const jsonLd = compactArray ? json[0] : json;
|
|
555
|
+
const normalized = cacheJsonLd;
|
|
556
|
+
const cachedJsonLd = await compactJsonLdCache(
|
|
557
|
+
normalized,
|
|
558
|
+
jsonLd,
|
|
559
|
+
options.contextLoader,
|
|
560
|
+
);
|
|
561
|
+
instance._cachedJsonLd = compactArray ? [cachedJsonLd] : cachedJsonLd;
|
|
562
|
+
} else {
|
|
563
|
+
instance._cachedJsonLd = structuredClone(json);
|
|
564
|
+
}
|
|
543
565
|
} catch {
|
|
544
566
|
getLogger(["fedify", "vocab"]).warn(
|
|
545
567
|
"Failed to cache JSON-LD: {json}",
|
package/src/property.ts
CHANGED
|
@@ -86,9 +86,10 @@ async function* generateProperty(
|
|
|
86
86
|
${JSON.stringify(metadata.version)},
|
|
87
87
|
);
|
|
88
88
|
return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
|
|
89
|
+
const lookupUrl = formatIri(url);
|
|
89
90
|
let fetchResult: RemoteDocument;
|
|
90
91
|
try {
|
|
91
|
-
fetchResult = await documentLoader(
|
|
92
|
+
fetchResult = await documentLoader(lookupUrl);
|
|
92
93
|
} catch (error) {
|
|
93
94
|
span.setStatus({
|
|
94
95
|
code: SpanStatusCode.ERROR,
|
|
@@ -98,21 +99,20 @@ async function* generateProperty(
|
|
|
98
99
|
if (options.suppressError) {
|
|
99
100
|
getLogger(["fedify", "vocab"]).error(
|
|
100
101
|
"Failed to fetch {url}: {error}",
|
|
101
|
-
{ error, url:
|
|
102
|
+
{ error, url: lookupUrl }
|
|
102
103
|
);
|
|
103
104
|
return null;
|
|
104
105
|
}
|
|
105
106
|
throw error;
|
|
106
107
|
}
|
|
107
108
|
const { document, documentUrl } = fetchResult;
|
|
108
|
-
const baseUrl =
|
|
109
|
+
const baseUrl = parseIri(documentUrl);
|
|
109
110
|
try {
|
|
110
111
|
const obj = await this.#${property.singularName}_fromJsonLd(
|
|
111
112
|
document,
|
|
112
113
|
{ documentLoader, contextLoader, tracerProvider, baseUrl }
|
|
113
114
|
);
|
|
114
|
-
if (
|
|
115
|
-
obj.id.origin !== baseUrl.origin) {
|
|
115
|
+
if (obj?.id != null && !isTrustedIriOrigin(options, obj.id, baseUrl)) {
|
|
116
116
|
if (options.crossOrigin === "throw") {
|
|
117
117
|
throw new Error(
|
|
118
118
|
"The object's @id (" + obj.id.href + ") has a different origin " +
|
|
@@ -141,7 +141,7 @@ async function* generateProperty(
|
|
|
141
141
|
if (options.suppressError) {
|
|
142
142
|
getLogger(["fedify", "vocab"]).error(
|
|
143
143
|
"Failed to parse {url}: {error}",
|
|
144
|
-
{ error: e, url:
|
|
144
|
+
{ error: e, url: lookupUrl }
|
|
145
145
|
);
|
|
146
146
|
return null;
|
|
147
147
|
}
|
|
@@ -275,8 +275,9 @@ async function* generateProperty(
|
|
|
275
275
|
}
|
|
276
276
|
if (this.${await getFieldName(property.uri)}.length < 1) return null;
|
|
277
277
|
let v = this.${await getFieldName(property.uri)}[0];
|
|
278
|
-
if (
|
|
279
|
-
v.id != null &&
|
|
278
|
+
if (!(v instanceof URL) &&
|
|
279
|
+
v.id != null &&
|
|
280
|
+
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
280
281
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
281
282
|
v = v.id;
|
|
282
283
|
}
|
|
@@ -315,8 +316,8 @@ async function* generateProperty(
|
|
|
315
316
|
`;
|
|
316
317
|
}
|
|
317
318
|
yield `
|
|
318
|
-
if (
|
|
319
|
-
this.id != null && v.id
|
|
319
|
+
if (v?.id != null &&
|
|
320
|
+
this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
|
|
320
321
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
321
322
|
if (options.crossOrigin === "throw") {
|
|
322
323
|
throw new Error(
|
|
@@ -380,8 +381,9 @@ async function* generateProperty(
|
|
|
380
381
|
const vs = this.${await getFieldName(property.uri)};
|
|
381
382
|
for (let i = 0; i < vs.length; i++) {
|
|
382
383
|
let v = vs[i];
|
|
383
|
-
if (
|
|
384
|
-
v.id != null &&
|
|
384
|
+
if (!(v instanceof URL) &&
|
|
385
|
+
v.id != null &&
|
|
386
|
+
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
385
387
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
386
388
|
v = v.id;
|
|
387
389
|
}
|
|
@@ -421,9 +423,9 @@ async function* generateProperty(
|
|
|
421
423
|
`;
|
|
422
424
|
}
|
|
423
425
|
yield `
|
|
424
|
-
if (
|
|
425
|
-
this.id != null && v.id
|
|
426
|
-
!this.${await getFieldName(property.uri, "#_trust")}.has(
|
|
426
|
+
if (v?.id != null &&
|
|
427
|
+
this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
|
|
428
|
+
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
427
429
|
if (options.crossOrigin === "throw") {
|
|
428
430
|
throw new Error(
|
|
429
431
|
"The property object's @id (" + v.id.href + ") has a different " +
|
package/src/type.ts
CHANGED
|
@@ -157,10 +157,10 @@ const scalarTypes: Record<string, ScalarType> = {
|
|
|
157
157
|
return `${v} instanceof URL`;
|
|
158
158
|
},
|
|
159
159
|
encoder(v) {
|
|
160
|
-
return `{ "@id": ${v}
|
|
160
|
+
return `{ "@id": formatIri(${v}) }`;
|
|
161
161
|
},
|
|
162
162
|
compactEncoder(v) {
|
|
163
|
-
return
|
|
163
|
+
return `formatIri(${v})`;
|
|
164
164
|
},
|
|
165
165
|
dataCheck(v) {
|
|
166
166
|
return `${v} != null && typeof ${v} === "object" && "@id" in ${v}
|
|
@@ -168,22 +168,7 @@ const scalarTypes: Record<string, ScalarType> = {
|
|
|
168
168
|
&& ${v}["@id"] !== ""`;
|
|
169
169
|
},
|
|
170
170
|
decoder(v, baseUrlVar) {
|
|
171
|
-
return
|
|
172
|
-
? new URL("at://" +
|
|
173
|
-
encodeURIComponent(
|
|
174
|
-
${v}["@id"].includes("/", 5)
|
|
175
|
-
? ${v}["@id"].slice(5, ${v}["@id"].indexOf("/", 5))
|
|
176
|
-
: ${v}["@id"].slice(5)
|
|
177
|
-
) +
|
|
178
|
-
(
|
|
179
|
-
${v}["@id"].includes("/", 5)
|
|
180
|
-
? ${v}["@id"].slice(${v}["@id"].indexOf("/", 5))
|
|
181
|
-
: ""
|
|
182
|
-
)
|
|
183
|
-
)
|
|
184
|
-
: URL.canParse(${v}["@id"]) && ${baseUrlVar}
|
|
185
|
-
? new URL(${v}["@id"])
|
|
186
|
-
: new URL(${v}["@id"], ${baseUrlVar})`;
|
|
171
|
+
return `parseIri(${v}["@id"], ${baseUrlVar})`;
|
|
187
172
|
},
|
|
188
173
|
},
|
|
189
174
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString": {
|
|
@@ -325,10 +310,10 @@ const scalarTypes: Record<string, ScalarType> = {
|
|
|
325
310
|
return `${v} instanceof URL`;
|
|
326
311
|
},
|
|
327
312
|
encoder(v) {
|
|
328
|
-
return `{ "@value": ${v}
|
|
313
|
+
return `{ "@value": formatIri(${v}) }`;
|
|
329
314
|
},
|
|
330
315
|
compactEncoder(v) {
|
|
331
|
-
return
|
|
316
|
+
return `formatIri(${v})`;
|
|
332
317
|
},
|
|
333
318
|
dataCheck(v) {
|
|
334
319
|
return `typeof ${v} === "object" && "@value" in ${v}
|
|
@@ -336,7 +321,7 @@ const scalarTypes: Record<string, ScalarType> = {
|
|
|
336
321
|
&& ${v}["@value"] !== "" && ${v}["@value"] !== "/"`;
|
|
337
322
|
},
|
|
338
323
|
decoder(v) {
|
|
339
|
-
return `
|
|
324
|
+
return `parseIri(${v}["@value"])`;
|
|
340
325
|
},
|
|
341
326
|
},
|
|
342
327
|
"fedify:publicKey": {
|