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

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/README.md CHANGED
@@ -46,3 +46,21 @@ mise run test:update_snapshots
46
46
 
47
47
  Review and commit every changed snapshot file. Updating only one runtime
48
48
  leaves the other test suites with stale expectations.
49
+
50
+
51
+ Conditional contexts and metadata trust
52
+ ---------------------------------------
53
+
54
+ A property schema can set `extraContext` to a context URL. The generated
55
+ serializer adds it to its default context when the property's expanded IRI
56
+ appears after compaction, including inside nested objects. A caller-provided
57
+ context takes precedence. When populated, such a property uses the JSON-LD
58
+ processor so context container rules, including `@set`, are respected.
59
+
60
+ A type schema can set `trustEmbeddedObjects: false` when its identifier is
61
+ metadata rather than a resource that establishes an origin. Its own
62
+ entity-valued accessors then fetch embedded objects with identifiers instead
63
+ of trusting a matching origin. Locally constructed values, previously fetched
64
+ values, and the explicit `crossOrigin: "trust"` option retain their usual
65
+ behavior. This does not verify id-less embedded objects or establish publishing
66
+ authority.
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-tools",
3
- "version": "2.4.0-dev.1962+8aaa5348",
3
+ "version": "2.4.0-dev.1964+1d28dc03",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts"
package/dist/mod.cjs CHANGED
@@ -8,7 +8,7 @@ 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-dev.1962+8aaa5348";
11
+ var version = "2.4.0-dev.1964+1d28dc03";
12
12
  //#endregion
13
13
  //#region src/type.ts
14
14
  const HEURISTICS_CONTEXTS = [
@@ -916,13 +916,29 @@ async function* generateEncoder(typeUri, types) {
916
916
  },
917
917
  );
918
918
  }
919
- const docContext = options.context ??
919
+ let docContext: Parameters<typeof jsonld.compact>[1] = options.context ??
920
920
  ${JSON.stringify(type.defaultContext)};
