@fedify/vocab-tools 2.4.0-pr.936.41 → 2.4.1

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/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 ??
@@ -73,6 +74,9 @@ async function* generateProperty(
73
74
  suppressError?: boolean,
74
75
  tracerProvider?: TracerProvider,
75
76
  crossOrigin?: "ignore" | "throw" | "trust";
77
+ gateways?: readonly (string | URL)[];
78
+ verifyPortableObject?: PortableObjectVerifier;
79
+ inheritPortableObjectVerifier?: boolean;
76
80
  } = {},
77
81
  ): Promise<${getTypeNames(property.range, types)} | null> {
78
82
  const documentLoader =
@@ -85,11 +89,96 @@ async function* generateProperty(
85
89
  ${JSON.stringify(metadata.name)},
86
90
  ${JSON.stringify(metadata.version)},
87
91
  );
92
+ // The fetched object uses the verifier given to this call (which falls
93
+ // back to the parent's own default) for its accessors by default,
94
+ // unless the call keeps its verifier to itself:
95
+ const childVerifier = options.inheritPortableObjectVerifier === false
96
+ ? this._verifyPortableObject
97
+ : options.verifyPortableObject;
88
98
  return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
99
+ const dereferencePortable = async (
100
+ portableUrl: URL,
101
+ extra: {
102
+ inferredGateways?: readonly URL[];
103
+ response?: RemoteDocument;
104
+ contextLoader?: DocumentLoader;
105
+ } = {},
106
+ ) => {
107
+ try {
108
+ const obj = await dereferencePortableIri(portableUrl, {
109
+ documentLoader,
110
+ contextLoader: extra.contextLoader ?? contextLoader,
111
+ tracerProvider,
112
+ gateways: options.gateways,
113
+ inferredGateways: extra.inferredGateways,
114
+ response: extra.response,
115
+ verifyPortableObject: options.verifyPortableObject,
116
+ referrer: {
117
+ object: this,
118
+ property: ${JSON.stringify(property.uri)},
119
+ },
120
+ suppressError: options.suppressError,
121
+ crossOrigin: options.crossOrigin,
122
+ span,
123
+ parse: (document, parseOptions) =>
124
+ this.#${property.singularName}_fromJsonLd(document, {
125
+ documentLoader,
126
+ contextLoader: parseOptions.contextLoader,
127
+ tracerProvider,
128
+ verifyPortableObject: childVerifier,
129
+ baseUrl: parseOptions.baseUrl,
130
+ }),
131
+ });
132
+ if (obj != null) {
133
+ span.setAttribute(
134
+ "activitypub.object.id",
135
+ (obj.id ?? portableUrl).href,
136
+ );
137
+ span.setAttribute(
138
+ "activitypub.object.type",
139
+ // @ts-ignore: obj.constructor always has a typeId.
140
+ obj.constructor.typeId.href
141
+ );
142
+ }
143
+ return obj;
144
+ } catch (e) {
145
+ span.setStatus({
146
+ code: SpanStatusCode.ERROR,
147
+ message: String(e),
148
+ });
149
+ throw e;
150
+ } finally {
151
+ span.end();
152
+ }
153
+ };
154
+ if (isPortableIri(url)) {
155
+ // A reference in a portable actor's own document, e.g., its outbox,
156
+ // has no location hints, so the actor's gateways are used instead:
157
+ return await dereferencePortable(
158
+ url,
159
+ options.gateways == null
160
+ ? { inferredGateways: getReferrerGateways(this, url) }
161
+ : {},
162
+ );
163
+ }
89
164
  const lookupUrl = formatIri(url);
165
+ const portableMode = isPortableMode(this, options);
166
+ if (portableMode) {
167
+ const compatible = parseCompatibleEf61Reference(url);
168
+ if (compatible === null) {
169
+ span.end();
170
+ return rejectMalformedCompatibleReference(lookupUrl, options);
171
+ } else if (compatible != null) {
172
+ return await dereferencePortable(compatible.id, {
173
+ inferredGateways: [compatible.gateway],
174
+ });
175
+ }
176
+ }
90
177
  let fetchResult: RemoteDocument;
