@fedify/vocab-tools 2.4.0-dev.1508 → 2.4.0-dev.1531

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.js CHANGED
@@ -7,7 +7,7 @@ import { parse } from "yaml";
7
7
  import { pascalCase } from "es-toolkit";
8
8
  //#region deno.json
9
9
  var name = "@fedify/vocab-tools";
10
- var version = "2.4.0-dev.1508+68519bf4";
10
+ var version = "2.4.0-dev.1531+0896bc51";
11
11
  //#endregion
12
12
  //#region src/type.ts
13
13
  const HEURISTICS_CONTEXTS = [
@@ -155,10 +155,10 @@ const scalarTypes = {
155
155
  return `${v} instanceof URL`;
156
156
  },
157
157
  encoder(v) {
158
- return `{ "@id": ${v}.href }`;
158
+ return `{ "@id": formatIri(${v}) }`;
159
159
  },
160
160
  compactEncoder(v) {
161
- return `${v}.href`;
161
+ return `formatIri(${v})`;
162
162
  },
163
163
  dataCheck(v) {
164
164
  return `${v} != null && typeof ${v} === "object" && "@id" in ${v}
@@ -166,22 +166,7 @@ const scalarTypes = {
166
166
  && ${v}["@id"] !== ""`;
167
167
  },
168
168
  decoder(v, baseUrlVar) {
169
- return `${v}["@id"].startsWith("at://")
170
- ? new URL("at://" +
171
- encodeURIComponent(
172
- ${v}["@id"].includes("/", 5)
173
- ? ${v}["@id"].slice(5, ${v}["@id"].indexOf("/", 5))
174
- : ${v}["@id"].slice(5)
175
- ) +
176
- (
177
- ${v}["@id"].includes("/", 5)
178
- ? ${v}["@id"].slice(${v}["@id"].indexOf("/", 5))
179
- : ""
180
- )
181
- )
182
- : URL.canParse(${v}["@id"]) && ${baseUrlVar}
183
- ? new URL(${v}["@id"])
184
- : new URL(${v}["@id"], ${baseUrlVar})`;
169
+ return `parseIri(${v}["@id"], ${baseUrlVar})`;
185
170
  }
186
171
  },
187
172
  "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString": {
@@ -323,10 +308,10 @@ const scalarTypes = {
323
308
  return `${v} instanceof URL`;
324
309
  },
325
310
  encoder(v) {
326
- return `{ "@value": ${v}.href }`;
311
+ return `{ "@value": formatIri(${v}) }`;
327
312
  },
328
313
  compactEncoder(v) {
329
- return `${v}.href`;
314
+ return `formatIri(${v})`;
330
315
  },
331
316
  dataCheck(v) {
332
317
  return `typeof ${v} === "object" && "@value" in ${v}
@@ -334,7 +319,7 @@ const scalarTypes = {
334
319
  && ${v}["@value"] !== "" && ${v}["@value"] !== "/"`;
335
320
  },
336
321
  decoder(v) {
337
- return `new URL(${v}["@value"])`;
322
+ return `parseIri(${v}["@value"])`;
338
323
  }
339
324
  },
