@fedify/vocab-tools 2.4.0-dev.1962 → 2.4.0-dev.1988

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/src/class.ts CHANGED
@@ -283,6 +283,44 @@ function isValidLanguageTag(language: string): boolean {
283
283
  }
284
284
  }
285
285
  `;
286
+ // Contexts are activated by expanded property IRIs left after compaction.
287
+ const extraContexts = Object.fromEntries(
288
+ Object.values(types).flatMap((type) =>
289
+ type.properties.flatMap((property) =>
290
+ property.extraContext == null
291
+ ? []
292
+ : [[property.uri, property.extraContext]]
293
+ )
294
+ ),
295
+ );
296
+ if (Object.keys(extraContexts).length > 0) {
297
+ yield `
298
+ const extraPropertyContexts: Readonly<Record<string, string>> =
299
+ ${JSON.stringify(extraContexts)};
300
+
301
+ function getExtraContexts(document: unknown): string[] {
302
+ const contexts = new Set<string>();
303
+ const pending: unknown[] = [document];
304
+ while (pending.length > 0) {
305
+ const value = pending.pop();
306
+ if (value == null || typeof value !== "object") continue;
307
+ if (Array.isArray(value)) {
308
+ for (const item of value) pending.push(item);
309
+ continue;
310
+ }
311
+ if ("@value" in value) continue;
312
+ for (const [key, child] of globalThis.Object.entries(value)) {
313
+ if (key === "@context") continue;
314
+ if (globalThis.Object.hasOwn(extraPropertyContexts, key)) {
315
+ contexts.add(extraPropertyContexts[key]);
316
+ }
317
+ pending.push(child);
318
+ }
319
+ }
320
+ return [...contexts];
321
+ }
322
+ `;
323
+ }
286
324
  yield "\n\n";
287
325
  const portableIriKeys = new Set(["@id", "id"]);
288
326
  for (const type of Object.values(types)) {
package/src/codec.ts CHANGED
@@ -265,13 +265,35 @@ export async function* generateEncoder(
265
265
  },
266
266
  );
267
267
  }
268
- const docContext = options.context ??
268
+ let docContext: Parameters<typeof jsonld.compact>[1] = options.context ??
269
269
  ${JSON.stringify(type.defaultContext)};
270
- const compacted = await jsonld.compact(
270
+ let compacted = await jsonld.compact(
271
271
  values,
272
272
  docContext,
273
273
  { documentLoader: options.contextLoader },
274
274
  );
275
+ `;
276
+ if (
277
+ Object.values(types).some((t) =>
278
+ t.properties.some((p) => p.extraContext != null)
279
+ )
280
+ ) {
281
+ yield `
282
+ if (options.context == null) {
283
+ const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
284
+ const additionalContexts = getExtraContexts(compacted).filter(
285
+ context => !currentContexts.includes(context)
286
+ );
287
+ if (additionalContexts.length > 0) {
288
+ docContext = [...currentContexts, ...additionalContexts];
289
+ compacted = await jsonld.compact(values, docContext, {
290
+ documentLoader: options.contextLoader,
291
+ });
292
+ }
293
+ }
294
+ `;
295
+ }
296
+ yield `
275
297
  if (docContext != null) {
276
298
  // Embed context
277
299
  `;
