@fedify/vocab-tools 2.5.0-dev.2332 → 2.5.0-dev.2355

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
@@ -52,10 +52,20 @@ Conditional contexts and metadata trust
52
52
  ---------------------------------------
53
53
 
54
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.
55
+ serializer adds it to its default context when the property is populated,
56
+ including inside nested objects, even if the default context already defines a
57
+ prefix that could abbreviate the property's IRI. A nested object kept in its
58
+ received form is recognized by the context URL or the full property IRI it
59
+ uses. A caller-provided context takes precedence. When populated, such a
60
+ property uses the JSON-LD processor so context container rules, including
61
+ `@set`, are respected.
62
+
63
+ The `fedify:absoluteIri` range is a `URL` written as an IRI reference
64
+ (`@id`). When reading, it also accepts plain string literals and literals
65
+ typed as `xsd:string` or `xsd:anyURI`, because publishers do not always coerce
66
+ the property's term to an IRI. The decoder never resolves a value against the
67
+ object's ID or a base URL, so a value must already be absolute after JSON-LD
68
+ expansion; values that are not are skipped.
59
69
 
60
70
  A type schema can set `trustEmbeddedObjects: false` when its identifier is
61
71
  metadata rather than a resource that establishes an origin. Its own
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-tools",
3
- "version": "2.5.0-dev.2332+d4e3f91f",
3
+ "version": "2.5.0-dev.2355+c92a1cb1",
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.5.0-dev.2332+d4e3f91f";
11
+ var version = "2.5.0-dev.2355+c92a1cb1";
12
12
  //#endregion
13
13
  //#region src/type.ts
14
14
  const HEURISTICS_CONTEXTS = [
@@ -327,6 +327,25 @@ const scalarTypes = {
327
327
  },
328
328
  skipUnparsable: true
329
329
  },
