@fedify/vocab-tools 2.4.0-pr.934.40 → 2.4.0

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/dist/mod.cjs CHANGED
@@ -8,15 +8,17 @@ let yaml = require("yaml");
8
8
  let es_toolkit = require("es-toolkit");
9
9
  //#region deno.json
10
10
  var name = "@fedify/vocab-tools";
11
- var version = "2.4.0-pr.934.40+3331d302";
11
+ var version = "2.4.0";
12
12
  //#endregion
13
13
  //#region src/type.ts
14
14
  const HEURISTICS_CONTEXTS = [
15
15
  "https://www.w3.org/ns/activitystreams",
16
16
  "https://w3id.org/security/v1",
17
17
  "https://w3id.org/security/data-integrity/v1",
18
+ "https://w3id.org/security/data-integrity/v2",
18
19
  "https://www.w3.org/ns/did/v1",
19
- "https://w3id.org/security/multikey/v1"
20
+ "https://w3id.org/security/multikey/v1",
21
+ "https://w3id.org/fep/ef61"
20
22
  ];
21
23
  const scalarTypes = {
22
24
  "http://www.w3.org/2001/XMLSchema#boolean": {
@@ -184,7 +186,8 @@ const scalarTypes = {
184
186
  dataCheck(v) {
185
187
  return `typeof ${v} === "object" && "@language" in ${v} && "@value" in ${v}
186
188
  && typeof ${v}["@language"] === "string"
187
- && typeof ${v}["@value"] === "string"`;
189
+ && typeof ${v}["@value"] === "string"
190
+ && isValidLanguageTag(${v}["@language"])`;
188
191
  },
189
192
  decoder(v) {
190
193
  return `new LanguageString(${v}["@value"], ${v}["@language"])`;
@@ -315,12 +318,35 @@ const scalarTypes = {
315
318
  return `formatIri(${v})`;
316
319
  },
317
320
  dataCheck(v) {
318
- return `typeof ${v} === "object" && "@value" in ${v}
319
- && typeof ${v}["@value"] === "string"
320
- && ${v}["@value"] !== "" && ${v}["@value"] !== "/"`;
321
+ return `typeof ${v} === "object" &&
322
+ ((typeof ${v}["@id"] === "string" && canDecodeIri(${v}["@id"])) ||
323
+ (typeof ${v}["@value"] === "string" && canDecodeIri(${v}["@value"])))`;
324
+ },
325
+ decoder(v) {
326
+ return `decodeIri(typeof ${v}["@id"] === "string" ? ${v}["@id"] : ${v}["@value"])`;
327
+ },
328
+ skipUnparsable: true
329
+ },
330
+ "fedify:gatewayUrl": {
331
+ name: "URL",
332
+ typeGuard(v) {
333
+ return `${v} instanceof URL && isGatewayUrl(${v})`;
334
+ },
335
+ encoder(v) {
336
+ return `{ "@id": ${v}.origin }`;
337
+ },
338
+ compactEncoder(v) {
339
+ return `${v}.origin`;
340
+ },
341
+ dataCheck(v) {
342
+ return `${v} != null && typeof ${v} === "object" &&
343
+ (("@id" in ${v} && typeof ${v}["@id"] === "string" &&
344
+ ${v}["@id"] !== "" && ${v}["@id"] !== "/") ||
345
+ ("@value" in ${v} && typeof ${v}["@value"] === "string" &&
346
+ ${v}["@value"] !== "" && ${v}["@value"] !== "/"))`;
321
347
  },
322
348
  decoder(v) {
323
- return `parseIri(${v}["@value"])`;
349
+ return `parseGatewayUrl(typeof ${v}["@id"] === "string" ? ${v}["@id"] : ${v}["@value"])`;
324
350
  }
325
351
  },
326
352
  "fedify:publicKey": {
@@ -533,6 +559,9 @@ function getDecoder(typeUri, types, variable, optionsVariable, baseUrlVariable)
533
559
  )`;
534
560
  throw new Error(`Unknown type: ${typeUri}`);
535
561
  }
562
+ function skipsUnparsable(typeUri) {
563
+ return scalarTypes[typeUri]?.skipUnparsable ?? false;
564
+ }
536
565
  function getDataCheck(typeUri, types, variable) {
537
566
  if (typeUri in scalarTypes) return scalarTypes[typeUri].dataCheck(variable);
538
567
  if (typeUri in types) {
@@ -712,6 +741,7 @@ function* generatePreprocessorBlock(property, rangeTypeName, variable, baseUrlEx
712
741
  documentLoader: options.documentLoader,
713
742
  contextLoader: options.contextLoader,
714
743
  tracerProvider: options.tracerProvider,
744
+ verifyPortableObject: options.verifyPortableObject,
715
745
  baseUrl: ${baseUrlExpr},
716
746
  });
717
747
  if (_result instanceof Error) throw _result;
@@ -760,6 +790,10 @@ async function* generateEncoder(typeUri, types) {
760
790
  ...options,
761
791
  contextLoader: options.contextLoader ?? getDocumentLoader(),
762
792
  };
793
+ // Joins the enclosing frame's signed-value scope, or starts one when this
794
+ // frame is the outermost. Only the owning frame puts retained signed
795
+ // child documents back in place of their placeholders.
796
+ const signedValues = enterSignedValueScope(options);
763
797
  `;
764
798
  if (isCompactableType(typeUri, types)) {
765
799
  yield `
@@ -790,9 +824,13 @@ async function* generateEncoder(typeUri, types) {
790
824
  for (const v of this.${await getFieldName(property.uri)}) {
791
825
  const item = (
792
826
  `;
793
- if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? formatIri(v) : ";
827
+ if (!areAllScalarTypes(property.range, types)) {
828
+ yield "retainedSignedValueRef(v, signedValues.scope) ?? (";
829
+ yield "v instanceof URL ? formatIri(v) : ";
830
+ }
794
831
  const encoders = getEncoders(property.range, types, "v", "options", true);
795
832
  for (const code of encoders) yield code;
833
+ if (!areAllScalarTypes(property.range, types)) yield ")";
796
834
  yield `
797
835
  );
798
836
  compactItems.push(item);
@@ -823,7 +861,9 @@ async function* generateEncoder(typeUri, types) {
823
861
  ${type.typeless ? "" : `result["type"] = ${JSON.stringify(type.compactName ?? type.uri)};`}
824
862
  if (this.id != null) result["id"] = formatIri(this.id);
825
863
  result["@context"] = ${JSON.stringify(type.defaultContext)};
826
- return result;
864
+ return signedValues.owner
865
+ ? resolveSignedValues(result, signedValues.scope)
866
+ : result;
827
867
  }
828
868
  `;
829
869
  }
@@ -847,12 +887,21 @@ async function* generateEncoder(typeUri, types) {
847
887
  yield `
848
888
  array = [];
849
889
  for (const v of this.${await getFieldName(property.uri)}) {
850
- const element = (
890
+ let element = (
851
891
  `;
852
- if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? { \"@id\": formatIri(v) } : ";
892
+ if (!areAllScalarTypes(property.range, types)) {
893
+ yield "retainedSignedValueRef(v, signedValues.scope) ?? (";
894
+ yield "v instanceof URL ? { \"@id\": formatIri(v) } : ";
895
+ }
853
896
  for (const code of getEncoders(property.range, types, "v", "options")) yield code;
897
+ if (!areAllScalarTypes(property.range, types)) yield ")";
854
898
  yield `
855
899
  );
900
+ if (Array.isArray(element)) {
901
+ if (element.length < 1) continue;
902
+ if (element.length == 1) element = element[0];
903
+ }
904
+ if (typeof element === "undefined") continue;
856
905
  `;
857
906
  if (isNonFunctionalProperty(property) && property.container === "graph") yield `array.push({ "@graph": element });`;
858
907
  else yield `array.push(element);`;
@@ -880,16 +929,35 @@ async function* generateEncoder(typeUri, types) {
880
929
  if (options.format === "expand") {
881
930
  return await jsonld.expand(
882
931
  values,
883
- { documentLoader: options.contextLoader },
932
+ {
933
+ documentLoader: options.contextLoader,
934
+ keepFreeFloatingNodes: true,
935
+ },
884
936
  );
885
937
  }
886
- const docContext = options.context ??
938
+ let docContext: Parameters<typeof jsonld.compact>[1] = options.context ??
887
939
  ${JSON.stringify(type.defaultContext)};
888
- const compacted = await jsonld.compact(
940
+ let compacted = await jsonld.compact(
889
941
  values,
890
942
  docContext,
891
943
  { documentLoader: options.contextLoader },
892
944
  );
945
+ `;
946
+ if (Object.values(types).some((t) => t.properties.some((p) => p.extraContext != null))) yield `
947
+ if (options.context == null) {
948
+ const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
949
+ const additionalContexts = getExtraContexts(compacted).filter(
950
+ context => !currentContexts.includes(context)
951
+ );
952
+ if (additionalContexts.length > 0) {
953
+ docContext = [...currentContexts, ...additionalContexts];
954
+ compacted = await jsonld.compact(values, docContext, {
955
+ documentLoader: options.contextLoader,
956
+ });
957
+ }
958
+ }
959
+ `;
960
+ yield `
893
961
  if (docContext != null) {
894
962
  // Embed context
895
963
  `;
@@ -913,12 +981,14 @@ async function* generateEncoder(typeUri, types) {
913
981
  }
914
982
  yield `
915
983
  }
916
- return compacted;
984
+ return signedValues.owner
985
+ ? resolveSignedValues(compacted, signedValues.scope)
986
+ : compacted;
917
987
  }
918
988
 
919
989
  protected ${emitOverride$1(typeUri, types)} isCompactable(): boolean {
920
990
  `;
921
- for (const property of type.properties) if (!property.range.every((r) => isCompactableType(r, types))) yield `
991
+ for (const property of type.properties) if (property.extraContext != null || !property.range.every((r) => isCompactableType(r, types))) yield `
922
992
  if (
923
993
  this.${await getFieldName(property.uri)} != null &&
924
994
  this.${await getFieldName(property.uri)}.length > 0
@@ -940,6 +1010,10 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
940
1010
  * - \`contextLoader\`: The loader for remote JSON-LD contexts.
941
1011
  * - \`tracerProvider\`: The OpenTelemetry tracer provider to use.
942
1012
  * If omitted, the global tracer provider is used.
1013
+ * - \`verifyPortableObject\`: The default FEP-ef61 portable
1014
+ * object verifier for the property accessors of the
1015
+ * returned object and the objects obtained from it. It
1016
+ * does not verify the given \`json\` itself.
943
1017
  * @returns The object of this type.
944
1018
  * @throws {TypeError} If the given \`json\` is invalid.
945
1019
  */
@@ -949,6 +1023,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
949
1023
  documentLoader?: DocumentLoader,
950
1024
  contextLoader?: DocumentLoader,
951
1025
  tracerProvider?: TracerProvider,
1026
+ verifyPortableObject?: PortableObjectVerifier,
952
1027
  baseUrl?: URL,
953
1028
  } = {},
954
1029
  ): Promise<${type.name}> {
@@ -987,6 +1062,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
987
1062
  documentLoader?: DocumentLoader,
988
1063
  contextLoader?: DocumentLoader,
989
1064
  tracerProvider?: TracerProvider,
1065
+ verifyPortableObject?: PortableObjectVerifier,
990
1066
  baseUrl?: URL,
991
1067
  } = {},
992
1068
  ): Promise<${type.name}> {
@@ -1003,7 +1079,17 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1003
1079
  // deno-lint-ignore no-explicit-any
1004
1080
  let values: Record<string, any[]> & { "@id"?: string };
1005
1081
  let expanded: unknown[];
1006
- if (globalThis.Object.keys(json).length == 0) {
1082
+ if (
1083
+ "_fromSubclass" in options &&
1084
+ options._fromSubclass &&
1085
+ typeof json === "object" &&
1086
+ !Array.isArray(json)
1087
+ ) {
1088
+ values =
1089
+ // deno-lint-ignore no-explicit-any
1090
+ json as (Record<string, any[]> & { "@id"?: string });
1091
+ expanded = [values];
1092
+ } else if (globalThis.Object.keys(json).length == 0) {
1007
1093
  values = {};
1008
1094
  expanded = [values];
1009
1095
  } else {
@@ -1060,8 +1146,9 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1060
1146
  );
1061
1147
  `;
1062
1148
  else yield `
1063
- delete values["@type"];
1064
- const instance = await super.fromJsonLd(values, {
1149
+ const superValues = structuredClone(values);
1150
+ delete superValues["@type"];
1151
+ const instance = await super.fromJsonLd(superValues, {
1065
1152
  ...options,
1066
1153
  // @ts-ignore: an internal option
1067
1154
  _fromSubclass: true,
@@ -1070,25 +1157,35 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1070
1157
  throw new TypeError("Unexpected type: " + instance.constructor.name);
1071
1158
  }
1072
1159
  `;
1160
+ yield `
1161
+ let shouldCacheJsonLd = instance._shouldCacheJsonLd;
1162
+ `;
1073
1163
  for (const property of type.properties) {
1074
1164
  const variable = await getFieldName(property.uri, "");
1075
1165
  yield await generateField(property, types, "const ");
1076
1166
  const arrayVariable = `${variable}__array`;
1077
1167
  yield `
1078
- let ${arrayVariable} = values[${JSON.stringify(property.uri)}];
1168
+ let ${arrayVariable} = ${property.uri === "https://w3id.org/fep/ef61/gateways" ? "getPortableActorGateways(values) as typeof values[string]" : `values[${JSON.stringify(property.uri)}]`};
1079
1169
  `;
1080
1170
  if (property.functional && property.redundantProperties != null) for (const prop of property.redundantProperties) yield `
1081
1171
  if (${arrayVariable} == null || ${arrayVariable}.length < 1) {
1082
1172
  ${arrayVariable} = values[${JSON.stringify(prop.uri)}];
1083
1173
  }
1084
1174
  `;
1175
+ let itemsExpression = `${arrayVariable}.length === 1 && "@list" in ${arrayVariable}[0]
1176
+ ? ${arrayVariable}[0]["@list"]
1177
+ : ${arrayVariable}`;
1178
+ if (isNonFunctionalProperty(property) && property.container === "graph") itemsExpression = `(${itemsExpression}).flatMap((item: unknown) =>
1179
+ item != null && typeof item === "object" && "@graph" in item &&
1180
+ Array.isArray((item as { "@graph": unknown })["@graph"])
1181
+ ? (item as { "@graph": unknown[] })["@graph"]
1182
+ : [item]
1183
+ )`;
1085
1184
  yield `
1086
1185
  for (
1087
1186
  const v of ${arrayVariable} == null
1088
1187
  ? []
1089
- : ${arrayVariable}.length === 1 && "@list" in ${arrayVariable}[0]
1090
- ? ${arrayVariable}[0]["@list"]
1091
- : ${arrayVariable}
1188
+ : ${itemsExpression}
1092
1189
  ) {
1093
1190
  if (v == null) continue;
1094
1191
  `;
@@ -1102,29 +1199,47 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1102
1199
  continue;
1103
1200
  }
1104
1201
  `;
1105
- if (property.range.length == 1) yield `
1106
- const decoded = ${getDecoder(property.range[0], types, "v", "options", propertyBaseUrl)};
1107
- if (typeof decoded === "undefined") continue;
1108
- ${variable}.push(decoded);`;
1109
- else {
1110
- yield `
1202
+ yield `
1111
1203
  const decoded =
1112
- `;
1204
+ `;
1205
+ const lenient = property.range.length == 1 && skipsUnparsable(property.range[0]);
1206
+ if (property.range.length == 1) {
1207
+ if (lenient) {
1208
+ yield getDataCheck(property.range[0], types, "v");
1209
+ yield " ? ";
1210
+ }
1211
+ yield getDecoder(property.range[0], types, "v", "options", propertyBaseUrl);
1212
+ if (lenient) yield " : undefined";
1213
+ } else {
1113
1214
  const decoders = getDecoders(property.range, types, "v", "options", propertyBaseUrl);
1114
1215
  for (const code of decoders) yield code;
1115
- yield `
1216
+ }
1217
+ yield `
1116
1218
  ;
1117
- if (typeof decoded === "undefined") continue;
1219
+ `;
1220
+ yield `
1221
+ if (typeof decoded === "undefined") {
1222
+ shouldCacheJsonLd = false;
1223
+ continue;
1224
+ }
1225
+ `;
1226
+ yield `
1227
+ if (!this._shouldCacheDecodedJsonLd(decoded)) {
1228
+ shouldCacheJsonLd = false;
1229
+ }
1118
1230
  ${variable}.push(decoded);
1119
- `;
1120
- }
1231
+ `;
1121
1232
  yield `
1122
1233
  }
1123
1234
  instance.${await getFieldName(property.uri)} = ${variable};
1124
1235
  `;
1125
1236
  }
1126
1237
  yield `
1127
- if (!("_fromSubclass" in options) || !options._fromSubclass) {
1238
+ instance._shouldCacheJsonLd = shouldCacheJsonLd;
1239
+ if (
1240
+ shouldCacheJsonLd &&
1241
+ (!("_fromSubclass" in options) || !options._fromSubclass)
1242
+ ) {
1128
1243
  try {
1129
1244
  if (cacheJsonLd != null && cacheJsonLd !== expanded) {
1130
1245
  const compactArray = Array.isArray(json) && json.length === 1;
@@ -1190,6 +1305,7 @@ async function* generateConstructor(typeUri, types) {
1190
1305
  documentLoader?: DocumentLoader,
1191
1306
  contextLoader?: DocumentLoader,
1192
1307
  tracerProvider?: TracerProvider,
1308
+ verifyPortableObject?: PortableObjectVerifier,
1193
1309
  } = {},
1194
1310
  ) {
1195
1311
  `;
@@ -1197,6 +1313,7 @@ async function* generateConstructor(typeUri, types) {
1197
1313
  this.#documentLoader = options.documentLoader;
1198
1314
  this.#contextLoader = options.contextLoader;
1199
1315
  this.#tracerProvider = options.tracerProvider;
1316
+ this.#verifyPortableObject = options.verifyPortableObject;
1200
1317
  const baseUrl = (options as { baseUrl?: URL }).baseUrl;
1201
1318
  this.#_baseUrl = baseUrl == null ? undefined : new URL(baseUrl.href);
1202
1319
  if ("$warning" in options) {
@@ -1291,6 +1408,13 @@ async function* generateCloner(typeUri, types) {
1291
1408
  yield `
1292
1409
  /**
1293
1410
  * Clones this instance, optionally updating it with the given values.
1411
+ *
1412
+ * A clone never inherits a signed JSON-LD representation retained by
1413
+ * \`signObject()\`: the clone may differ from the document that the proof
1414
+ * covers, so embedding the original secured JSON in a parent document
1415
+ * would be wrong. Sign the clone again if it has to be embedded as a
1416
+ * secured child.
1417
+ *
1294
1418
  * @param values The values to update the clone with.
1295
1419
  * @param options The options to use for cloning.
1296
1420
  * @returns The cloned instance.
@@ -1304,6 +1428,7 @@ async function* generateCloner(typeUri, types) {
1304
1428
  options: {
1305
1429
  documentLoader?: DocumentLoader,
1306
1430
  contextLoader?: DocumentLoader,
1431
+ verifyPortableObject?: PortableObjectVerifier,
1307
1432
  } = {}
1308
1433
  ): ${type.name} {
1309
1434
  if (this._warning != null) {
@@ -1321,6 +1446,7 @@ async function* generateCloner(typeUri, types) {
1321
1446
  { id: values.id ?? this.id },
1322
1447
  options
1323
1448
  );
1449
+ copyPortableProvenance(this, clone);
1324
1450
  `;
1325
1451
  else yield `const clone = super.clone(values, options) as unknown as ${type.name};`;
1326
1452
  for (const property of type.properties) {
@@ -1416,12 +1542,12 @@ async function* generateInspector(typeUri, types) {
1416
1542
  [Symbol.for("Deno.customInspect")]: (
1417
1543
  inspect: typeof Deno.inspect,
1418
1544
  options: Deno.InspectOptions,
1419
- ): string => "URL " + inspect(this.id!.href, options),
1545
+ ): string => "URL " + inspect(inspectIri(this.id!), options),
1420
1546
  [Symbol.for("nodejs.util.inspect.custom")]: (
1421
1547
  _depth: number,
1422
1548
  options: unknown,
1423
1549
  inspect: (value: unknown, options: unknown) => string,
1424
- ): string => "URL " + inspect(this.id!.href, options),
1550
+ ): string => "URL " + inspect(inspectIri(this.id!), options),
1425
1551
  };
1426
1552
  }
1427
1553
  `;
@@ -1437,12 +1563,12 @@ async function* generateInspector(typeUri, types) {
1437
1563
  [Symbol.for("Deno.customInspect")]: (
1438
1564
  inspect: typeof Deno.inspect,
1439
1565
  options: Deno.InspectOptions,
1440
- ): string => "URL " + inspect(v.href, options),
1566
+ ): string => "URL " + inspect(inspectIri(v), options),
1441
1567
  [Symbol.for("nodejs.util.inspect.custom")]: (
1442
1568
  _depth: number,
1443
1569
  options: unknown,
1444
1570
  inspect: (value: unknown, options: unknown) => string,
1445
- ): string => "URL " + inspect(v.href, options),
1571
+ ): string => "URL " + inspect(inspectIri(v), options),
1446
1572
  }
1447
1573
  : v);
1448
1574
  `;
@@ -1512,6 +1638,7 @@ function emitOverride(typeUri, types, property) {
1512
1638
  }
1513
1639
  async function* generateProperty(type, property, types, moduleVarNames) {
1514
1640
  const override = emitOverride(type.uri, types, property);
1641
+ const trustOwner = type.trustEmbeddedObjects === false ? "null" : "this.id";
1515
1642
  const doc = `\n/** ${property.description.replaceAll("\n", "\n * ")}\n */\n`;
1516
1643
  const cachedPropertyBaseUrl = `(options as { baseUrl?: URL }).baseUrl ??
1517
1644
  this._baseUrl ??
@@ -1552,6 +1679,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1552
1679
  suppressError?: boolean,
1553
1680
  tracerProvider?: TracerProvider,
1554
1681
  crossOrigin?: "ignore" | "throw" | "trust";
1682
+ gateways?: readonly (string | URL)[];
1683
+ verifyPortableObject?: PortableObjectVerifier;
1684
+ inheritPortableObjectVerifier?: boolean;
1555
1685
  } = {},
1556
1686
  ): Promise<${getTypeNames(property.range, types)} | null> {
1557
1687
  const documentLoader =
@@ -1564,11 +1694,96 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1564
1694
  ${JSON.stringify(name)},
1565
1695
  ${JSON.stringify(version)},
1566
1696
  );
1697
+ // The fetched object uses the verifier given to this call (which falls
1698
+ // back to the parent's own default) for its accessors by default,
1699
+ // unless the call keeps its verifier to itself:
1700
+ const childVerifier = options.inheritPortableObjectVerifier === false
1701
+ ? this._verifyPortableObject
1702
+ : options.verifyPortableObject;
1567
1703
  return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
1704
+ const dereferencePortable = async (
1705
+ portableUrl: URL,
1706
+ extra: {
1707
+ inferredGateways?: readonly URL[];
1708
+ response?: RemoteDocument;
1709
+ contextLoader?: DocumentLoader;
1710
+ } = {},
1711
+ ) => {
1712
+ try {
1713
+ const obj = await dereferencePortableIri(portableUrl, {
1714
+ documentLoader,
1715
+ contextLoader: extra.contextLoader ?? contextLoader,
1716
+ tracerProvider,
1717
+ gateways: options.gateways,
1718
+ inferredGateways: extra.inferredGateways,
1719
+ response: extra.response,
1720
+ verifyPortableObject: options.verifyPortableObject,
1721
+ referrer: {
1722
+ object: this,
1723
+ property: ${JSON.stringify(property.uri)},
1724
+ },
1725
+ suppressError: options.suppressError,
1726
+ crossOrigin: options.crossOrigin,
1727
+ span,
1728
+ parse: (document, parseOptions) =>
1729
+ this.#${property.singularName}_fromJsonLd(document, {
1730
+ documentLoader,
1731
+ contextLoader: parseOptions.contextLoader,
1732
+ tracerProvider,
1733
+ verifyPortableObject: childVerifier,
1734
+ baseUrl: parseOptions.baseUrl,
1735
+ }),
1736
+ });
1737
+ if (obj != null) {
1738
+ span.setAttribute(
1739
+ "activitypub.object.id",
1740
+ (obj.id ?? portableUrl).href,
1741
+ );
1742
+ span.setAttribute(
1743
+ "activitypub.object.type",
1744
+ // @ts-ignore: obj.constructor always has a typeId.
1745
+ obj.constructor.typeId.href
1746
+ );
1747
+ }
1748
+ return obj;
1749
+ } catch (e) {
1750
+ span.setStatus({
1751
+ code: SpanStatusCode.ERROR,
1752
+ message: String(e),
1753
+ });
1754
+ throw e;
1755
+ } finally {
1756
+ span.end();
1757
+ }
1758
+ };
1759
+ if (isPortableIri(url)) {
1760
+ // A reference in a portable actor's own document, e.g., its outbox,
1761
+ // has no location hints, so the actor's gateways are used instead:
1762
+ return await dereferencePortable(
1763
+ url,
1764
+ options.gateways == null
1765
+ ? { inferredGateways: getReferrerGateways(this, url) }
1766
+ : {},
1767
+ );
1768
+ }
1568
1769
  const lookupUrl = formatIri(url);
1770
+ const portableMode = isPortableMode(this, options);
1771
+ if (portableMode) {
1772
+ const compatible = parseCompatibleEf61Reference(url);
1773
+ if (compatible === null) {
1774
+ span.end();
1775
+ return rejectMalformedCompatibleReference(lookupUrl, options);
1776
+ } else if (compatible != null) {
1777
+ return await dereferencePortable(compatible.id, {
1778
+ inferredGateways: [compatible.gateway],
1779
+ });
1780
+ }
1781
+ }
1569
1782
  let fetchResult: RemoteDocument;
1570
1783
  try {
1571
- fetchResult = await documentLoader(lookupUrl);
1784
+ fetchResult = await documentLoader(lookupUrl, {
1785
+ suppressError: options.suppressError,
1786
+ });
1572
1787
  } catch (error) {
1573
1788
  span.setStatus({
1574
1789
  code: SpanStatusCode.ERROR,
@@ -1576,7 +1791,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1576
1791
  });
1577
1792
  span.end();
1578
1793
  if (options.suppressError) {
1579
- getLogger(["fedify", "vocab"]).error(
1794
+ getLogger(["fedify", "vocab"]).warn(
1580
1795
  "Failed to fetch {url}: {error}",
1581
1796
  { error, url: lookupUrl }
1582
1797
  );
@@ -1585,52 +1800,96 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1585
1800
  throw error;
1586
1801
  }
1587
1802
  const { document, documentUrl } = fetchResult;
1588
- const baseUrl = parseIri(documentUrl);
1803
+ // In portable mode, the document may turn out to be a portable object,
1804
+ // which has to be verified with the same context documents:
1805
+ const snapshot = portableMode
1806
+ ? createSnapshotContextLoader(contextLoader, options.suppressError)
1807
+ : null;
1808
+ const scopedContext = createScopedContextLoader(
1809
+ snapshot?.loader ?? contextLoader,
1810
+ snapshot == null && options.suppressError,
1811
+ );
1589
1812
  try {
1590
- const obj = await this.#${property.singularName}_fromJsonLd(
1591
- document,
1592
- { documentLoader, contextLoader, tracerProvider, baseUrl }
1593
- );
1594
- if (obj?.id != null && !isTrustedIriOrigin(options, obj.id, baseUrl)) {
1595
- if (options.crossOrigin === "throw") {
1596
- throw new Error(
1597
- "The object's @id (" + obj.id.href + ") has a different origin " +
1598
- "than the document URL (" + baseUrl.href + "); refusing to return " +
1599
- "the object. If you want to bypass this check and are aware of" +
1600
- 'the security implications, set the crossOrigin option to "trust".'
1813
+ let claim: PortableResponseClaim | null | undefined;
1814
+ try {
1815
+ const baseUrl = parseIri(documentUrl);
1816
+ const obj = await this.#${property.singularName}_fromJsonLd(
1817
+ document,
1818
+ {
1819
+ documentLoader,
1820
+ contextLoader: scopedContext.loader,
1821
+ tracerProvider,
1822
+ verifyPortableObject: childVerifier,
1823
+ baseUrl,
1824
+ }
1825
+ );
1826
+ if (snapshot == null) {
1827
+ markUnverifiedPortableClaim(obj, url, documentUrl);
1828
+ } else {
1829
+ claim = getPortableResponseClaim(documentUrl, obj.id);
1830
+ }
1831
+ if (claim === undefined) {
1832
+ if (
1833
+ obj?.id != null &&
1834
+ !isTrustedIriOrigin(options, obj.id, baseUrl)
1835
+ ) {
1836
+ if (options.crossOrigin === "throw") {
1837
+ throw new Error(
1838
+ "The object's @id (" + obj.id.href + ") has a different origin " +
1839
+ "than the document URL (" + baseUrl.href + "); refusing to return " +
1840
+ "the object. If you want to bypass this check and are aware of" +
1841
+ 'the security implications, set the crossOrigin option to "trust".'
1842
+ );
1843
+ }
1844
+ getLogger(["fedify", "vocab"]).warn(
1845
+ "The object's @id ({objectId}) has a different origin than the document " +
1846
+ "URL ({documentUrl}); refusing to return the object. If you want to " +
1847
+ "bypass this check and are aware of the security implications, " +
1848
+ 'set the crossOrigin option to "trust".',
1849
+ { ...fetchResult, objectId: obj.id.href },
1850
+ );
1851
+ span.end();
1852
+ return null;
1853
+ }
1854
+ span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
1855
+ span.setAttribute(
1856
+ "activitypub.object.type",
1857
+ // @ts-ignore: obj.constructor always has a typeId.
1858
+ obj.constructor.typeId.href
1601
1859
  );
1860
+ span.end();
1861
+ return obj;
1602
1862
  }
1603
- getLogger(["fedify", "vocab"]).warn(
1604
- "The object's @id ({objectId}) has a different origin than the document " +
1605
- "URL ({documentUrl}); refusing to return the object. If you want to " +
1606
- "bypass this check and are aware of the security implications, " +
1607
- 'set the crossOrigin option to "trust".',
1608
- { ...fetchResult, objectId: obj.id.href },
1609
- );
1610
- return null;
1863
+ } catch (e) {
1864
+ if (options.suppressError) {
1865
+ getLogger(["fedify", "vocab"]).warn(
1866
+ "Failed to parse {url}: {error}",
1867
+ { error: e, url: lookupUrl }
1868
+ );
1869
+ span.end();
1870
+ return null;
1871
+ }
1872
+ span.setStatus({
1873
+ code: SpanStatusCode.ERROR,
1874
+ message: String(e),
1875
+ });
1876
+ span.end();
1877
+ throw e;
1611
1878
  }
1612
- span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
1613
- span.setAttribute(
1614
- "activitypub.object.type",
1615
- // @ts-ignore: obj.constructor always has a typeId.
1616
- obj.constructor.typeId.href
1617
- );
1618
- return obj;
1619
- } catch (e) {
1620
- if (options.suppressError) {
1621
- getLogger(["fedify", "vocab"]).error(
1622
- "Failed to parse {url}: {error}",
1623
- { error: e, url: lookupUrl }
1624
- );
1625
- return null;
1879
+ // The document stands for a portable object, so it is verified as
1880
+ // one instead of being trusted because of where it came from:
1881
+ if (claim === null) {
1882
+ span.end();
1883
+ return rejectMalformedCompatibleReference(documentUrl, options);
1626
1884
  }
1627
- span.setStatus({
1628
- code: SpanStatusCode.ERROR,
1629
- message: String(e),
1885
+ return await dereferencePortable(claim.id, {
1886
+ inferredGateways: claim.inferredGateways,
1887
+ response: fetchResult,
1888
+ contextLoader: snapshot?.loader,
1630
1889
  });
1631
- throw e;
1632
1890
  } finally {
1633
- span.end();
1891
+ scopedContext.release();
1892
+ snapshot?.release();
1634
1893
  }
1635
1894
  });
1636
1895
  }
@@ -1641,6 +1900,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1641
1900
  documentLoader?: DocumentLoader,
1642
1901
  contextLoader?: DocumentLoader,
1643
1902
  tracerProvider?: TracerProvider,
1903
+ verifyPortableObject?: PortableObjectVerifier,
1644
1904
  baseUrl?: URL
1645
1905
  }
1646
1906
  ): Promise<${getTypeNames(property.range, types)}> {
@@ -1651,6 +1911,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1651
1911
  const tracerProvider = options.tracerProvider ??
1652
1912
  this._tracerProvider ?? trace.getTracerProvider();
1653
1913
  const baseUrl = options.baseUrl;
1914
+ const verifyPortableObject = options.verifyPortableObject;
1654
1915
  `;
1655
1916
  if (property.preprocessors != null && property.preprocessors.length > 0) {
1656
1917
  yield `
@@ -1670,6 +1931,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1670
1931
  documentLoader,
1671
1932
  contextLoader,
1672
1933
  tracerProvider,
1934
+ verifyPortableObject,
1673
1935
  baseUrl,
1674
1936
  });
1675
1937
  if (_result instanceof Error) throw _result;
@@ -1688,7 +1950,13 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1688
1950
  try {
1689
1951
  return await ${types[range].name}.fromJsonLd(
1690
1952
  jsonLd,
1691
- { documentLoader, contextLoader, tracerProvider, baseUrl },
1953
+ {
1954
+ documentLoader,
1955
+ contextLoader,
1956
+ tracerProvider,
1957
+ verifyPortableObject,
1958
+ baseUrl,
1959
+ },
1692
1960
  );
1693
1961
  } catch (e) {
1694
1962
  if (!(e instanceof TypeError)) throw e;
@@ -1730,6 +1998,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1730
1998
  suppressError?: boolean,
1731
1999
  tracerProvider?: TracerProvider,
1732
2000
  crossOrigin?: "ignore" | "throw" | "trust";
2001
+ gateways?: readonly (string | URL)[];
2002
+ verifyPortableObject?: PortableObjectVerifier;
2003
+ /**
2004
+ * Whether objects that this call newly fetches use the
2005
+ * \`verifyPortableObject\` option of this call as their default
2006
+ * verifier. Defaults to \`true\`. If \`false\`, the option
2007
+ * applies to this call only, and fetched objects use this object's
2008
+ * own default verifier, if any, as their default instead. Pass
2009
+ * \`false\` for a temporary verifier, e.g., one that accepts
2010
+ * everything. Objects that were already cached in this object or
2011
+ * embedded in it keep their defaults either way, and this option
2012
+ * itself is not passed on.
2013
+ * @since 2.4.0
2014
+ */
2015
+ inheritPortableObjectVerifier?: boolean;
1733
2016
  } = {}
1734
2017
  ): Promise<${getTypeNames(property.range, types)} | null> {
1735
2018
  if (this._warning != null) {
@@ -1739,25 +2022,46 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1739
2022
  );
1740
2023
  }
1741
2024
  if (this.${await getFieldName(property.uri)}.length < 1) return null;
2025
+ if (
2026
+ options.verifyPortableObject == null &&
2027
+ this._verifyPortableObject != null
2028
+ ) {
2029
+ options = {
2030
+ ...options,
2031
+ verifyPortableObject: this._verifyPortableObject,
2032
+ };
2033
+ }
1742
2034
  let v = this.${await getFieldName(property.uri)}[0];
1743
2035
  if (!(v instanceof URL) &&
1744
- v.id != null &&
1745
- !isTrustedIriOrigin(options, v.id, this.id) &&
1746
2036
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1747
- v = v.id;
2037
+ if (v.id == null) {
2038
+ if (isUnsecuredPortableObject(this)) {
2039
+ warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
2040
+ return null;
2041
+ }
2042
+ } else if (
2043
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
2044
+ isUnsecuredPortableObject(this) ||
2045
+ mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
2046
+ ) {
2047
+ v = v.id;
2048
+ }
1748
2049
  }
1749
2050
  if (v instanceof URL) {
1750
2051
  const fetched =
1751
2052
  await this.#fetch${(0, es_toolkit.pascalCase)(property.singularName)}(v, options);
1752
2053
  if (fetched == null) return null;
2054
+ // Not cached, so that it is verified when the portable object
2055
+ // policy applies later:
2056
+ if (isUnverifiedPortableClaim(fetched)) return fetched;
1753
2057
  this.${await getFieldName(property.uri)}[0] = fetched;
1754
2058
  this.${await getFieldName(property.uri, "#_trust")}.add(0);
1755
2059
  this._cachedJsonLd = undefined;
2060
+ recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
1756
2061
  return fetched;
1757
2062
  }
1758
2063
  `;
1759
- if (property.compactName != null) {
1760
- yield `
2064
+ if (property.compactName != null) yield `
1761
2065
  if (
1762
2066
  this._cachedJsonLd != null &&
1763
2067
  typeof this._cachedJsonLd === "object" &&
@@ -1768,26 +2072,36 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1768
2072
  ${JSON.stringify(property.compactName)}];
1769
2073
  const doc = Array.isArray(prop) ? prop[0] : prop;
1770
2074
  if (doc != null && typeof doc === "object" && "@context" in doc) {
1771
- `;
1772
- yield `
1773
- v = await this.#${property.singularName}_fromJsonLd(doc, {
1774
- ...options,
1775
- baseUrl: ${cachedPropertyBaseUrl},
1776
- });
1777
- `;
1778
- yield `
2075
+ try {
2076
+ v = await this.#${property.singularName}_fromJsonLd(doc, {
2077
+ ...options,
2078
+ // Keep the parent default for embedded objects.
2079
+ verifyPortableObject: this._verifyPortableObject,
2080
+ baseUrl: ${cachedPropertyBaseUrl},
2081
+ });
2082
+ } catch (error) {
2083
+ if (!options.suppressError) throw error;
2084
+ getLogger(["fedify", "vocab"]).debug(
2085
+ "Failed to parse embedded value of {property}: {error}",
2086
+ { property: ${JSON.stringify(property.uri)}, error },
2087
+ );
2088
+ return null;
2089
+ }
1779
2090
  }
1780
2091
  }
1781
2092
  `;
1782
- }
1783
2093
  yield `
1784
2094
  if (v?.id != null &&
1785
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
2095
+ ((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
2096
+ isUnsecuredPortableObject(this) ||
2097
+ mustDereferencePortableObject(
2098
+ this, ${trustOwner}, v.id, options,
2099
+ )) &&
1786
2100
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1787
2101
  if (options.crossOrigin === "throw") {
1788
2102
  throw new Error(
1789
2103
  "The property object's @id (" + v.id.href + ") has a different " +
1790
- "origin than the property owner's @id (" + this.id.href + "); " +
2104
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
1791
2105
  "refusing to return the object. If you want to bypass this " +
1792
2106
  "check and are aware of the security implications, set the " +
1793
2107
  'crossOrigin option to "trust".'
@@ -1799,10 +2113,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1799
2113
  "return the object. If you want to bypass this check and are " +
1800
2114
  "aware of the security implications, set the crossOrigin option " +
1801
2115
  'to "trust".',
1802
- { objectId: v.id.href, parentObjectId: this.id.href },
2116
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
1803
2117
  );
1804
2118
  return null;
1805
2119
  }
2120
+ recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
1806
2121
  return v;
1807
2122
  }
1808
2123
  `;
@@ -1835,6 +2150,21 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1835
2150
  suppressError?: boolean,
1836
2151
  tracerProvider?: TracerProvider,
1837
2152
  crossOrigin?: "ignore" | "throw" | "trust";
2153
+ gateways?: readonly (string | URL)[];
2154
+ verifyPortableObject?: PortableObjectVerifier;
2155
+ /**
2156
+ * Whether objects that this call newly fetches use the
2157
+ * \`verifyPortableObject\` option of this call as their default
2158
+ * verifier. Defaults to \`true\`. If \`false\`, the option
2159
+ * applies to this call only, and fetched objects use this object's
2160
+ * own default verifier, if any, as their default instead. Pass
2161
+ * \`false\` for a temporary verifier, e.g., one that accepts
2162
+ * everything. Objects that were already cached in this object or
2163
+ * embedded in it keep their defaults either way, and this option
2164
+ * itself is not passed on.
2165
+ * @since 2.4.0
2166
+ */
2167
+ inheritPortableObjectVerifier?: boolean;
1838
2168
  } = {}
1839
2169
  ): AsyncIterable<${getTypeNames(property.range, types)}> {
1840
2170
  if (this._warning != null) {
@@ -1843,28 +2173,52 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1843
2173
  this._warning.values
1844
2174
  );
1845
2175
  }
2176
+ if (
2177
+ options.verifyPortableObject == null &&
2178
+ this._verifyPortableObject != null
2179
+ ) {
2180
+ options = {
2181
+ ...options,
2182
+ verifyPortableObject: this._verifyPortableObject,
2183
+ };
2184
+ }
1846
2185
  const vs = this.${await getFieldName(property.uri)};
1847
2186
  for (let i = 0; i < vs.length; i++) {
1848
2187
  let v = vs[i];
1849
2188
  if (!(v instanceof URL) &&
1850
- v.id != null &&
1851
- !isTrustedIriOrigin(options, v.id, this.id) &&
1852
2189
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
1853
- v = v.id;
2190
+ if (v.id == null) {
2191
+ if (isUnsecuredPortableObject(this)) {
2192
+ warnUnverifiableEmbeddedObject(this, ${JSON.stringify(property.uri)});
2193
+ continue;
2194
+ }
2195
+ } else if (
2196
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
2197
+ isUnsecuredPortableObject(this) ||
2198
+ mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
2199
+ ) {
2200
+ v = v.id;
2201
+ }
1854
2202
  }
1855
2203
  if (v instanceof URL) {
1856
2204
  const fetched =
1857
2205
  await this.#fetch${(0, es_toolkit.pascalCase)(property.singularName)}(v, options);
1858
2206
  if (fetched == null) continue;
2207
+ // Not cached, so that it is verified when the portable object
2208
+ // policy applies later:
2209
+ if (isUnverifiedPortableClaim(fetched)) {
2210
+ yield fetched;
2211
+ continue;
2212
+ }
1859
2213
  vs[i] = fetched;
1860
2214
  this.${await getFieldName(property.uri, "#_trust")}.add(i);
1861
2215
  this._cachedJsonLd = undefined;
2216
+ recordPortableReferrer(this, fetched, ${JSON.stringify(property.uri)});
1862
2217
  yield fetched;
1863
2218
  continue;
1864
2219
  }
1865
2220
  `;
1866
- if (property.compactName != null) {
1867
- yield `
2221
+ if (property.compactName != null) yield `
1868
2222
  if (
1869
2223
  this._cachedJsonLd != null &&
1870
2224
  typeof this._cachedJsonLd === "object" &&
@@ -1875,26 +2229,36 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1875
2229
  ${JSON.stringify(property.compactName)}];
1876
2230
  const obj = Array.isArray(prop) ? prop[i] : prop;
1877
2231
  if (obj != null && typeof obj === "object" && "@context" in obj) {
1878
- `;
1879
- yield `
1880
- v = await this.#${property.singularName}_fromJsonLd(obj, {
1881
- ...options,
1882
- baseUrl: ${cachedPropertyBaseUrl},
1883
- });
1884
- `;
1885
- yield `
2232
+ try {
2233
+ v = await this.#${property.singularName}_fromJsonLd(obj, {
2234
+ ...options,
2235
+ // Keep the parent default for embedded objects.
2236
+ verifyPortableObject: this._verifyPortableObject,
2237
+ baseUrl: ${cachedPropertyBaseUrl},
2238
+ });
2239
+ } catch (error) {
2240
+ if (!options.suppressError) throw error;
2241
+ getLogger(["fedify", "vocab"]).debug(
2242
+ "Failed to parse embedded value of {property} at index {index}: {error}",
2243
+ { property: ${JSON.stringify(property.uri)}, index: i, error },
2244
+ );
2245
+ continue;
2246
+ }
1886
2247
  }
1887
2248
  }
1888
2249
  `;
1889
- }
1890
2250
  yield `
1891
2251
  if (v?.id != null &&
1892
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
2252
+ ((${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
2253
+ isUnsecuredPortableObject(this) ||
2254
+ mustDereferencePortableObject(
2255
+ this, ${trustOwner}, v.id, options,
2256
+ )) &&
1893
2257
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
1894
2258
  if (options.crossOrigin === "throw") {
1895
2259
  throw new Error(
1896
2260
  "The property object's @id (" + v.id.href + ") has a different " +
1897
- "origin than the property owner's @id (" + this.id.href + "); " +
2261
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} + "); " +
1898
2262
  "refusing to return the object. If you want to bypass this " +
1899
2263
  "check and are aware of the security implications, set the " +
1900
2264
  'crossOrigin option to "trust".'
@@ -1906,10 +2270,11 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1906
2270
  "return the object. If you want to bypass this check and are " +
1907
2271
  "aware of the security implications, set the crossOrigin " +
1908
2272
  'option to "trust".',
1909
- { objectId: v.id.href, parentObjectId: this.id.href },
2273
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "(this.id?.href ?? null)"} },
1910
2274
  );
1911
2275
  continue;
1912
2276
  }
2277
+ recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
1913
2278
  yield v;
1914
2279
  }
1915
2280
  }
@@ -1927,10 +2292,35 @@ const XSD_ANY_URI = "http://www.w3.org/2001/XMLSchema#anyURI";
1927
2292
  const FEDIFY_URL = "fedify:url";
1928
2293
  const INTERNAL_RUNTIME_IMPORTS = [
1929
2294
  "compactJsonLdCache",
2295
+ "createScopedContextLoader",
1930
2296
  "getJsonLdContext",
2297
+ "getPortableActorGateways",
1931
2298
  "isTrustedIriOrigin",
1932
2299
  "normalizeJsonLdIris"
1933
2300
  ].join(",\n ");
2301
+ const PORTABLE_DEREFERENCE_IMPORTS = [
2302
+ "copyPortableProvenance",
2303
+ "createSnapshotContextLoader",
2304
+ "dereferencePortableIri",
2305
+ "getPortableResponseClaim",
2306
+ "getReferrerGateways",
2307
+ "isPortableIri",
2308
+ "isPortableMode",
2309
+ "isUnsecuredPortableObject",
2310
+ "isUnverifiedPortableClaim",
2311
+ "markUnverifiedPortableClaim",
2312
+ "mustDereferencePortableObject",
2313
+ "parseCompatibleEf61Reference",
2314
+ "type PortableResponseClaim",
2315
+ "recordPortableReferrer",
2316
+ "rejectMalformedCompatibleReference",
2317
+ "warnUnverifiableEmbeddedObject"
2318
+ ].join(",\n ");
2319
+ const SIGNED_REPRESENTATION_IMPORTS = [
2320
+ "enterSignedValueScope",
2321
+ "resolveSignedValues",
2322
+ "retainedSignedValueRef"
2323
+ ].join(",\n ");
1934
2324
  const RUNTIME_IMPORTS = [
1935
2325
  "canParseDecimal",
1936
2326
  "decodeMultibase",
@@ -1944,10 +2334,13 @@ const RUNTIME_IMPORTS = [
1944
2334
  "importMultibaseKey",
1945
2335
  "importPem",
1946
2336
  "isDecimal",
2337
+ "isGatewayUrl",
1947
2338
  "LanguageString",
1948
2339
  "parseDecimal",
2340
+ "parseGatewayUrl",
1949
2341
  "parseIri",
1950
2342
  "parseJsonLdId",
2343
+ "type PortableObjectVerifier",
1951
2344
  "type RemoteDocument"
1952
2345
  ].join(",\n ");
1953
2346
  /**
@@ -1983,6 +2376,7 @@ async function* generateClass(typeUri, types, moduleVarNames) {
1983
2376
  readonly #documentLoader?: DocumentLoader;
1984
2377
  readonly #contextLoader?: DocumentLoader;
1985
2378
  readonly #tracerProvider?: TracerProvider;
2379
+ readonly #verifyPortableObject?: PortableObjectVerifier;
1986
2380
  readonly #warning?: {
1987
2381
  category: string[];
1988
2382
  message: string;
@@ -1990,6 +2384,7 @@ async function* generateClass(typeUri, types, moduleVarNames) {
1990
2384
  };
1991
2385
  #cachedJsonLd?: unknown;
1992
2386
  readonly #_baseUrl?: URL;
2387
+ #shouldCacheJsonLd = true;
1993
2388
  readonly id: URL | null;
1994
2389
 
1995
2390
  protected get _documentLoader(): DocumentLoader | undefined {
@@ -2004,6 +2399,10 @@ async function* generateClass(typeUri, types, moduleVarNames) {
2004
2399
  return this.#tracerProvider;
2005
2400
  }
2006
2401
 
2402
+ protected get _verifyPortableObject(): PortableObjectVerifier | undefined {
2403
+ return this.#verifyPortableObject;
2404
+ }
2405
+
2007
2406
  protected get _warning(): {
2008
2407
  category: string[];
2009
2408
  message: string;
@@ -2023,6 +2422,20 @@ async function* generateClass(typeUri, types, moduleVarNames) {
2023
2422
  protected get _baseUrl(): URL | undefined {
2024
2423
  return this.#_baseUrl;
2025
2424
  }
2425
+
2426
+ protected get _shouldCacheJsonLd(): boolean {
2427
+ return this.#shouldCacheJsonLd;
2428
+ }
2429
+
2430
+ protected set _shouldCacheJsonLd(value: boolean) {
2431
+ this.#shouldCacheJsonLd = value;
2432
+ }
2433
+
2434
+ protected static _shouldCacheDecodedJsonLd(value: unknown): boolean {
2435
+ if (value == null || typeof value !== "object") return true;
2436
+ if (!("_shouldCacheJsonLd" in value)) return true;
2437
+ return (value as { _shouldCacheJsonLd: boolean })._shouldCacheJsonLd;
2438
+ }
2026
2439
  `;
2027
2440
  yield `
2028
2441
  /**
@@ -2109,11 +2522,69 @@ async function* generateClasses(types) {
2109
2522
  from "@opentelemetry/api";\n`;
2110
2523
  yield `import {\n ${RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime";\n`;
2111
2524
  yield `import {\n ${INTERNAL_RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime/internal/jsonld-cache";\n`;
2525
+ yield `import {\n ${PORTABLE_DEREFERENCE_IMPORTS}\n} from "@fedify/vocab-runtime/internal/portable-dereference";\n`;
2526
+ yield `import {\n ${SIGNED_REPRESENTATION_IMPORTS}\n} from "@fedify/vocab-runtime/internal/signed-representation";\n`;
2112
2527
  yield `import {
2113
2528
  isTemporalDuration,
2114
2529
  isTemporalInstant,
2115
2530
  } from "@fedify/vocab-runtime/temporal";\n`;
2116
- const portableIriKeys = new Set(["@id", "id"]);
2531
+ yield `
2532
+ function canDecodeIri(iri: string): boolean {
2533
+ try { parseIri(iri); return true; } catch { return iri.startsWith("at://"); }
2534
+ }
2535
+
2536
+ function decodeIri(iri: string): URL {
2537
+ return !URL.canParse(iri) && iri.startsWith("at://")
2538
+ ? new URL("at://" + encodeURIComponent(iri.substring(5)))
2539
+ : parseIri(iri);
2540
+ }
2541
+
2542
+ function inspectIri(iri: URL): string {
2543
+ // Inspecting an object must not throw, even for a URL that cannot be
2544
+ // formatted as an IRI, e.g., a malformed portable ID:
2545
+ try { return formatIri(iri); } catch { return iri.href; }
2546
+ }
2547
+ `;
2548
+ yield `
2549
+ function isValidLanguageTag(language: string): boolean {
2550
+ try {
2551
+ new Intl.Locale(language);
2552
+ return true;
2553
+ } catch (error) {
2554
+ if (error instanceof RangeError) return false;
2555
+ throw error;
2556
+ }
2557
+ }
2558
+ `;
2559
+ const extraContexts = Object.fromEntries(Object.values(types).flatMap((type) => type.properties.flatMap((property) => property.extraContext == null ? [] : [[property.uri, property.extraContext]])));
2560
+ if (Object.keys(extraContexts).length > 0) yield `
2561
+ const extraPropertyContexts: Readonly<Record<string, string>> =
2562
+ ${JSON.stringify(extraContexts)};
2563
+
2564
+ function getExtraContexts(document: unknown): string[] {
2565
+ const contexts = new Set<string>();
2566
+ const pending: unknown[] = [document];
2567
+ while (pending.length > 0) {
2568
+ const value = pending.pop();
2569
+ if (value == null || typeof value !== "object") continue;
2570
+ if (Array.isArray(value)) {
2571
+ for (const item of value) pending.push(item);
2572
+ continue;
2573
+ }
2574
+ if ("@value" in value) continue;
2575
+ for (const [key, child] of globalThis.Object.entries(value)) {
2576
+ if (key === "@context") continue;
2577
+ if (globalThis.Object.hasOwn(extraPropertyContexts, key)) {
2578
+ contexts.add(extraPropertyContexts[key]);
2579
+ }
2580
+ pending.push(child);
2581
+ }
2582
+ }
2583
+ return [...contexts];
2584
+ }
2585
+ `;
2586
+ yield "\n\n";
2587
+ const portableIriKeys = /* @__PURE__ */ new Set(["@id", "id"]);
2117
2588
  for (const type of Object.values(types)) for (const property of type.properties) addPortableIriKeys(portableIriKeys, property, types);
2118
2589
  yield "const PORTABLE_IRI_PATTERN = /^ap(?:\\+ef61)?:\\/\\//i;\n";
2119
2590
  yield `const PORTABLE_IRI_KEYS: ReadonlySet<string> = new Set(${JSON.stringify([...portableIriKeys].sort())});\n\n`;