340
325
  "fedify:publicKey": {
@@ -804,7 +789,7 @@ async function* generateEncoder(typeUri, types) {
804
789
  for (const v of this.${await getFieldName(property.uri)}) {
805
790
  const item = (
806
791
  `;
807
- if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? v.href : ";
792
+ if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? formatIri(v) : ";
808
793
  const encoders = getEncoders(property.range, types, "v", "options", true);
809
794
  for (const code of encoders) yield code;
810
795
  yield `
@@ -835,7 +820,7 @@ async function* generateEncoder(typeUri, types) {
835
820
  }
836
821
  yield `
837
822
  ${type.typeless ? "" : `result["type"] = ${JSON.stringify(type.compactName ?? type.uri)};`}
838
- if (this.id != null) result["id"] = this.id.href;
823
+ if (this.id != null) result["id"] = formatIri(this.id);
839
824
  result["@context"] = ${JSON.stringify(type.defaultContext)};
840
825
  return result;
841
826
  }
@@ -863,7 +848,7 @@ async function* generateEncoder(typeUri, types) {
863
848
  for (const v of this.${await getFieldName(property.uri)}) {
864
849
  const element = (
865
850
  `;
866
- if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? { \"@id\": v.href } : ";
851
+ if (!areAllScalarTypes(property.range, types)) yield "v instanceof URL ? { \"@id\": formatIri(v) } : ";
867
852
  for (const code of getEncoders(property.range, types, "v", "options")) yield code;
868
853
  yield `
869
854
  );
@@ -890,7 +875,7 @@ async function* generateEncoder(typeUri, types) {
890
875
  }
891
876
  yield `
892
877
  ${type.typeless ? "" : `values["@type"] = [${JSON.stringify(type.uri)}];`}
893
- if (this.id != null) values["@id"] = this.id.href;
878
+ if (this.id != null) values["@id"] = formatIri(this.id);
894
879
  if (options.format === "expand") {
895
880
  return await jsonld.expand(
896
881
  values,
@@ -1016,10 +1001,12 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1016
1001
  };
1017
1002
  // deno-lint-ignore no-explicit-any
1018
1003
  let values: Record<string, any[]> & { "@id"?: string };
1004
+ let expanded: unknown[];
1019
1005
  if (globalThis.Object.keys(json).length == 0) {
1020
1006
  values = {};
1007
+ expanded = [values];
1021
1008
  } else {
1022
- const expanded = await jsonld.expand(json, {
1009
+ expanded = await jsonld.expand(json, {
1023
1010
  documentLoader: options.contextLoader,
1024
1011
  keepFreeFloatingNodes: true,
1025
1012
  });
@@ -1027,11 +1014,9 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1027
1014
  // deno-lint-ignore no-explicit-any
1028
1015
  (expanded[0] ?? {}) as (Record<string, any[]> & { "@id"?: string });
1029
1016
  }
1030
- if (values["@id"] != null && !values["@id"].startsWith("_:") && !URL.canParse(values["@id"], options.baseUrl)) {
1031
- throw new TypeError("Invalid @id: " + values["@id"]);
1032
- }
1033
- if (options.baseUrl == null && values["@id"] != null && !values["@id"].startsWith("_:") && URL.canParse(values["@id"])) {
1034
- options = { ...options, baseUrl: new URL(values["@id"]) };
1017
+ const id = parseJsonLdId(values["@id"], options.baseUrl);
1018
+ if (id != null && options.baseUrl == null) {
1019
+ options = { ...options, baseUrl: id };
1035
1020
  }
1036
1021
  `;
1037
1022
  const subtypes = getSubtypes(typeUri, types, true);
@@ -1052,10 +1037,24 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1052
1037
  throw new TypeError("Invalid type: " + values["@type"]);
1053
1038
  }
1054
1039
  }
1040
+ `;
1041
+ yield `
1042
+ let cacheJsonLd = !("_fromSubclass" in options) || !options._fromSubclass
1043
+ ? normalizeJsonLdIris(
1044
+ expanded,
1045
+ PORTABLE_IRI_KEYS,
1046
+ PORTABLE_IRI_PATTERN,
1047
+ )
1048
+ : undefined;
1049
+ if (cacheJsonLd != null && cacheJsonLd !== expanded) {
1050
+ cacheJsonLd = structuredClone(cacheJsonLd);
1051
+ }
1055
1052
  `;
1056
1053
  if (type.extends == null) yield `
1057
1054
  const instance = new this(
1058
- { id: values["@id"] != null && !values["@id"].startsWith("_:") && URL.canParse(values["@id"], options.baseUrl) ? new URL(values["@id"], options.baseUrl) : undefined },
1055
+ {
1056
+ id
1057
+ },
1059
1058
  options,
1060
1059
  );
1061
1060
  `;
@@ -1098,11 +1097,7 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1098
1097
  if (typeof v === "object" && "@id" in v && !("@type" in v)
1099
1098
  && globalThis.Object.keys(v).length === 1) {
1100
1099
  if (v["@id"].startsWith("_:")) continue;
1101
- ${variable}.push(
1102
- !URL.canParse(v["@id"], ${propertyBaseUrl}) && v["@id"].startsWith("at://")
1103
- ? new URL("at://" + encodeURIComponent(v["@id"].substring(5)))
1104
- : new URL(v["@id"], ${propertyBaseUrl})
1105
- );
1100
+ ${variable}.push(parseIri(v["@id"], ${propertyBaseUrl}));
1106
1101
  continue;
1107
1102
  }
1108
1103
  `;
@@ -1130,7 +1125,19 @@ async function* generateDecoder(typeUri, types, moduleVarNames) {
1130
1125
  yield `
1131
1126
  if (!("_fromSubclass" in options) || !options._fromSubclass) {
1132
1127
  try {
1133
- instance._cachedJsonLd = structuredClone(json);
1128
+ if (cacheJsonLd != null && cacheJsonLd !== expanded) {
1129
+ const compactArray = Array.isArray(json) && json.length === 1;
1130
+ const jsonLd = compactArray ? json[0] : json;
1131
+ const normalized = cacheJsonLd;
1132
+ const cachedJsonLd = await compactJsonLdCache(
1133
+ normalized,
1134
+ jsonLd,
1135
+ options.contextLoader,
1136
+ );
1137
+ instance._cachedJsonLd = compactArray ? [cachedJsonLd] : cachedJsonLd;
1138
+ } else {
1139
+ instance._cachedJsonLd = structuredClone(json);
1140
+ }
1134
1141
  } catch {
1135
1142
  getLogger(["fedify", "vocab"]).warn(
1136
1143
  "Failed to cache JSON-LD: {json}",
@@ -1557,9 +1564,10 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1557
1564
  ${JSON.stringify(version)},
1558
1565
  );
1559
1566
  return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
1567
+ const lookupUrl = formatIri(url);
1560
1568
  let fetchResult: RemoteDocument;
1561
1569
  try {
1562
- fetchResult = await documentLoader(url.href);
1570
+ fetchResult = await documentLoader(lookupUrl);
1563
1571
  } catch (error) {
1564
1572
  span.setStatus({
1565
1573
  code: SpanStatusCode.ERROR,
@@ -1569,21 +1577,20 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1569
1577
  if (options.suppressError) {
1570
1578
  getLogger(["fedify", "vocab"]).error(
1571
1579
  "Failed to fetch {url}: {error}",
1572
- { error, url: url.href }
1580
+ { error, url: lookupUrl }
1573
1581
  );
1574
1582
  return null;
1575
1583
  }
1576
1584
  throw error;
1577
1585
  }
1578
1586
  const { document, documentUrl } = fetchResult;
1579
- const baseUrl = new URL(documentUrl);
1587
+ const baseUrl = parseIri(documentUrl);
1580
1588
  try {
1581
1589
  const obj = await this.#${property.singularName}_fromJsonLd(
1582
1590
  document,
1583
1591
  { documentLoader, contextLoader, tracerProvider, baseUrl }
1584
1592
  );
1585
- if (options.crossOrigin !== "trust" && obj?.id != null &&
1586
- obj.id.origin !== baseUrl.origin) {
1593
+ if (obj?.id != null && !isTrustedIriOrigin(options, obj.id, baseUrl)) {
1587
1594
  if (options.crossOrigin === "throw") {
1588
1595
  throw new Error(
1589
1596
  "The object's @id (" + obj.id.href + ") has a different origin " +
@@ -1612,7 +1619,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1612
1619
  if (options.suppressError) {
1613
1620
  getLogger(["fedify", "vocab"]).error(
1614
1621
  "Failed to parse {url}: {error}",
1615
- { error: e, url: url.href }
1622
+ { error: e, url: lookupUrl }
1616
1623
  );
1617
1624
  return null;
1618
1625
  }
@@ -1732,8 +1739,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1732
1739
  }
1733
1740
  if (this.${await getFieldName(property.uri)}.length < 1) return null;
1734
1741
  let v = this.${await getFieldName(property.uri)}[0];
1735
- if (options.crossOrigin !== "trust" && !(v instanceof URL) &&
1736
- v.id != null && v.id.origin !== this.id?.origin &&
1742
+ if (!(v instanceof URL) &&
1743
+ v.id != null &&
1744
+ !isTrustedIriOrigin(options, v.id, this.id) &&
1737
1745
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1738
1746
  v = v.id;
1739
1747
  }
@@ -1772,8 +1780,8 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1772
1780
  `;
1773
1781
  }
1774
1782
  yield `
1775
- if (options.crossOrigin !== "trust" && v?.id != null &&
1776
- this.id != null && v.id.origin !== this.id.origin &&
1783
+ if (v?.id != null &&
1784
+ this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
1777
1785
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1778
1786
  if (options.crossOrigin === "throw") {
1779
1787
  throw new Error(
@@ -1837,8 +1845,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1837
1845
  const vs = this.${await getFieldName(property.uri)};
1838
1846
  for (let i = 0; i < vs.length; i++) {
1839
1847
  let v = vs[i];
1840
- if (options.crossOrigin !== "trust" && !(v instanceof URL) &&
1841
- v.id != null && v.id.origin !== this.id?.origin &&
1848
+ if (!(v instanceof URL) &&
1849
+ v.id != null &&
1850
+ !isTrustedIriOrigin(options, v.id, this.id) &&
1842
1851
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
1843
1852
  v = v.id;
1844
1853
  }
@@ -1878,9 +1887,9 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1878
1887
  `;
1879
1888
  }
1880
1889
  yield `
1881
- if (options.crossOrigin !== "trust" && v?.id != null &&
1882
- this.id != null && v.id.origin !== this.id.origin &&
1883
- !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1890
+ if (v?.id != null &&
1891
+ this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
1892
+ !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
1884
1893
  if (options.crossOrigin === "throw") {
1885
1894
  throw new Error(
1886
1895
  "The property object's @id (" + v.id.href + ") has a different " +
@@ -1913,6 +1922,33 @@ async function* generateProperties(typeUri, types, moduleVarNames) {
1913
1922
  }
1914
1923
  //#endregion
1915
1924
  //#region src/class.ts
1925
+ const XSD_ANY_URI = "http://www.w3.org/2001/XMLSchema#anyURI";
1926
+ const FEDIFY_URL = "fedify:url";
1927
+ const INTERNAL_RUNTIME_IMPORTS = [
1928
+ "compactJsonLdCache",
1929
+ "getJsonLdContext",
1930
+ "isTrustedIriOrigin",
1931
+ "normalizeJsonLdIris"
1932
+ ].join(",\n ");
1933
+ const RUNTIME_IMPORTS = [
1934
+ "canParseDecimal",
1935
+ "decodeMultibase",
1936
+ "type Decimal",
1937
+ "type DocumentLoader",
1938
+ "encodeMultibase",
1939
+ "exportMultibaseKey",
1940
+ "exportSpki",
1941
+ "formatIri",
1942
+ "getDocumentLoader",
1943
+ "importMultibaseKey",
1944
+ "importPem",
1945
+ "isDecimal",
1946
+ "LanguageString",
1947
+ "parseDecimal",
1948
+ "parseIri",
1949
+ "parseJsonLdId",
1950
+ "type RemoteDocument"
1951
+ ].join(",\n ");
1916
1952
  /**
1917
1953
  * Sorts the given types topologically so that the base types come before the
1918
1954
  * extended types.
@@ -2046,6 +2082,18 @@ export function getEntityTypeById(id: string | URL): $EntityType | undefined {
2046
2082
 
2047
2083
  `;
2048
2084
  }
2085
+ function canContainIriValue(property, types) {
2086
+ return property.range.some((typeUri) => typeUri === XSD_ANY_URI || typeUri === FEDIFY_URL || types[typeUri]?.entity);
2087
+ }
2088
+ function addKeys(keys, property) {
2089
+ keys.add(property.uri);
2090
+ if (property.compactName != null) keys.add(property.compactName);
2091
+ }
2092
+ function addPortableIriKeys(keys, property, types) {
2093
+ if (!canContainIriValue(property, types)) return;
2094
+ addKeys(keys, property);
2095
+ if ("redundantProperties" in property && property.redundantProperties != null) for (const redundantProperty of property.redundantProperties) addKeys(keys, redundantProperty);
2096
+ }
2049
2097
  /**
2050
2098
  * Generates the TypeScript classes from the given types.
2051
2099
  * @param types The types to generate classes from.
@@ -2053,33 +2101,21 @@ export function getEntityTypeById(id: string | URL): $EntityType | undefined {
2053
2101
  */
2054
2102
  async function* generateClasses(types) {
2055
2103
  validateTypeSchemas(types);
2056
- const runtimeImports = [
2057
- "canParseDecimal",
2058
- "decodeMultibase",
2059
- "type Decimal",
2060
- "type DocumentLoader",
2061
- "encodeMultibase",
2062
- "exportMultibaseKey",
2063
- "exportSpki",
2064
- "getDocumentLoader",
2065
- "importMultibaseKey",
2066
- "importPem",
2067
- "isDecimal",
2068
- "LanguageString",
2069
- "parseDecimal",
2070
- "type RemoteDocument"
2071
- ];
2072
2104
  yield "// deno-lint-ignore-file ban-unused-ignore no-explicit-any no-unused-vars prefer-const verbatim-module-syntax\n";
2073
2105
  yield "import jsonld from \"@fedify/vocab-runtime/jsonld\";\n";
2074
2106
  yield "import { getLogger } from \"@logtape/logtape\";\n";
2075
2107
  yield `import { type Span, SpanStatusCode, type TracerProvider, trace }
2076
2108
  from "@opentelemetry/api";\n`;
2077
- yield `import {\n ${runtimeImports.join(",\n ")}\n} from "@fedify/vocab-runtime";\n`;
2109
+ yield `import {\n ${RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime";\n`;
2110
+ yield `import {\n ${INTERNAL_RUNTIME_IMPORTS}\n} from "@fedify/vocab-runtime/internal/jsonld-cache";\n`;
2078
2111
  yield `import {
2079
2112
  isTemporalDuration,
2080
2113
  isTemporalInstant,
2081
2114
  } from "@fedify/vocab-runtime/temporal";\n`;
2082
- yield "\n\n";
2115
+ const portableIriKeys = new Set(["@id", "id"]);
2116
+ for (const type of Object.values(types)) for (const property of type.properties) addPortableIriKeys(portableIriKeys, property, types);
2117
+ yield "const PORTABLE_IRI_PATTERN = /^ap(?:\\+ef61)?:\\/\\//i;\n";
2118
+ yield `const PORTABLE_IRI_KEYS: ReadonlySet<string> = new Set(${JSON.stringify([...portableIriKeys].sort())});\n\n`;
2083
2119
  const moduleVarNames = /* @__PURE__ */ new Map();
2084
2120
  const sorted = sortTopologically(types);
2085
2121
  for (const typeUri of sorted) for (const property of types[typeUri].properties) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-tools",
3
- "version": "2.4.0-dev.1508+68519bf4",
3
+ "version": "2.4.0-dev.1531+0896bc51",
4
4
  "description": "Code generator for Activity Vocabulary APIs",
5
5
  "homepage": "https://fedify.dev/",
6
6
  "repository": {