@@ -309,7 +331,10 @@ export async function* generateEncoder(
309
331
  protected ${emitOverride(typeUri, types)} isCompactable(): boolean {
310
332
  `;
311
333
  for (const property of type.properties) {
312
- if (!property.range.every((r) => isCompactableType(r, types))) {
334
+ if (
335
+ property.extraContext != null ||
336
+ !property.range.every((r) => isCompactableType(r, types))
337
+ ) {
313
338
  yield `
314
339
  if (
315
340
  this.${await getFieldName(property.uri)} != null &&
package/src/property.ts CHANGED
@@ -29,6 +29,7 @@ async function* generateProperty(
29
29
  moduleVarNames: ReadonlyMap<string, string>,
30
30
  ): AsyncIterable<string> {
31
31
  const override = emitOverride(type.uri, types, property);
32
+ const trustOwner = type.trustEmbeddedObjects === false ? "null" : "this.id";
32
33
  const doc = `\n/** ${property.description.replaceAll("\n", "\n * ")}\n */\n`;
33
34
  const cachedPropertyBaseUrl = `(options as { baseUrl?: URL }).baseUrl ??
34
35
  this._baseUrl ??
@@ -277,7 +278,7 @@ async function* generateProperty(
277
278
  let v = this.${await getFieldName(property.uri)}[0];
278
279
  if (!(v instanceof URL) &&
279
280
  v.id != null &&
280
- !isTrustedIriOrigin(options, v.id, this.id) &&
281
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
281
282
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
282
283
  v = v.id;
283
284
  }
@@ -317,12 +318,18 @@ async function* generateProperty(
317
318
  }
318
319
  yield `
319
320
  if (v?.id != null &&
320
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
321
+ ${
322
+ type.trustEmbeddedObjects === false ? "" : "this.id != null && "
323
+ }!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
321
324
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
322
325
  if (options.crossOrigin === "throw") {
323
326
  throw new Error(
324
327
  "The property object's @id (" + v.id.href + ") has a different " +
325
- "origin than the property owner's @id (" + this.id.href + "); " +
328
+ "origin than the property owner's @id (" + ${
329
+ type.trustEmbeddedObjects === false
330
+ ? '"untrusted metadata"'
331
+ : "this.id.href"
332
+ } + "); " +
326
333
  "refusing to return the object. If you want to bypass this " +
327
334
  "check and are aware of the security implications, set the " +
328
335
  'crossOrigin option to "trust".'
@@ -334,7 +341,11 @@ async function* generateProperty(
334
341
  "return the object. If you want to bypass this check and are " +
335
342
  "aware of the security implications, set the crossOrigin option " +
336
343
  'to "trust".',
337
- { objectId: v.id.href, parentObjectId: this.id.href },
344
+ { objectId: v.id.href, parentObjectId: ${
345
+ type.trustEmbeddedObjects === false
346
+ ? '"untrusted metadata"'
347
+ : "this.id.href"
348
+ } },
338
349
  );
339
350
  return null;
340
351
  }
@@ -383,7 +394,7 @@ async function* generateProperty(
383
394
  let v = vs[i];
384
395
  if (!(v instanceof URL) &&
385
396
  v.id != null &&
386
- !isTrustedIriOrigin(options, v.id, this.id) &&
397
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
387
398
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
388
399
  v = v.id;
389
400
  }
@@ -424,12 +435,18 @@ async function* generateProperty(
424
435
  }
425
436
  yield `
426
437
  if (v?.id != null &&
427
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
438
+ ${
439
+ type.trustEmbeddedObjects === false ? "" : "this.id != null && "
440
+ }!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
428
441
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
429
442
  if (options.crossOrigin === "throw") {
430
443
  throw new Error(
431
444
  "The property object's @id (" + v.id.href + ") has a different " +
432
- "origin than the property owner's @id (" + this.id.href + "); " +
445
+ "origin than the property owner's @id (" + ${
446
+ type.trustEmbeddedObjects === false
447
+ ? '"untrusted metadata"'
448
+ : "this.id.href"
449
+ } + "); " +
433
450
  "refusing to return the object. If you want to bypass this " +
434
451
  "check and are aware of the security implications, set the " +
435
452
  'crossOrigin option to "trust".'
@@ -441,7 +458,11 @@ async function* generateProperty(
441
458
  "return the object. If you want to bypass this check and are " +
442
459
  "aware of the security implications, set the crossOrigin " +
443
460
  'option to "trust".',
444
- { objectId: v.id.href, parentObjectId: this.id.href },
461
+ { objectId: v.id.href, parentObjectId: ${
462
+ type.trustEmbeddedObjects === false
463
+ ? '"untrusted metadata"'
464
+ : "this.id.href"
465
+ } },
445
466
  );
446
467
  continue;
447
468
  }
package/src/schema.ts CHANGED
@@ -47,6 +47,14 @@ export interface TypeSchema {
47
47
  */
48
48
  entity: boolean;
49
49
 
50
+ /**
51
+ * Whether this type's id can establish same-origin trust for its own
52
+ * embedded entity-valued properties. Defaults to true. Set to false for
53
+ * metadata whose id does not identify an independently fetched resource.
54
+ * Locally constructed and explicitly trusted values remain trusted.
55
+ */
56
+ trustEmbeddedObjects?: boolean;
57
+
50
58
  /**
51
59
  * Whether the type omits `@type` in JSON-LD serialization. When `true`,
52
60
  * the generated `toJsonLd()` method will not emit `@type` (or `type` in
@@ -77,6 +85,14 @@ export interface TypeSchema {
77
85
  }
78
86
 
79
87
  export interface PropertySchemaBase {
88
+ /**
89
+ * A context URL added to the default serialization context when this
90
+ * property's expanded IRI appears in the compacted document. Explicit
91
+ * caller contexts are unchanged. Populated properties with this option
92
+ * use the JSON-LD processor instead of compact serialization heuristics.
93
+ */
94
+ extraContext?: string;
95
+
80
96
  /**
81
97
  * The singular form of the property name. It is used as the name of the
82
98
  * generated property accessors.
package/src/schema.yaml CHANGED
@@ -72,6 +72,14 @@ $defs:
72
72
  property-base:
73
73
  type: object
74
74
  properties:
75
+ extraContext:
76
+ description: >-
77
+ A context URL added to default serialization when this property's
78
+ expanded IRI appears in the compacted document. Explicit caller
79
+ contexts are unchanged. Populated properties use the JSON-LD
80
+ processor instead of compact serialization heuristics.
81
+ type: string
82
+ format: uri
75
83
  singularName:
76
84
  description: >-
77
85
  The singular form of the property name. It is used as the name of the
@@ -250,6 +258,13 @@ properties:
250
258
 
251
259
  The extended subtypes must have the consistent value of this flag.
252
260
  type: boolean
261
+ trustEmbeddedObjects:
262
+ description: >-
263
+ Whether this type's id establishes same-origin trust for its own
264
+ embedded entity-valued properties. Defaults to true. Set to false
265
+ for metadata ids that do not identify independently fetched resources.
266
+ Locally constructed and explicitly trusted values remain trusted.
267
+ type: boolean
253
268
  typeless:
254
269
  description: >-
255
270
  Whether the type omits @type in JSON-LD serialization. When true,