330
+ "fedify:absoluteIri": {
331
+ name: "URL",
332
+ typeGuard(v) {
333
+ return `${v} instanceof URL`;
334
+ },
335
+ encoder(v) {
336
+ return `{ "@id": formatIri(${v}) }`;
337
+ },
338
+ compactEncoder(v) {
339
+ return `formatIri(${v})`;
340
+ },
341
+ dataCheck(v) {
342
+ return `isAbsoluteIriValue(${v})`;
343
+ },
344
+ decoder(v) {
345
+ return `decodeIri(getAbsoluteIriValue(${v})!)`;
346
+ },
347
+ skipUnparsable: true
348
+ },
330
349
  "fedify:gatewayUrl": {
331
350
  name: "URL",
332
351
  typeGuard(v) {
@@ -940,13 +959,27 @@ async function* generateEncoder(typeUri, types) {
940
959
  }
941
960
  let docContext: Parameters<typeof jsonld.compact>[1] = options.context ??
942
961
  ${JSON.stringify(type.defaultContext)};
962
+ `;
963
+ const hasExtraContexts = Object.values(types).some((t) => t.properties.some((p) => p.extraContext != null));
964
+ if (hasExtraContexts) yield `
965
+ if (options.context == null) {
966
+ const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
967
+ const additionalContexts = getExtraContexts(values).filter(
968
+ context => !currentContexts.includes(context)
969
+ );
970
+ if (additionalContexts.length > 0) {
971
+ docContext = [...currentContexts, ...additionalContexts];
972
+ }
973
+ }
974
+ `;
975
+ yield `
943
976
  let compacted = await jsonld.compact(
944
977
  values,
945
978
  docContext,
946
979
  { documentLoader: options.contextLoader },
947
980
  );
948
981
  `;
949
- if (Object.values(types).some((t) => t.properties.some((p) => p.extraContext != null))) yield `
982
+ if (hasExtraContexts) yield `
950
983
  if (options.context == null) {
951
984
  const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
952
985
  const additionalContexts = getExtraContexts(compacted).filter(
@@ -2294,6 +2327,7 @@ async function* generateProperties(typeUri, types, moduleVarNames) {
2294
2327
  //#region src/class.ts
2295
2328
  const XSD_ANY_URI = "http://www.w3.org/2001/XMLSchema#anyURI";
2296
2329
  const FEDIFY_URL = "fedify:url";
2330
+ const FEDIFY_ABSOLUTE_IRI = "fedify:absoluteIri";
2297
2331
  const INTERNAL_RUNTIME_IMPORTS = [
2298
2332
  "compactJsonLdCache",
2299
2333
  "createScopedContextLoader",
@@ -2502,7 +2536,7 @@ export function getEntityTypeById(id: string | URL): $EntityType | undefined {
2502
2536
  `;
2503
2537
  }
2504
2538
  function canContainIriValue(property, types) {
2505
- return property.range.some((typeUri) => typeUri === XSD_ANY_URI || typeUri === FEDIFY_URL || types[typeUri]?.entity);
2539
+ return property.range.some((typeUri) => typeUri === XSD_ANY_URI || typeUri === FEDIFY_URL || typeUri === FEDIFY_ABSOLUTE_IRI || types[typeUri]?.entity);
2506
2540
  }
2507
2541
  function addKeys(keys, property) {
2508
2542
  keys.add(property.uri);
@@ -2560,12 +2594,38 @@ function isValidLanguageTag(language: string): boolean {
2560
2594
  throw error;
2561
2595
  }
2562
2596
  }
2597
+ `;
2598
+ if (Object.values(types).some((type) => type.properties.some((property) => property.range.includes(FEDIFY_ABSOLUTE_IRI)))) yield `
2599
+ function getAbsoluteIriValue(value: unknown): string | undefined {
2600
+ if (value == null || typeof value !== "object") return undefined;
2601
+ const node = value as Record<string, unknown>;
2602
+ if (typeof node["@id"] === "string") {
2603
+ return node["@id"].startsWith("_:") ? undefined : node["@id"];
2604
+ }
2605
+ // A language tag can come from a context's default language, so it does
2606
+ // not make a literal any less of an IRI:
2607
+ if (typeof node["@value"] !== "string") return undefined;
2608
+ const type = node["@type"];
2609
+ return type == null ||
2610
+ type === "http://www.w3.org/2001/XMLSchema#string" ||
2611
+ type === "http://www.w3.org/2001/XMLSchema#anyURI"
2612
+ ? node["@value"]
2613
+ : undefined;
2614
+ }
2615
+
2616
+ function isAbsoluteIriValue(value: unknown): boolean {
2617
+ const iri = getAbsoluteIriValue(value);
2618
+ return iri != null && canDecodeIri(iri);
2619
+ }
2563
2620
  `;
2564
2621
  const extraContexts = Object.fromEntries(Object.values(types).flatMap((type) => type.properties.flatMap((property) => property.extraContext == null ? [] : [[property.uri, property.extraContext]])));
2565
2622
  if (Object.keys(extraContexts).length > 0) yield `
2566
2623
  const extraPropertyContexts: Readonly<Record<string, string>> =
2567
2624
  ${JSON.stringify(extraContexts)};
2568
2625
 
2626
+ const extraContextUrls: readonly string[] =
2627
+ ${JSON.stringify([...new Set(Object.values(extraContexts))])};
2628
+
2569
2629
  function getExtraContexts(document: unknown): string[] {
2570
2630
  const contexts = new Set<string>();
2571
2631
  const pending: unknown[] = [document];
@@ -2578,7 +2638,16 @@ function getExtraContexts(document: unknown): string[] {
2578
2638
  }
2579
2639
  if ("@value" in value) continue;
2580
2640
  for (const [key, child] of globalThis.Object.entries(value)) {
2581
- if (key === "@context") continue;
2641
+ if (key === "@context") {
2642
+ for (const context of Array.isArray(child) ? child : [child]) {
2643
+ if (
2644
+ typeof context === "string" && extraContextUrls.includes(context)
2645
+ ) {
2646
+ contexts.add(context);
2647
+ }
2648
+ }
2649
+ continue;
2650
+ }
2582
2651
  if (globalThis.Object.hasOwn(extraPropertyContexts, key)) {
2583
2652
  contexts.add(extraPropertyContexts[key]);
2584
2653
  }
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.5.0-dev.2332+d4e3f91f";
10
+ var version = "2.5.0-dev.2355+c92a1cb1";
11
11
  //#endregion
12
12
  //#region src/type.ts
13
13
  const HEURISTICS_CONTEXTS = [
@@ -326,6 +326,25 @@ const scalarTypes = {
326
326
  },
327
327
  skipUnparsable: true
328
328
  },
329
+ "fedify:absoluteIri": {
330
+ name: "URL",
331
+ typeGuard(v) {
332
+ return `${v} instanceof URL`;
333
+ },
334
+ encoder(v) {
335
+ return `{ "@id": formatIri(${v}) }`;
336
+ },
337
+ compactEncoder(v) {
338
+ return `formatIri(${v})`;
339
+ },
340
+ dataCheck(v) {
341
+ return `isAbsoluteIriValue(${v})`;
342
+ },
343
+ decoder(v) {
344
+ return `decodeIri(getAbsoluteIriValue(${v})!)`;
345
+ },
346
+ skipUnparsable: true
347
+ },
329
348
  "fedify:gatewayUrl": {
330
349
  name: "URL",
331
350
  typeGuard(v) {
@@ -939,13 +958,27 @@ async function* generateEncoder(typeUri, types) {
939
958
  }
940
959
  let docContext: Parameters<typeof jsonld.compact>[1] = options.context ??
941
960
  ${JSON.stringify(type.defaultContext)};
961
+ `;
962
+ const hasExtraContexts = Object.values(types).some((t) => t.properties.some((p) => p.extraContext != null));
963
+ if (hasExtraContexts) yield `
964
+ if (options.context == null) {
965
+ const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
966
+ const additionalContexts = getExtraContexts(values).filter(
967
+ context => !currentContexts.includes(context)
968
+ );
969
+ if (additionalContexts.length > 0) {
970
+ docContext = [...currentContexts, ...additionalContexts];
971
+ }
972
+ }
973
+ `;
974
+ yield `
942
975
  let compacted = await jsonld.compact(
943
976
  values,
944
977
  docContext,
945
978
  { documentLoader: options.contextLoader },
946
979
  );
947
980
  `;
948
- if (Object.values(types).some((t) => t.properties.some((p) => p.extraContext != null))) yield `
981
+ if (hasExtraContexts) yield `
949
982
  if (options.context == null) {
950
983
  const currentContexts = Array.isArray(docContext) ? docContext : [docContext];
951
984
  const additionalContexts = getExtraContexts(compacted).filter(
@@ -2293,6 +2326,7 @@ async function* generateProperties(typeUri, types, moduleVarNames) {
2293
2326
  //#region src/class.ts
2294
2327
  const XSD_ANY_URI = "http://www.w3.org/2001/XMLSchema#anyURI";
2295
2328
  const FEDIFY_URL = "fedify:url";
2329
+ const FEDIFY_ABSOLUTE_IRI = "fedify:absoluteIri";
2296
2330
  const INTERNAL_RUNTIME_IMPORTS = [
2297
2331
  "compactJsonLdCache",
2298
2332
  "createScopedContextLoader",
@@ -2501,7 +2535,7 @@ export function getEntityTypeById(id: string | URL): $EntityType | undefined {
2501
2535
  `;
2502
2536
  }
2503
2537
  function canContainIriValue(property, types) {
2504
- return property.range.some((typeUri) => typeUri === XSD_ANY_URI || typeUri === FEDIFY_URL || types[typeUri]?.entity);
2538
+ return property.range.some((typeUri) => typeUri === XSD_ANY_URI || typeUri === FEDIFY_URL || typeUri === FEDIFY_ABSOLUTE_IRI || types[typeUri]?.entity);
2505
2539
  }
2506
2540
  function addKeys(keys, property) {
2507
2541
  keys.add(property.uri);
@@ -2559,12 +2593,38 @@ function isValidLanguageTag(language: string): boolean {
2559
2593
  throw error;
2560
2594
  }
2561
2595
  }
2596
+ `;
2597
+ if (Object.values(types).some((type) => type.properties.some((property) => property.range.includes(FEDIFY_ABSOLUTE_IRI)))) yield `
2598
+ function getAbsoluteIriValue(value: unknown): string | undefined {
2599
+ if (value == null || typeof value !== "object") return undefined;
2600
+ const node = value as Record<string, unknown>;
2601
+ if (typeof node["@id"] === "string") {
2602
+ return node["@id"].startsWith("_:") ? undefined : node["@id"];
2603
+ }
2604
+ // A language tag can come from a context's default language, so it does
2605
+ // not make a literal any less of an IRI:
2606
+ if (typeof node["@value"] !== "string") return undefined;
2607
+ const type = node["@type"];
2608
+ return type == null ||
2609
+ type === "http://www.w3.org/2001/XMLSchema#string" ||
2610
+ type === "http://www.w3.org/2001/XMLSchema#anyURI"
2611
+ ? node["@value"]
2612
+ : undefined;
2613
+ }
2614
+
2615
+ function isAbsoluteIriValue(value: unknown): boolean {
2616
+ const iri = getAbsoluteIriValue(value);
2617
+ return iri != null && canDecodeIri(iri);
2618
+ }
2562
2619
  `;
2563
2620
  const extraContexts = Object.fromEntries(Object.values(types).flatMap((type) => type.properties.flatMap((property) => property.extraContext == null ? [] : [[property.uri, property.extraContext]])));
2564
2621
  if (Object.keys(extraContexts).length > 0) yield `
2565
2622
  const extraPropertyContexts: Readonly<Record<string, string>> =
2566
2623
  ${JSON.stringify(extraContexts)};
2567
2624
 
2625
+ const extraContextUrls: readonly string[] =
2626
+ ${JSON.stringify([...new Set(Object.values(extraContexts))])};
2627
+
2568
2628
  function getExtraContexts(document: unknown): string[] {
2569
2629
  const contexts = new Set<string>();
2570
2630
  const pending: unknown[] = [document];
@@ -2577,7 +2637,16 @@ function getExtraContexts(document: unknown): string[] {
2577
2637
  }
2578
2638
  if ("@value" in value) continue;
2579
2639
  for (const [key, child] of globalThis.Object.entries(value)) {
2580
- if (key === "@context") continue;
2640
+ if (key === "@context") {
2641
+ for (const context of Array.isArray(child) ? child : [child]) {
2642
+ if (
2643
+ typeof context === "string" && extraContextUrls.includes(context)
2644
+ ) {
2645
+ contexts.add(context);
2646
+ }
2647
+ }
2648
+ continue;
2649
+ }
2581
2650
  if (globalThis.Object.hasOwn(extraPropertyContexts, key)) {
2582
2651
  contexts.add(extraPropertyContexts[key]);
2583
2652
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-tools",
3
- "version": "2.5.0-dev.2332+d4e3f91f",
3
+ "version": "2.5.0-dev.2355+c92a1cb1",
4
4
  "description": "Code generator for Activity Vocabulary APIs",
5
5
  "homepage": "https://fedify.dev/",
6
6
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "@types/node": "^24.12.2",
52
52
  "tsdown": "^0.22.0",
53
53
  "typescript": "^6.0.0",
54
- "@fedify/vocab-runtime": "^2.5.0-dev.2332+d4e3f91f"
54
+ "@fedify/vocab-runtime": "^2.5.0-dev.2355+c92a1cb1"
55
55
  },
56
56
  "dependencies": {
57
57
  "@cfworker/json-schema": "^4.1.1",