91
178
  try {
92
- fetchResult = await documentLoader(lookupUrl);
179
+ fetchResult = await documentLoader(lookupUrl, {
180
+ suppressError: options.suppressError,
181
+ });
93
182
  } catch (error) {
94
183
  span.setStatus({
95
184
  code: SpanStatusCode.ERROR,
@@ -97,7 +186,7 @@ async function* generateProperty(
97
186
  });
98
187
  span.end();
99
188
  if (options.suppressError) {
100
- getLogger(["fedify", "vocab"]).error(
189
+ getLogger(["fedify", "vocab"]).warn(
101
190
  "Failed to fetch {url}: {error}",
102
191
  { error, url: lookupUrl }
103
192
  );
@@ -106,52 +195,96 @@ async function* generateProperty(
106
195
  throw error;
107
196
  }
108
197
  const { document, documentUrl } = fetchResult;
109
- const baseUrl = parseIri(documentUrl);
198
+ // In portable mode, the document may turn out to be a portable object,
199
+ // which has to be verified with the same context documents:
200
+ const snapshot = portableMode
201
+ ? createSnapshotContextLoader(contextLoader, options.suppressError)
202
+ : null;
203
+ const scopedContext = createScopedContextLoader(
204
+ snapshot?.loader ?? contextLoader,
205
+ snapshot == null && options.suppressError,
206
+ );
110
207
  try {
111
- const obj = await this.#${property.singularName}_fromJsonLd(
112
- document,
113
- { documentLoader, contextLoader, tracerProvider, baseUrl }
114
- );
115
- if (obj?.id != null && !isTrustedIriOrigin(options, obj.id, baseUrl)) {
116
- if (options.crossOrigin === "throw") {
117
- throw new Error(
118
- "The object's @id (" + obj.id.href + ") has a different origin " +
119
- "than the document URL (" + baseUrl.href + "); refusing to return " +
120
- "the object. If you want to bypass this check and are aware of" +
121
- 'the security implications, set the crossOrigin option to "trust".'
208
+ let claim: PortableResponseClaim | null | undefined;
209
+ try {
210
+ const baseUrl = parseIri(documentUrl);
211
+ const obj = await this.#${property.singularName}_fromJsonLd(
212
+ document,
213
+ {
214
+ documentLoader,
215
+ contextLoader: scopedContext.loader,
216
+ tracerProvider,
217
+ verifyPortableObject: childVerifier,
218
+ baseUrl,
219
+ }
220
+ );
221
+ if (snapshot == null) {
222
+ markUnverifiedPortableClaim(obj, url, documentUrl);
223
+ } else {
224
+ claim = getPortableResponseClaim(documentUrl, obj.id);
225
+ }
226
+ if (claim === undefined) {
227
+ if (
228
+ obj?.id != null &&
229
+ !isTrustedIriOrigin(options, obj.id, baseUrl)
230
+ ) {
231
+ if (options.crossOrigin === "throw") {
232
+ throw new Error(
233
+ "The object's @id (" + obj.id.href + ") has a different origin " +
234
+ "than the document URL (" + baseUrl.href + "); refusing to return " +
235
+ "the object. If you want to bypass this check and are aware of" +
236
+ 'the security implications, set the crossOrigin option to "trust".'
237
+ );
238
+ }
239
+ getLogger(["fedify", "vocab"]).warn(
240
+ "The object's @id ({objectId}) has a different origin than the document " +
241
+ "URL ({documentUrl}); refusing to return the object. If you want to " +
242
+ "bypass this check and are aware of the security implications, " +
243
+ 'set the crossOrigin option to "trust".',
244
+ { ...fetchResult, objectId: obj.id.href },
245
+ );
246
+ span.end();
247
+ return null;
248
+ }
249
+ span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
250
+ span.setAttribute(
251
+ "activitypub.object.type",
252
+ // @ts-ignore: obj.constructor always has a typeId.
253
+ obj.constructor.typeId.href
122
254
  );
255
+ span.end();
256
+ return obj;
123
257
  }
124
- getLogger(["fedify", "vocab"]).warn(
125
- "The object's @id ({objectId}) has a different origin than the document " +
126
- "URL ({documentUrl}); refusing to return the object. If you want to " +
127
- "bypass this check and are aware of the security implications, " +
128
- 'set the crossOrigin option to "trust".',
129
- { ...fetchResult, objectId: obj.id.href },
130
- );
131
- return null;
258
+ } catch (e) {
259
+ if (options.suppressError) {
260
+ getLogger(["fedify", "vocab"]).warn(
261
+ "Failed to parse {url}: {error}",
262
+ { error: e, url: lookupUrl }
263
+ );
264
+ span.end();
265
+ return null;
266
+ }
267
+ span.setStatus({
268
+ code: SpanStatusCode.ERROR,
269
+ message: String(e),
270
+ });
271
+ span.end();
272
+ throw e;
132
273
  }
133
- span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
134
- span.setAttribute(
135
- "activitypub.object.type",
136
- // @ts-ignore: obj.constructor always has a typeId.
137
- obj.constructor.typeId.href
138
- );
139
- return obj;
140
- } catch (e) {
141
- if (options.suppressError) {
142
- getLogger(["fedify", "vocab"]).error(
143
- "Failed to parse {url}: {error}",
144
- { error: e, url: lookupUrl }
145
- );
146
- return null;
274
+ // The document stands for a portable object, so it is verified as
275
+ // one instead of being trusted because of where it came from:
276
+ if (claim === null) {
277
+ span.end();
278
+ return rejectMalformedCompatibleReference(documentUrl, options);
147
279
  }
148
- span.setStatus({
149
- code: SpanStatusCode.ERROR,
150
- message: String(e),
280
+ return await dereferencePortable(claim.id, {
281
+ inferredGateways: claim.inferredGateways,
282
+ response: fetchResult,
283
+ contextLoader: snapshot?.loader,
151
284
  });
152
- throw e;
153
285
  } finally {
154
- span.end();
286
+ scopedContext.release();
287
+ snapshot?.release();
155
288
  }
156
289
  });
157
290
  }
@@ -162,6 +295,7 @@ async function* generateProperty(
162
295
  documentLoader?: DocumentLoader,
163
296
  contextLoader?: DocumentLoader,
164
297
  tracerProvider?: TracerProvider,
298
+ verifyPortableObject?: PortableObjectVerifier,
165
299
  baseUrl?: URL
166
300
  }
167
301
  ): Promise<${getTypeNames(property.range, types)}> {
@@ -172,6 +306,7 @@ async function* generateProperty(
172
306
  const tracerProvider = options.tracerProvider ??
173
307
  this._tracerProvider ?? trace.getTracerProvider();
174
308
  const baseUrl = options.baseUrl;
309
+ const verifyPortableObject = options.verifyPortableObject;
175
310
  `;
176
311
  if (
177
312
  property.preprocessors != null &&
@@ -202,6 +337,7 @@ async function* generateProperty(
202
337
  documentLoader,
203
338
  contextLoader,
204
339
  tracerProvider,
340
+ verifyPortableObject,
205
341
  baseUrl,
206
342
  });
207
343
  if (_result instanceof Error) throw _result;
@@ -223,7 +359,13 @@ async function* generateProperty(
223
359
  try {
224
360
  return await ${rangeType.name}.fromJsonLd(
225
361
  jsonLd,
226
- { documentLoader, contextLoader, tracerProvider, baseUrl },
362
+ {
363
+ documentLoader,
364
+ contextLoader,
365
+ tracerProvider,
366
+ verifyPortableObject,
367
+ baseUrl,
368
+ },
227
369
  );
228
370
  } catch (e) {
229
371
  if (!(e instanceof TypeError)) throw e;
@@ -265,6 +407,21 @@ async function* generateProperty(
265
407
  suppressError?: boolean,
266
408
  tracerProvider?: TracerProvider,
267
409
  crossOrigin?: "ignore" | "throw" | "trust";
410
+ gateways?: readonly (string | URL)[];
411
+ verifyPortableObject?: PortableObjectVerifier;
412
+ /**
413
+ * Whether objects that this call newly fetches use the
414
+ * \`verifyPortableObject\` option of this call as their default
415
+ * verifier. Defaults to \`true\`. If \`false\`, the option
416
+ * applies to this call only, and fetched objects use this object's
417
+ * own default verifier, if any, as their default instead. Pass
418
+ * \`false\` for a temporary verifier, e.g., one that accepts
419
+ * everything. Objects that were already cached in this object or
420
+ * embedded in it keep their defaults either way, and this option
421
+ * itself is not passed on.
422
+ * @since 2.4.0
423
+ */
424
+ inheritPortableObjectVerifier?: boolean;
268
425
  } = {}
269
426
  ): Promise<${getTypeNames(property.range, types)} | null> {
270
427
  if (this._warning != null) {
@@ -274,20 +431,46 @@ async function* generateProperty(
274
431
  );
275
432
  }
276
433
  if (this.${await getFieldName(property.uri)}.length < 1) return null;
434
+ if (
435
+ options.verifyPortableObject == null &&
436
+ this._verifyPortableObject != null
437
+ ) {
438
+ options = {
439
+ ...options,
440
+ verifyPortableObject: this._verifyPortableObject,
441
+ };
442
+ }
277
443
  let v = this.${await getFieldName(property.uri)}[0];
278
444
  if (!(v instanceof URL) &&
279
- v.id != null &&
280
- !isTrustedIriOrigin(options, v.id, this.id) &&
281
445
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
282
- v = v.id;
446
+ if (v.id == null) {
447
+ if (isUnsecuredPortableObject(this)) {
448
+ warnUnverifiableEmbeddedObject(this, ${
449
+ JSON.stringify(property.uri)
450
+ });
451
+ return null;
452
+ }
453
+ } else if (
454
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
455
+ isUnsecuredPortableObject(this) ||
456
+ mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
457
+ ) {
458
+ v = v.id;
459
+ }
283
460
  }
284
461
  if (v instanceof URL) {
285
462
  const fetched =
286
463
  await this.#fetch${pascalCase(property.singularName)}(v, options);
287
464
  if (fetched == null) return null;
465
+ // Not cached, so that it is verified when the portable object
466
+ // policy applies later:
467
+ if (isUnverifiedPortableClaim(fetched)) return fetched;
288
468
  this.${await getFieldName(property.uri)}[0] = fetched;
289
469
  this.${await getFieldName(property.uri, "#_trust")}.add(0);
290
470
  this._cachedJsonLd = undefined;
471
+ recordPortableReferrer(this, fetched, ${
472
+ JSON.stringify(property.uri)
473
+ });
291
474
  return fetched;
292
475
  }
293
476
  `;
@@ -303,26 +486,43 @@ async function* generateProperty(
303
486
  ${JSON.stringify(property.compactName)}];
304
487
  const doc = Array.isArray(prop) ? prop[0] : prop;
305
488
  if (doc != null && typeof doc === "object" && "@context" in doc) {
306
- `;
307
- yield `
308
- v = await this.#${property.singularName}_fromJsonLd(doc, {
309
- ...options,
310
- baseUrl: ${cachedPropertyBaseUrl},
311
- });
312
- `;
313
- yield `
489
+ try {
490
+ v = await this.#${property.singularName}_fromJsonLd(doc, {
491
+ ...options,
492
+ // Keep the parent default for embedded objects.
493
+ verifyPortableObject: this._verifyPortableObject,
494
+ baseUrl: ${cachedPropertyBaseUrl},
495
+ });
496
+ } catch (error) {
497
+ if (!options.suppressError) throw error;
498
+ getLogger(["fedify", "vocab"]).debug(
499
+ "Failed to parse embedded value of {property}: {error}",
500
+ { property: ${JSON.stringify(property.uri)}, error },
501
+ );
502
+ return null;
503
+ }
314
504
  }
315
505
  }
316
506
  `;
317
507
  }
318
508
  yield `
319
509
  if (v?.id != null &&
320
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
510
+ ((${
511
+ type.trustEmbeddedObjects === false ? "" : "this.id != null && "
512
+ }!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
513
+ isUnsecuredPortableObject(this) ||
514
+ mustDereferencePortableObject(
515
+ this, ${trustOwner}, v.id, options,
516
+ )) &&
321
517
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
322
518
  if (options.crossOrigin === "throw") {
323
519
  throw new Error(
324
520
  "The property object's @id (" + v.id.href + ") has a different " +
325
- "origin than the property owner's @id (" + this.id.href + "); " +
521
+ "origin than the property owner's @id (" + ${
522
+ type.trustEmbeddedObjects === false
523
+ ? '"untrusted metadata"'
524
+ : "(this.id?.href ?? null)"
525
+ } + "); " +
326
526
  "refusing to return the object. If you want to bypass this " +
327
527
  "check and are aware of the security implications, set the " +
328
528
  'crossOrigin option to "trust".'
@@ -334,10 +534,15 @@ async function* generateProperty(
334
534
  "return the object. If you want to bypass this check and are " +
335
535
  "aware of the security implications, set the crossOrigin option " +
336
536
  'to "trust".',
337
- { objectId: v.id.href, parentObjectId: this.id.href },
537
+ { objectId: v.id.href, parentObjectId: ${
538
+ type.trustEmbeddedObjects === false
539
+ ? '"untrusted metadata"'
540
+ : "(this.id?.href ?? null)"
541
+ } },
338
542
  );
339
543
  return null;
340
544
  }
545
+ recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
341
546
  return v;
342
547
  }
343
548
  `;
@@ -370,6 +575,21 @@ async function* generateProperty(
370
575
  suppressError?: boolean,
371
576
  tracerProvider?: TracerProvider,
372
577
  crossOrigin?: "ignore" | "throw" | "trust";
578
+ gateways?: readonly (string | URL)[];
579
+ verifyPortableObject?: PortableObjectVerifier;
580
+ /**
581
+ * Whether objects that this call newly fetches use the
582
+ * \`verifyPortableObject\` option of this call as their default
583
+ * verifier. Defaults to \`true\`. If \`false\`, the option
584
+ * applies to this call only, and fetched objects use this object's
585
+ * own default verifier, if any, as their default instead. Pass
586
+ * \`false\` for a temporary verifier, e.g., one that accepts
587
+ * everything. Objects that were already cached in this object or
588
+ * embedded in it keep their defaults either way, and this option
589
+ * itself is not passed on.
590
+ * @since 2.4.0
591
+ */
592
+ inheritPortableObjectVerifier?: boolean;
373
593
  } = {}
374
594
  ): AsyncIterable<${getTypeNames(property.range, types)}> {
375
595
  if (this._warning != null) {
@@ -378,22 +598,51 @@ async function* generateProperty(
378
598
  this._warning.values
379
599
  );
380
600
  }
601
+ if (
602
+ options.verifyPortableObject == null &&
603
+ this._verifyPortableObject != null
604
+ ) {
605
+ options = {
606
+ ...options,
607
+ verifyPortableObject: this._verifyPortableObject,
608
+ };
609
+ }
381
610
  const vs = this.${await getFieldName(property.uri)};
382
611
  for (let i = 0; i < vs.length; i++) {
383
612
  let v = vs[i];
384
613
  if (!(v instanceof URL) &&
385
- v.id != null &&
386
- !isTrustedIriOrigin(options, v.id, this.id) &&
387
614
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
388
- v = v.id;
615
+ if (v.id == null) {
616
+ if (isUnsecuredPortableObject(this)) {
617
+ warnUnverifiableEmbeddedObject(this, ${
618
+ JSON.stringify(property.uri)
619
+ });
620
+ continue;
621
+ }
622
+ } else if (
623
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
624
+ isUnsecuredPortableObject(this) ||
625
+ mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
626
+ ) {
627
+ v = v.id;
628
+ }
389
629
  }
390
630
  if (v instanceof URL) {
391
631
  const fetched =
392
632
  await this.#fetch${pascalCase(property.singularName)}(v, options);
393
633
  if (fetched == null) continue;
634
+ // Not cached, so that it is verified when the portable object
635
+ // policy applies later:
636
+ if (isUnverifiedPortableClaim(fetched)) {
637
+ yield fetched;
638
+ continue;
639
+ }
394
640
  vs[i] = fetched;
395
641
  this.${await getFieldName(property.uri, "#_trust")}.add(i);
396
642
  this._cachedJsonLd = undefined;
643
+ recordPortableReferrer(this, fetched, ${
644
+ JSON.stringify(property.uri)
645
+ });
397
646
  yield fetched;
398
647
  continue;
399
648
  }
@@ -410,26 +659,45 @@ async function* generateProperty(
410
659
  ${JSON.stringify(property.compactName)}];
411
660
  const obj = Array.isArray(prop) ? prop[i] : prop;
412
661
  if (obj != null && typeof obj === "object" && "@context" in obj) {
413
- `;
414
- yield `
415
- v = await this.#${property.singularName}_fromJsonLd(obj, {
416
- ...options,
417
- baseUrl: ${cachedPropertyBaseUrl},
418
- });
419
- `;
420
- yield `
662
+ try {
663
+ v = await this.#${property.singularName}_fromJsonLd(obj, {
664
+ ...options,
665
+ // Keep the parent default for embedded objects.
666
+ verifyPortableObject: this._verifyPortableObject,
667
+ baseUrl: ${cachedPropertyBaseUrl},
668
+ });
669
+ } catch (error) {
670
+ if (!options.suppressError) throw error;
671
+ getLogger(["fedify", "vocab"]).debug(
672
+ "Failed to parse embedded value of {property} at index {index}: {error}",
673
+ { property: ${
674
+ JSON.stringify(property.uri)
675
+ }, index: i, error },
676
+ );
677
+ continue;
678
+ }
421
679
  }
422
680
  }
423
681
  `;
424
682
  }
425
683
  yield `
426
684
  if (v?.id != null &&
427
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
685
+ ((${
686
+ type.trustEmbeddedObjects === false ? "" : "this.id != null && "
687
+ }!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
688
+ isUnsecuredPortableObject(this) ||
689
+ mustDereferencePortableObject(
690
+ this, ${trustOwner}, v.id, options,
691
+ )) &&
428
692
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
429
693
  if (options.crossOrigin === "throw") {
430
694
  throw new Error(
431
695
  "The property object's @id (" + v.id.href + ") has a different " +
432
- "origin than the property owner's @id (" + this.id.href + "); " +
696
+ "origin than the property owner's @id (" + ${
697
+ type.trustEmbeddedObjects === false
698
+ ? '"untrusted metadata"'
699
+ : "(this.id?.href ?? null)"
700
+ } + "); " +
433
701
  "refusing to return the object. If you want to bypass this " +
434
702
  "check and are aware of the security implications, set the " +
435
703
  'crossOrigin option to "trust".'
@@ -441,10 +709,15 @@ async function* generateProperty(
441
709
  "return the object. If you want to bypass this check and are " +
442
710
  "aware of the security implications, set the crossOrigin " +
443
711
  'option to "trust".',
444
- { objectId: v.id.href, parentObjectId: this.id.href },
712
+ { objectId: v.id.href, parentObjectId: ${
713
+ type.trustEmbeddedObjects === false
714
+ ? '"untrusted metadata"'
715
+ : "(this.id?.href ?? null)"
716
+ } },
445
717
  );
446
718
  continue;
447
719
  }
720
+ recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
448
721
  yield v;
449
722
  }
450
723
  }
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,