921
- const compacted = await jsonld.compact(
921
+ let compacted = await jsonld.compact(
922
922
  values,
923
923
  docContext,
924
924
  { documentLoader: options.contextLoader },
925
925
  );
926
+ `;
927
+ if (Object.values(types).some((t) => t.properties.some((p) => p.extraContext != null))) yield `
928
+ if (options.context == null) {
929
+ const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
930
+ const additionalContexts = getExtraContexts(compacted).filter(
931
+ context => !currentContexts.includes(context)
932
+ );
933
+ if (additionalContexts.length > 0) {
934
+ docContext = [...currentContexts, ...additionalContexts];
935
+ compacted = await jsonld.compact(values, docContext, {
936
+ documentLoader: options.contextLoader,
937
+ });
938
+ }
939
+ }
940
+ `;
941
+ yield `
926
942
  if (docContext != null) {
927
943
  // Embed context
928
944
  `;
@@ -951,7 +967,7 @@ async function* generateEncoder(typeUri, types) {
951
967
 
952
968
  protected ${emitOverride$1(typeUri, types)} isCompactable(): boolean {
953
969
  `;
954
- for (const property of type.properties) if (!property.range.every((r) => isCompactableType(r, types))) yield `
970
+ for (const property of type.properties) if (property.extraContext != null || !property.range.every((r) => isCompactableType(r, types))) yield `
955
971
  if (
956
972
  this.${await getFieldName(property.uri)} != null &&
957
973
  this.${await getFieldName(property.uri)}.length > 0
@@ -1577,6 +1593,7 @@ function emitOverride(typeUri, types, property) {
1577
1593
  }
1578
1594
  async function* generateProperty(type, property, types, moduleVarNames) {
1579
1595
  const override = emitOverride(type.uri, types, property);
1596
+ const trustOwner = type.trustEmbeddedObjects === false ? "null" : "this.id";
1580
1597
  const doc = `\n/** ${property.description.replaceAll("\n", "\n * ")}\n */\n`;
1581
1598
  const cachedPropertyBaseUrl = `(options as { baseUrl?: URL }).baseUrl ??
1582
1599
  this._baseUrl ??
@@ -1807,7 +1824,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1807
1824
  let v = this.${await getFieldName(property.uri)}[0];
1808
1825
  if (!(v instanceof URL) &&
1809
1826
  v.id != null &&
1810
- !isTrustedIriOrigin(options, v.id, this.id) &&
1827
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
1811
1828
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1812
1829
  v = v.id;
1813
1830
  }
@@ -1847,12 +1864,12 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1847
1864
  }
1848
1865
  yield `
1849
1866
  if (v?.id != null &&
1850
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
1867
+ ${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
1851
1868
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1852
1869
  if (options.crossOrigin === "throw") {
1853
1870
  throw new Error(
1854
1871
  "The property object's @id (" + v.id.href + ") has a different " +
1855
- "origin than the property owner's @id (" + this.id.href + "); " +
1872
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} + "); " +
1856
1873
  "refusing to return the object. If you want to bypass this " +
1857
1874
  "check and are aware of the security implications, set the " +
1858
1875
  'crossOrigin option to "trust".'
@@ -1864,7 +1881,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1864
1881
  "return the object. If you want to bypass this check and are " +
1865
1882
  "aware of the security implications, set the crossOrigin option " +
1866
1883
  'to "trust".',
1867
- { objectId: v.id.href, parentObjectId: this.id.href },
1884
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} },
1868
1885
  );
1869
1886
  return null;
1870
1887
  }
@@ -1913,7 +1930,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1913
1930
  let v = vs[i];
1914
1931
  if (!(v instanceof URL) &&
1915
1932
  v.id != null &&
1916
- !isTrustedIriOrigin(options, v.id, this.id) &&
1933
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
1917
1934
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
1918
1935
  v = v.id;
1919
1936
  }
@@ -1954,12 +1971,12 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1954
1971
  }
1955
1972
  yield `
1956
1973
  if (v?.id != null &&
1957
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
1974
+ ${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
1958
1975
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
1959
1976
  if (options.crossOrigin === "throw") {
1960
1977
  throw new Error(
1961
1978
  "The property object's @id (" + v.id.href + ") has a different " +
1962
- "origin than the property owner's @id (" + this.id.href + "); " +
1979
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} + "); " +
1963
1980
  "refusing to return the object. If you want to bypass this " +
1964
1981
  "check and are aware of the security implications, set the " +
1965
1982
  'crossOrigin option to "trust".'
@@ -1971,7 +1988,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1971
1988
  "return the object. If you want to bypass this check and are " +
1972
1989
  "aware of the security implications, set the crossOrigin " +
1973
1990
  'option to "trust".',
1974
- { objectId: v.id.href, parentObjectId: this.id.href },
1991
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} },
1975
1992
  );
1976
1993
  continue;
1977
1994
  }
@@ -2205,6 +2222,33 @@ function isValidLanguageTag(language: string): boolean {
2205
2222
  throw error;
2206
2223
  }
2207
2224
  }
2225
+ `;
2226
+ const extraContexts = Object.fromEntries(Object.values(types).flatMap((type) => type.properties.flatMap((property) => property.extraContext == null ? [] : [[property.uri, property.extraContext]])));
2227
+ if (Object.keys(extraContexts).length > 0) yield `
2228
+ const extraPropertyContexts: Readonly<Record<string, string>> =
2229
+ ${JSON.stringify(extraContexts)};
2230
+
2231
+ function getExtraContexts(document: unknown): string[] {
2232
+ const contexts = new Set<string>();
2233
+ const pending: unknown[] = [document];
2234
+ while (pending.length > 0) {
2235
+ const value = pending.pop();
2236
+ if (value == null || typeof value !== "object") continue;
2237
+ if (Array.isArray(value)) {
2238
+ for (const item of value) pending.push(item);
2239
+ continue;
2240
+ }
2241
+ if ("@value" in value) continue;
2242
+ for (const [key, child] of globalThis.Object.entries(value)) {
2243
+ if (key === "@context") continue;
2244
+ if (globalThis.Object.hasOwn(extraPropertyContexts, key)) {
2245
+ contexts.add(extraPropertyContexts[key]);
2246
+ }
2247
+ pending.push(child);
2248
+ }
2249
+ }
2250
+ return [...contexts];
2251
+ }
2208
2252
  `;
2209
2253
  yield "\n\n";
2210
2254
  const portableIriKeys = /* @__PURE__ */ new Set(["@id", "id"]);
package/dist/mod.d.cts CHANGED
@@ -36,6 +36,13 @@ interface TypeSchema {
36
36
  */
37
37
  entity: boolean;
38
38
  /**
39
+ * Whether this type's id can establish same-origin trust for its own
40
+ * embedded entity-valued properties. Defaults to true. Set to false for
41
+ * metadata whose id does not identify an independently fetched resource.
42
+ * Locally constructed and explicitly trusted values remain trusted.
43
+ */
44
+ trustEmbeddedObjects?: boolean;
45
+ /**
39
46
  * Whether the type omits `@type` in JSON-LD serialization. When `true`,
40
47
  * the generated `toJsonLd()` method will not emit `@type` (or `type` in
41
48
  * compact form) in the serialized JSON-LD. The generated `fromJsonLd()`
@@ -61,6 +68,13 @@ interface TypeSchema {
61
68
  defaultContext: Context;
62
69
  }
63
70
  interface PropertySchemaBase {
71
+ /**
72
+ * A context URL added to the default serialization context when this
73
+ * property's expanded IRI appears in the compacted document. Explicit
74
+ * caller contexts are unchanged. Populated properties with this option
75
+ * use the JSON-LD processor instead of compact serialization heuristics.
76
+ */
77
+ extraContext?: string;
64
78
  /**
65
79
  * The singular form of the property name. It is used as the name of the
66
80
  * generated property accessors.
package/dist/mod.d.ts CHANGED
@@ -36,6 +36,13 @@ interface TypeSchema {
36
36
  */
37
37
  entity: boolean;
38
38
  /**
39
+ * Whether this type's id can establish same-origin trust for its own
40
+ * embedded entity-valued properties. Defaults to true. Set to false for
41
+ * metadata whose id does not identify an independently fetched resource.
42
+ * Locally constructed and explicitly trusted values remain trusted.
43
+ */
44
+ trustEmbeddedObjects?: boolean;
45
+ /**
39
46
  * Whether the type omits `@type` in JSON-LD serialization. When `true`,
40
47
  * the generated `toJsonLd()` method will not emit `@type` (or `type` in
41
48
  * compact form) in the serialized JSON-LD. The generated `fromJsonLd()`
@@ -61,6 +68,13 @@ interface TypeSchema {
61
68
  defaultContext: Context;
62
69
  }
63
70
  interface PropertySchemaBase {
71
+ /**
72
+ * A context URL added to the default serialization context when this
73
+ * property's expanded IRI appears in the compacted document. Explicit
74
+ * caller contexts are unchanged. Populated properties with this option
75
+ * use the JSON-LD processor instead of compact serialization heuristics.
76
+ */
77
+ extraContext?: string;
64
78
  /**
65
79
  * The singular form of the property name. It is used as the name of the
66
80
  * generated property accessors.
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.1962+8aaa5348";
10
+ var version = "2.4.0-dev.1964+1d28dc03";
11
11
  //#endregion
12
12
  //#region src/type.ts
13
13
  const HEURISTICS_CONTEXTS = [
@@ -915,13 +915,29 @@ async function* generateEncoder(typeUri, types) {
915
915
  },
916
916
  );
917
917
  }
918
- const docContext = options.context ??
918
+ let docContext: Parameters<typeof jsonld.compact>[1] = options.context ??
919
919
  ${JSON.stringify(type.defaultContext)};
920
- const compacted = await jsonld.compact(
920
+ let compacted = await jsonld.compact(
921
921
  values,
922
922
  docContext,
923
923
  { documentLoader: options.contextLoader },
924
924
  );
925
+ `;
926
+ if (Object.values(types).some((t) => t.properties.some((p) => p.extraContext != null))) yield `
927
+ if (options.context == null) {
928
+ const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
929
+ const additionalContexts = getExtraContexts(compacted).filter(
930
+ context => !currentContexts.includes(context)
931
+ );
932
+ if (additionalContexts.length > 0) {
933
+ docContext = [...currentContexts, ...additionalContexts];
934
+ compacted = await jsonld.compact(values, docContext, {
935
+ documentLoader: options.contextLoader,
936
+ });
937
+ }
938
+ }
939
+ `;
940
+ yield `
925
941
  if (docContext != null) {
926
942
  // Embed context
927
943
  `;
@@ -950,7 +966,7 @@ async function* generateEncoder(typeUri, types) {
950
966
 
951
967
  protected ${emitOverride$1(typeUri, types)} isCompactable(): boolean {
952
968
  `;
953
- for (const property of type.properties) if (!property.range.every((r) => isCompactableType(r, types))) yield `
969
+ for (const property of type.properties) if (property.extraContext != null || !property.range.every((r) => isCompactableType(r, types))) yield `
954
970
  if (
955
971
  this.${await getFieldName(property.uri)} != null &&
956
972
  this.${await getFieldName(property.uri)}.length > 0
@@ -1576,6 +1592,7 @@ function emitOverride(typeUri, types, property) {
1576
1592
  }
1577
1593
  async function* generateProperty(type, property, types, moduleVarNames) {
1578
1594
  const override = emitOverride(type.uri, types, property);
1595
+ const trustOwner = type.trustEmbeddedObjects === false ? "null" : "this.id";
1579
1596
  const doc = `\n/** ${property.description.replaceAll("\n", "\n * ")}\n */\n`;
1580
1597
  const cachedPropertyBaseUrl = `(options as { baseUrl?: URL }).baseUrl ??
1581
1598
  this._baseUrl ??
@@ -1806,7 +1823,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1806
1823
  let v = this.${await getFieldName(property.uri)}[0];
1807
1824
  if (!(v instanceof URL) &&
1808
1825
  v.id != null &&
1809
- !isTrustedIriOrigin(options, v.id, this.id) &&
1826
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
1810
1827
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1811
1828
  v = v.id;
1812
1829
  }
@@ -1846,12 +1863,12 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1846
1863
  }
1847
1864
  yield `
1848
1865
  if (v?.id != null &&
1849
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
1866
+ ${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
1850
1867
  !this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
1851
1868
  if (options.crossOrigin === "throw") {
1852
1869
  throw new Error(
1853
1870
  "The property object's @id (" + v.id.href + ") has a different " +
1854
- "origin than the property owner's @id (" + this.id.href + "); " +
1871
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} + "); " +
1855
1872
  "refusing to return the object. If you want to bypass this " +
1856
1873
  "check and are aware of the security implications, set the " +
1857
1874
  'crossOrigin option to "trust".'
@@ -1863,7 +1880,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1863
1880
  "return the object. If you want to bypass this check and are " +
1864
1881
  "aware of the security implications, set the crossOrigin option " +
1865
1882
  'to "trust".',
1866
- { objectId: v.id.href, parentObjectId: this.id.href },
1883
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} },
1867
1884
  );
1868
1885
  return null;
1869
1886
  }
@@ -1912,7 +1929,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1912
1929
  let v = vs[i];
1913
1930
  if (!(v instanceof URL) &&
1914
1931
  v.id != null &&
1915
- !isTrustedIriOrigin(options, v.id, this.id) &&
1932
+ !isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
1916
1933
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
1917
1934
  v = v.id;
1918
1935
  }
@@ -1953,12 +1970,12 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1953
1970
  }
1954
1971
  yield `
1955
1972
  if (v?.id != null &&
1956
- this.id != null && !isTrustedIriOrigin(options, v.id, this.id) &&
1973
+ ${type.trustEmbeddedObjects === false ? "" : "this.id != null && "}!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
1957
1974
  !this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
1958
1975
  if (options.crossOrigin === "throw") {
1959
1976
  throw new Error(
1960
1977
  "The property object's @id (" + v.id.href + ") has a different " +
1961
- "origin than the property owner's @id (" + this.id.href + "); " +
1978
+ "origin than the property owner's @id (" + ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} + "); " +
1962
1979
  "refusing to return the object. If you want to bypass this " +
1963
1980
  "check and are aware of the security implications, set the " +
1964
1981
  'crossOrigin option to "trust".'
@@ -1970,7 +1987,7 @@ async function* generateProperty(type, property, types, moduleVarNames) {
1970
1987
  "return the object. If you want to bypass this check and are " +
1971
1988
  "aware of the security implications, set the crossOrigin " +
1972
1989
  'option to "trust".',
1973
- { objectId: v.id.href, parentObjectId: this.id.href },
1990
+ { objectId: v.id.href, parentObjectId: ${type.trustEmbeddedObjects === false ? "\"untrusted metadata\"" : "this.id.href"} },
1974
1991
  );
1975
1992
  continue;
1976
1993
  }
@@ -2204,6 +2221,33 @@ function isValidLanguageTag(language: string): boolean {
2204
2221
  throw error;
2205
2222
  }
2206
2223
  }
2224
+ `;
2225
+ const extraContexts = Object.fromEntries(Object.values(types).flatMap((type) => type.properties.flatMap((property) => property.extraContext == null ? [] : [[property.uri, property.extraContext]])));
2226
+ if (Object.keys(extraContexts).length > 0) yield `
2227
+ const extraPropertyContexts: Readonly<Record<string, string>> =
2228
+ ${JSON.stringify(extraContexts)};
2229
+
2230
+ function getExtraContexts(document: unknown): string[] {
2231
+ const contexts = new Set<string>();
2232
+ const pending: unknown[] = [document];
2233
+ while (pending.length > 0) {
2234
+ const value = pending.pop();
2235
+ if (value == null || typeof value !== "object") continue;
2236
+ if (Array.isArray(value)) {
2237
+ for (const item of value) pending.push(item);
2238
+ continue;
2239
+ }
2240
+ if ("@value" in value) continue;
2241
+ for (const [key, child] of globalThis.Object.entries(value)) {
2242
+ if (key === "@context") continue;
2243
+ if (globalThis.Object.hasOwn(extraPropertyContexts, key)) {
2244
+ contexts.add(extraPropertyContexts[key]);
2245
+ }
2246
+ pending.push(child);
2247
+ }
2248
+ }
2249
+ return [...contexts];
2250
+ }
2207
2251
  `;
2208
2252
  yield "\n\n";
2209
2253
  const portableIriKeys = /* @__PURE__ */ new Set(["@id", "id"]);
package/dist/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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-tools",
3
- "version": "2.4.0-dev.1962+8aaa5348",
3
+ "version": "2.4.0-dev.1964+1d28dc03",
4
4
  "description": "Code generator for Activity Vocabulary APIs",
5
5
  "homepage": "https://fedify.dev/",
6
6
  "repository": {