@fedify/vocab 2.1.0-dev.406 → 2.1.0-dev.411

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/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab",
3
- "version": "2.1.0-dev.406+61a21e4e",
3
+ "version": "2.1.0-dev.411+13731841",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts"
@@ -3,8 +3,8 @@
3
3
  globalThis.addEventListener = () => {};
4
4
 
5
5
  import { __export } from "./chunk-BeeFIeNn.js";
6
- import { Application, Group, Organization, Person, Service, test } from "./vocab-D_VpV6k4.js";
7
- import { deno_default, esm_default } from "./deno-C_9tl6Bv.js";
6
+ import { Application, Group, Organization, Person, Service, test } from "./vocab-DNa6QysH.js";
7
+ import { deno_default, esm_default } from "./deno-DmoNrT-i.js";
8
8
  import { getTypeId } from "./type-Dnf0m2yO.js";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
10
10
  import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert/strict";
@@ -1240,7 +1240,7 @@ var esm_default = FetchMock_default;
1240
1240
  //#endregion
1241
1241
  //#region deno.json
1242
1242
  var name = "@fedify/vocab";
1243
- var version = "2.1.0-dev.406+61a21e4e";
1243
+ var version = "2.1.0-dev.411+13731841";
1244
1244
  var license = "MIT";
1245
1245
  var exports = { ".": "./src/mod.ts" };
1246
1246
  var description = "Vocabularies library for @fedify/fedify";
@@ -3,6 +3,7 @@ name: Endpoints
3
3
  compactName: as:Endpoints
4
4
  uri: "https://www.w3.org/ns/activitystreams#Endpoints"
5
5
  entity: false
6
+ typeless: true
6
7
  description: Contents of {@link Actor}'s `endpoints`.
7
8
  defaultContext: "https://www.w3.org/ns/activitystreams"
8
9
 
@@ -2,8 +2,8 @@
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
 
5
- import { Collection, Note, Object as Object$1, Person, createTestTracerProvider, mockDocumentLoader, test } from "./vocab-D_VpV6k4.js";
6
- import { deno_default, esm_default } from "./deno-C_9tl6Bv.js";
5
+ import { Collection, Note, Object as Object$1, Person, createTestTracerProvider, mockDocumentLoader, test } from "./vocab-DNa6QysH.js";
6
+ import { deno_default, esm_default } from "./deno-DmoNrT-i.js";
7
7
  import { getTypeId } from "./type-Dnf0m2yO.js";
8
8
  import { assertInstanceOf } from "./utils-Dm0Onkcz.js";
9
9
  import { getLogger } from "@logtape/logtape";
package/dist/mod.cjs CHANGED
@@ -34,7 +34,7 @@ const es_toolkit = __toESM(require("es-toolkit"));
34
34
 
35
35
  //#region deno.json
36
36
  var name = "@fedify/vocab";
37
- var version = "2.1.0-dev.406+61a21e4e";
37
+ var version = "2.1.0-dev.411+13731841";
38
38
  var license = "MIT";
39
39
  var exports$1 = { ".": "./src/mod.ts" };
40
40
  var description = "Vocabularies library for @fedify/fedify";
@@ -19197,7 +19197,6 @@ var Endpoints = class {
19197
19197
  compactItems.push(item);
19198
19198
  }
19199
19199
  if (compactItems.length > 0) result["sharedInbox"] = compactItems.length > 1 ? compactItems : compactItems[0];
19200
- result["type"] = "as:Endpoints";
19201
19200
  if (this.id != null) result["id"] = this.id.href;
19202
19201
  result["@context"] = "https://www.w3.org/ns/activitystreams";
19203
19202
  return result;
@@ -19258,7 +19257,6 @@ var Endpoints = class {
19258
19257
  const propValue = array;
19259
19258
  values["https://www.w3.org/ns/activitystreams#sharedInbox"] = propValue;
19260
19259
  }
19261
- values["@type"] = ["https://www.w3.org/ns/activitystreams#Endpoints"];
19262
19260
  if (this.id != null) values["@id"] = this.id.href;
19263
19261
  if (options.format === "expand") return await jsonld.default.expand(values, { documentLoader: options.contextLoader });
19264
19262
  const docContext = options.context ?? "https://www.w3.org/ns/activitystreams";
@@ -40028,7 +40026,6 @@ var Source = class {
40028
40026
  compactItems.push(item);
40029
40027
  }
40030
40028
  if (compactItems.length > 0) result["mediaType"] = compactItems.length > 1 ? compactItems : compactItems[0];
40031
- result["type"] = "as:Source";
40032
40029
  if (this.id != null) result["id"] = this.id.href;
40033
40030
  result["@context"] = "https://www.w3.org/ns/activitystreams";
40034
40031
  return result;
@@ -40056,7 +40053,6 @@ var Source = class {
40056
40053
  const propValue = array;
40057
40054
  values["https://www.w3.org/ns/activitystreams#mediaType"] = propValue;
40058
40055
  }
40059
- values["@type"] = ["https://www.w3.org/ns/activitystreams#Source"];
40060
40056
  if (this.id != null) values["@id"] = this.id.href;
40061
40057
  if (options.format === "expand") return await jsonld.default.expand(values, { documentLoader: options.contextLoader });
40062
40058
  const docContext = options.context ?? "https://www.w3.org/ns/activitystreams";
package/dist/mod.js CHANGED
@@ -11,7 +11,7 @@ import { delay } from "es-toolkit";
11
11
 
12
12
  //#region deno.json
13
13
  var name = "@fedify/vocab";
14
- var version = "2.1.0-dev.406+61a21e4e";
14
+ var version = "2.1.0-dev.411+13731841";
15
15
  var license = "MIT";
16
16
  var exports = { ".": "./src/mod.ts" };
17
17
  var description = "Vocabularies library for @fedify/fedify";
@@ -19174,7 +19174,6 @@ var Endpoints = class {
19174
19174
  compactItems.push(item);
19175
19175
  }
19176
19176
  if (compactItems.length > 0) result["sharedInbox"] = compactItems.length > 1 ? compactItems : compactItems[0];
19177
- result["type"] = "as:Endpoints";
19178
19177
  if (this.id != null) result["id"] = this.id.href;
19179
19178
  result["@context"] = "https://www.w3.org/ns/activitystreams";
19180
19179
  return result;
@@ -19235,7 +19234,6 @@ var Endpoints = class {
19235
19234
  const propValue = array;
19236
19235
  values["https://www.w3.org/ns/activitystreams#sharedInbox"] = propValue;
19237
19236
  }
19238
- values["@type"] = ["https://www.w3.org/ns/activitystreams#Endpoints"];
19239
19237
  if (this.id != null) values["@id"] = this.id.href;
19240
19238
  if (options.format === "expand") return await jsonld.expand(values, { documentLoader: options.contextLoader });
19241
19239
  const docContext = options.context ?? "https://www.w3.org/ns/activitystreams";
@@ -40005,7 +40003,6 @@ var Source = class {
40005
40003
  compactItems.push(item);
40006
40004
  }
40007
40005
  if (compactItems.length > 0) result["mediaType"] = compactItems.length > 1 ? compactItems : compactItems[0];
40008
- result["type"] = "as:Source";
40009
40006
  if (this.id != null) result["id"] = this.id.href;
40010
40007
  result["@context"] = "https://www.w3.org/ns/activitystreams";
40011
40008
  return result;
@@ -40033,7 +40030,6 @@ var Source = class {
40033
40030
  const propValue = array;
40034
40031
  values["https://www.w3.org/ns/activitystreams#mediaType"] = propValue;
40035
40032
  }
40036
- values["@type"] = ["https://www.w3.org/ns/activitystreams#Source"];
40037
40033
  if (this.id != null) values["@id"] = this.id.href;
40038
40034
  if (options.format === "expand") return await jsonld.expand(values, { documentLoader: options.contextLoader });
40039
40035
  const docContext = options.context ?? "https://www.w3.org/ns/activitystreams";
package/dist/source.yaml CHANGED
@@ -3,6 +3,7 @@ name: Source
3
3
  compactName: as:Source
4
4
  uri: "https://www.w3.org/ns/activitystreams#Source"
5
5
  entity: false
6
+ typeless: true
6
7
  description: Contents of {@link Object}'s `source`.
7
8
  defaultContext: "https://www.w3.org/ns/activitystreams"
8
9
 
package/dist/type.test.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
 
5
- import { Person, test } from "./vocab-D_VpV6k4.js";
5
+ import { Person, test } from "./vocab-DNa6QysH.js";
6
6
  import { getTypeId } from "./type-Dnf0m2yO.js";
7
7
  import { deepStrictEqual } from "node:assert/strict";
8
8
 
@@ -25453,7 +25453,6 @@ var Endpoints = class {
25453
25453
  compactItems.push(item);
25454
25454
  }
25455
25455
  if (compactItems.length > 0) result["sharedInbox"] = compactItems.length > 1 ? compactItems : compactItems[0];
25456
- result["type"] = "as:Endpoints";
25457
25456
  if (this.id != null) result["id"] = this.id.href;
25458
25457
  result["@context"] = "https://www.w3.org/ns/activitystreams";
25459
25458
  return result;
@@ -25514,7 +25513,6 @@ var Endpoints = class {
25514
25513
  const propValue = array;
25515
25514
  values["https://www.w3.org/ns/activitystreams#sharedInbox"] = propValue;
25516
25515
  }
25517
- values["@type"] = ["https://www.w3.org/ns/activitystreams#Endpoints"];
25518
25516
  if (this.id != null) values["@id"] = this.id.href;
25519
25517
  if (options.format === "expand") return await jsonld.expand(values, { documentLoader: options.contextLoader });
25520
25518
  const docContext = options.context ?? "https://www.w3.org/ns/activitystreams";
@@ -46284,7 +46282,6 @@ var Source = class {
46284
46282
  compactItems.push(item);
46285
46283
  }
46286
46284
  if (compactItems.length > 0) result["mediaType"] = compactItems.length > 1 ? compactItems : compactItems[0];
46287
- result["type"] = "as:Source";
46288
46285
  if (this.id != null) result["id"] = this.id.href;
46289
46286
  result["@context"] = "https://www.w3.org/ns/activitystreams";
46290
46287
  return result;
@@ -46312,7 +46309,6 @@ var Source = class {
46312
46309
  const propValue = array;
46313
46310
  values["https://www.w3.org/ns/activitystreams#mediaType"] = propValue;
46314
46311
  }
46315
- values["@type"] = ["https://www.w3.org/ns/activitystreams#Source"];
46316
46312
  if (this.id != null) values["@id"] = this.id.href;
46317
46313
  if (options.format === "expand") return await jsonld.expand(values, { documentLoader: options.contextLoader });
46318
46314
  const docContext = options.context ?? "https://www.w3.org/ns/activitystreams";
@@ -47833,4 +47829,4 @@ View.prototype[Symbol.for("nodejs.util.inspect.custom")] = function(_depth, opti
47833
47829
  };
47834
47830
 
47835
47831
  //#endregion
47836
- export { Activity, Announce, Application, Collection, Create, CryptographicKey, Follow, Group, Hashtag, Link, Note, Object$1 as Object, OrderedCollectionPage, Organization, Person, Place, Question, Service, Source, createTestTracerProvider, mockDocumentLoader, test, vocab_exports };
47832
+ export { Activity, Announce, Application, Collection, Create, CryptographicKey, Endpoints, Follow, Group, Hashtag, Link, Note, Object$1 as Object, OrderedCollectionPage, Organization, Person, Place, Question, Service, Source, createTestTracerProvider, mockDocumentLoader, test, vocab_exports };
@@ -2,7 +2,7 @@
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
 
5
- import { Activity, Announce, Collection, Create, CryptographicKey, Follow, Hashtag, Link, Note, Object as Object$1, OrderedCollectionPage, Person, Place, Question, Source, mockDocumentLoader, test, vocab_exports } from "./vocab-D_VpV6k4.js";
5
+ import { Activity, Announce, Collection, Create, CryptographicKey, Endpoints, Follow, Hashtag, Link, Note, Object as Object$1, OrderedCollectionPage, Person, Place, Question, Source, mockDocumentLoader, test, vocab_exports } from "./vocab-DNa6QysH.js";
6
6
  import { assertInstanceOf } from "./utils-Dm0Onkcz.js";
7
7
  import { deepStrictEqual, notDeepStrictEqual, ok, rejects, throws } from "node:assert/strict";
8
8
  import { LanguageString, decodeMultibase } from "@fedify/vocab-runtime";
@@ -432,6 +432,258 @@ test("Person.toJsonLd()", async () => {
432
432
  type: "Person"
433
433
  });
434
434
  });
435
+ test("Endpoints.toJsonLd() omits type", async () => {
436
+ const ep = new Endpoints({ sharedInbox: new URL("https://example.com/inbox") });
437
+ const compact = await ep.toJsonLd();
438
+ ok(!("type" in compact), "compact heuristic output should not have 'type'");
439
+ deepStrictEqual(compact["sharedInbox"], "https://example.com/inbox");
440
+ deepStrictEqual(compact["@context"], "https://www.w3.org/ns/activitystreams");
441
+ const expanded = await ep.toJsonLd({
442
+ format: "expand",
443
+ contextLoader: mockDocumentLoader
444
+ });
445
+ ok(!("@type" in expanded[0]), "expanded output should not have '@type'");
446
+ const compactLib = await ep.toJsonLd({
447
+ format: "compact",
448
+ contextLoader: mockDocumentLoader
449
+ });
450
+ ok(!("type" in compactLib), "compact (library) output should not have 'type'");
451
+ const restored = await Endpoints.fromJsonLd(compact, {
452
+ documentLoader: mockDocumentLoader,
453
+ contextLoader: mockDocumentLoader
454
+ });
455
+ deepStrictEqual(restored, ep);
456
+ });
457
+ test("Source.toJsonLd() omits type", async () => {
458
+ const src = new Source({
459
+ content: "Hello, world!",
460
+ mediaType: "text/plain"
461
+ });
462
+ const compact = await src.toJsonLd();
463
+ ok(!("type" in compact), "compact heuristic output should not have 'type'");
464
+ deepStrictEqual(compact["mediaType"], "text/plain");
465
+ const expanded = await src.toJsonLd({
466
+ format: "expand",
467
+ contextLoader: mockDocumentLoader
468
+ });
469
+ ok(!("@type" in expanded[0]), "expanded output should not have '@type'");
470
+ const restored = await Source.fromJsonLd(compact, {
471
+ documentLoader: mockDocumentLoader,
472
+ contextLoader: mockDocumentLoader
473
+ });
474
+ deepStrictEqual(restored, src);
475
+ });
476
+ test("Endpoints.fromJsonLd() accepts input with @type (backward compat)", async () => {
477
+ const ep = await Endpoints.fromJsonLd({
478
+ "@context": "https://www.w3.org/ns/activitystreams",
479
+ "type": "as:Endpoints",
480
+ "sharedInbox": "https://example.com/inbox"
481
+ }, {
482
+ documentLoader: mockDocumentLoader,
483
+ contextLoader: mockDocumentLoader
484
+ });
485
+ assertInstanceOf(ep, Endpoints);
486
+ deepStrictEqual(ep.sharedInbox?.href, "https://example.com/inbox");
487
+ });
488
+ test("Source.fromJsonLd() accepts input with @type (backward compat)", async () => {
489
+ const src = await Source.fromJsonLd({
490
+ "@context": "https://www.w3.org/ns/activitystreams",
491
+ "type": "as:Source",
492
+ "content": "Hello",
493
+ "mediaType": "text/plain"
494
+ }, {
495
+ documentLoader: mockDocumentLoader,
496
+ contextLoader: mockDocumentLoader
497
+ });
498
+ assertInstanceOf(src, Source);
499
+ deepStrictEqual(src.content, "Hello");
500
+ deepStrictEqual(src.mediaType, "text/plain");
501
+ });
502
+ test("Endpoints with all properties set omits type", async () => {
503
+ const ep = new Endpoints({
504
+ proxyUrl: new URL("https://example.com/proxy"),
505
+ oauthAuthorizationEndpoint: new URL("https://example.com/oauth/authorize"),
506
+ oauthTokenEndpoint: new URL("https://example.com/oauth/token"),
507
+ provideClientKey: new URL("https://example.com/provide-key"),
508
+ signClientKey: new URL("https://example.com/sign-key"),
509
+ sharedInbox: new URL("https://example.com/inbox")
510
+ });
511
+ const compact = await ep.toJsonLd();
512
+ ok(!("type" in compact), "compact output should not have 'type'");
513
+ deepStrictEqual(compact["proxyUrl"], "https://example.com/proxy");
514
+ deepStrictEqual(compact["oauthAuthorizationEndpoint"], "https://example.com/oauth/authorize");
515
+ deepStrictEqual(compact["oauthTokenEndpoint"], "https://example.com/oauth/token");
516
+ deepStrictEqual(compact["provideClientKey"], "https://example.com/provide-key");
517
+ deepStrictEqual(compact["signClientKey"], "https://example.com/sign-key");
518
+ deepStrictEqual(compact["sharedInbox"], "https://example.com/inbox");
519
+ for (const format of [
520
+ void 0,
521
+ "compact",
522
+ "expand"
523
+ ]) {
524
+ const jsonLd = await ep.toJsonLd({
525
+ format,
526
+ contextLoader: mockDocumentLoader
527
+ });
528
+ const restored = await Endpoints.fromJsonLd(jsonLd, {
529
+ documentLoader: mockDocumentLoader,
530
+ contextLoader: mockDocumentLoader
531
+ });
532
+ deepStrictEqual(restored, ep, `round-trip failed for format=${format ?? "heuristic"}`);
533
+ }
534
+ });
535
+ test("Empty Endpoints omits type", async () => {
536
+ const ep = new Endpoints({});
537
+ const compact = await ep.toJsonLd();
538
+ ok(!("type" in compact), "empty compact output should not have 'type'");
539
+ const expanded = await ep.toJsonLd({
540
+ format: "expand",
541
+ contextLoader: mockDocumentLoader
542
+ });
543
+ ok(!("@type" in (expanded[0] ?? {})), "empty expanded output should not have '@type'");
544
+ });
545
+ test("Empty Source omits type", async () => {
546
+ const src = new Source({});
547
+ const compact = await src.toJsonLd();
548
+ ok(!("type" in compact), "empty compact output should not have 'type'");
549
+ const expanded = await src.toJsonLd({
550
+ format: "expand",
551
+ contextLoader: mockDocumentLoader
552
+ });
553
+ ok(!("@type" in (expanded[0] ?? {})), "empty expanded output should not have '@type'");
554
+ });
555
+ test("Person.toJsonLd() embeds Endpoints without type", async () => {
556
+ const person = new Person({
557
+ id: new URL("https://example.com/person/1"),
558
+ endpoints: new Endpoints({ sharedInbox: new URL("https://example.com/inbox") })
559
+ });
560
+ const compact = await person.toJsonLd();
561
+ const endpoints = compact["endpoints"];
562
+ ok(endpoints != null, "endpoints should be present");
563
+ ok(!("type" in endpoints), "embedded endpoints should not have 'type'");
564
+ deepStrictEqual(endpoints["sharedInbox"], "https://example.com/inbox");
565
+ const restored = await Person.fromJsonLd(compact, {
566
+ documentLoader: mockDocumentLoader,
567
+ contextLoader: mockDocumentLoader
568
+ });
569
+ deepStrictEqual(restored.id, person.id);
570
+ deepStrictEqual(restored.endpoints?.sharedInbox, person.endpoints?.sharedInbox);
571
+ const expanded = await person.toJsonLd({
572
+ format: "expand",
573
+ contextLoader: mockDocumentLoader
574
+ });
575
+ const expandedEndpoints = expanded[0]["https://www.w3.org/ns/activitystreams#endpoints"]?.[0];
576
+ ok(expandedEndpoints != null, "expanded endpoints should be present");
577
+ ok(!("@type" in expandedEndpoints), "expanded embedded endpoints should not have '@type'");
578
+ const restored2 = await Person.fromJsonLd(expanded, {
579
+ documentLoader: mockDocumentLoader,
580
+ contextLoader: mockDocumentLoader
581
+ });
582
+ deepStrictEqual(restored2.endpoints?.sharedInbox, person.endpoints?.sharedInbox);
583
+ const compactLib = await person.toJsonLd({
584
+ format: "compact",
585
+ contextLoader: mockDocumentLoader,
586
+ context: "https://www.w3.org/ns/activitystreams"
587
+ });
588
+ const endpointsLib = compactLib["endpoints"];
589
+ ok(endpointsLib != null, "compact-lib endpoints should be present");
590
+ ok(!("type" in endpointsLib), "compact-lib endpoints should not have 'type'");
591
+ const restored3 = await Person.fromJsonLd(compactLib, {
592
+ documentLoader: mockDocumentLoader,
593
+ contextLoader: mockDocumentLoader
594
+ });
595
+ deepStrictEqual(restored3.endpoints?.sharedInbox, person.endpoints?.sharedInbox);
596
+ });
597
+ test("Object.toJsonLd() embeds Source without type", async () => {
598
+ const obj = new Object$1({
599
+ id: new URL("https://example.com/object/1"),
600
+ source: new Source({
601
+ content: "Hello, world!",
602
+ mediaType: "text/plain"
603
+ })
604
+ });
605
+ const compact = await obj.toJsonLd();
606
+ const source = compact["source"];
607
+ ok(source != null, "source should be present");
608
+ ok(!("type" in source), "embedded source should not have 'type'");
609
+ deepStrictEqual(source["mediaType"], "text/plain");
610
+ const restored = await Object$1.fromJsonLd(compact, {
611
+ documentLoader: mockDocumentLoader,
612
+ contextLoader: mockDocumentLoader
613
+ });
614
+ deepStrictEqual(restored.source?.content, "Hello, world!");
615
+ deepStrictEqual(restored.source?.mediaType, "text/plain");
616
+ });
617
+ test("Person.fromJsonLd() with Mastodon-style endpoints (no type)", async () => {
618
+ const person = await Person.fromJsonLd({
619
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
620
+ "id": "https://mastodon.social/users/testuser",
621
+ "type": "Person",
622
+ "preferredUsername": "testuser",
623
+ "inbox": "https://mastodon.social/users/testuser/inbox",
624
+ "outbox": "https://mastodon.social/users/testuser/outbox",
625
+ "endpoints": { "sharedInbox": "https://mastodon.social/inbox" }
626
+ }, {
627
+ documentLoader: mockDocumentLoader,
628
+ contextLoader: mockDocumentLoader
629
+ });
630
+ assertInstanceOf(person, Person);
631
+ deepStrictEqual(person.endpoints?.sharedInbox?.href, "https://mastodon.social/inbox");
632
+ });
633
+ test("Person.fromJsonLd() with old Fedify-style endpoints (with type)", async () => {
634
+ const person = await Person.fromJsonLd({
635
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
636
+ "id": "https://example.com/users/testuser",
637
+ "type": "Person",
638
+ "endpoints": {
639
+ "type": "as:Endpoints",
640
+ "sharedInbox": "https://example.com/inbox"
641
+ }
642
+ }, {
643
+ documentLoader: mockDocumentLoader,
644
+ contextLoader: mockDocumentLoader
645
+ });
646
+ assertInstanceOf(person, Person);
647
+ deepStrictEqual(person.endpoints?.sharedInbox?.href, "https://example.com/inbox");
648
+ });
649
+ test("Source with LanguageString content omits type", async () => {
650
+ const src = new Source({
651
+ contents: [new LanguageString("Hello", "en"), new LanguageString("Bonjour", "fr")],
652
+ mediaType: "text/plain"
653
+ });
654
+ const compact = await src.toJsonLd();
655
+ ok(!("type" in compact), "source with LanguageString should not have 'type'");
656
+ const restored = await Source.fromJsonLd(compact, {
657
+ documentLoader: mockDocumentLoader,
658
+ contextLoader: mockDocumentLoader
659
+ });
660
+ deepStrictEqual(restored, src);
661
+ });
662
+ test("Cross-format round-trip for Endpoints", async () => {
663
+ const ep = new Endpoints({
664
+ sharedInbox: new URL("https://example.com/inbox"),
665
+ proxyUrl: new URL("https://example.com/proxy")
666
+ });
667
+ const compact1 = await ep.toJsonLd();
668
+ const restored1 = await Endpoints.fromJsonLd(compact1, {
669
+ documentLoader: mockDocumentLoader,
670
+ contextLoader: mockDocumentLoader
671
+ });
672
+ const expanded = await restored1.toJsonLd({
673
+ format: "expand",
674
+ contextLoader: mockDocumentLoader
675
+ });
676
+ const restored2 = await Endpoints.fromJsonLd(expanded, {
677
+ documentLoader: mockDocumentLoader,
678
+ contextLoader: mockDocumentLoader
679
+ });
680
+ const compact2 = await restored2.toJsonLd({ contextLoader: mockDocumentLoader });
681
+ const restored3 = await Endpoints.fromJsonLd(compact2, {
682
+ documentLoader: mockDocumentLoader,
683
+ contextLoader: mockDocumentLoader
684
+ });
685
+ deepStrictEqual(restored3, ep);
686
+ });
435
687
  test("Collection.fromJsonLd()", async () => {
436
688
  const collection = await Collection.fromJsonLd({
437
689
  "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/fep/5711"],
@@ -1212,7 +1464,13 @@ for (const typeUri in types) {
1212
1464
  "@id": "https://example.com/",
1213
1465
  "@type": typeUri
1214
1466
  });
1215
- deepStrictEqual(await instance.toJsonLd({
1467
+ if (type.typeless) {
1468
+ const compactJsonLd = await instance.toJsonLd({
1469
+ format: "compact",
1470
+ contextLoader: mockDocumentLoader
1471
+ });
1472
+ ok(!("type" in compactJsonLd), `${type.name} is typeless; compact output should not have 'type'`);
1473
+ } else deepStrictEqual(await instance.toJsonLd({
1216
1474
  format: "compact",
1217
1475
  contextLoader: mockDocumentLoader
1218
1476
  }), {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab",
3
- "version": "2.1.0-dev.406+61a21e4e",
3
+ "version": "2.1.0-dev.411+13731841",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -47,9 +47,9 @@
47
47
  "jsonld": "^9.0.0",
48
48
  "multicodec": "^3.2.1",
49
49
  "pkijs": "^3.3.3",
50
- "@fedify/webfinger": "2.1.0-dev.406+61a21e4e",
51
- "@fedify/vocab-runtime": "2.1.0-dev.406+61a21e4e",
52
- "@fedify/vocab-tools": "2.1.0-dev.406+61a21e4e"
50
+ "@fedify/vocab-runtime": "2.1.0-dev.411+13731841",
51
+ "@fedify/vocab-tools": "2.1.0-dev.411+13731841",
52
+ "@fedify/webfinger": "2.1.0-dev.411+13731841"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^22.17.0",
@@ -3,6 +3,7 @@ name: Endpoints
3
3
  compactName: as:Endpoints
4
4
  uri: "https://www.w3.org/ns/activitystreams#Endpoints"
5
5
  entity: false
6
+ typeless: true
6
7
  description: Contents of {@link Actor}'s `endpoints`.
7
8
  defaultContext: "https://www.w3.org/ns/activitystreams"
8
9
 
package/src/source.yaml CHANGED
@@ -3,6 +3,7 @@ name: Source
3
3
  compactName: as:Source
4
4
  uri: "https://www.w3.org/ns/activitystreams#Source"
5
5
  entity: false
6
+ typeless: true
6
7
  description: Contents of {@link Object}'s `source`.
7
8
  defaultContext: "https://www.w3.org/ns/activitystreams"
8
9
 
package/src/vocab.test.ts CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  Create,
24
24
  CryptographicKey,
25
25
  type DataIntegrityProof,
26
+ Endpoints,
26
27
  Follow,
27
28
  Hashtag,
28
29
  Link,
@@ -656,6 +657,388 @@ test("Person.toJsonLd()", async () => {
656
657
  });
657
658
  });
658
659
 
660
+ test("Endpoints.toJsonLd() omits type", async () => {
661
+ const ep = new Endpoints({
662
+ sharedInbox: new URL("https://example.com/inbox"),
663
+ });
664
+
665
+ // Compact heuristic path (format == null)
666
+ const compact = await ep.toJsonLd() as Record<string, unknown>;
667
+ ok(!("type" in compact), "compact heuristic output should not have 'type'");
668
+ deepStrictEqual(compact["sharedInbox"], "https://example.com/inbox");
669
+ deepStrictEqual(compact["@context"], "https://www.w3.org/ns/activitystreams");
670
+
671
+ // Expanded format
672
+ const expanded = await ep.toJsonLd({
673
+ format: "expand",
674
+ contextLoader: mockDocumentLoader,
675
+ }) as Record<string, unknown>[];
676
+ ok(
677
+ !("@type" in expanded[0]),
678
+ "expanded output should not have '@type'",
679
+ );
680
+
681
+ // Compact via JSON-LD library
682
+ const compactLib = await ep.toJsonLd({
683
+ format: "compact",
684
+ contextLoader: mockDocumentLoader,
685
+ }) as Record<string, unknown>;
686
+ ok(
687
+ !("type" in compactLib),
688
+ "compact (library) output should not have 'type'",
689
+ );
690
+
691
+ // Round-trip: compact heuristic → fromJsonLd → compare
692
+ const restored = await Endpoints.fromJsonLd(compact, {
693
+ documentLoader: mockDocumentLoader,
694
+ contextLoader: mockDocumentLoader,
695
+ });
696
+ deepStrictEqual(restored, ep);
697
+ });
698
+
699
+ test("Source.toJsonLd() omits type", async () => {
700
+ const src = new Source({
701
+ content: "Hello, world!",
702
+ mediaType: "text/plain",
703
+ });
704
+
705
+ // Compact heuristic path (format == null)
706
+ const compact = await src.toJsonLd() as Record<string, unknown>;
707
+ ok(!("type" in compact), "compact heuristic output should not have 'type'");
708
+ deepStrictEqual(compact["mediaType"], "text/plain");
709
+
710
+ // Expanded format
711
+ const expanded = await src.toJsonLd({
712
+ format: "expand",
713
+ contextLoader: mockDocumentLoader,
714
+ }) as Record<string, unknown>[];
715
+ ok(
716
+ !("@type" in expanded[0]),
717
+ "expanded output should not have '@type'",
718
+ );
719
+
720
+ // Round-trip: compact heuristic → fromJsonLd → compare
721
+ const restored = await Source.fromJsonLd(compact, {
722
+ documentLoader: mockDocumentLoader,
723
+ contextLoader: mockDocumentLoader,
724
+ });
725
+ deepStrictEqual(restored, src);
726
+ });
727
+
728
+ test("Endpoints.fromJsonLd() accepts input with @type (backward compat)", async () => {
729
+ // Older Fedify instances may still send @type for Endpoints
730
+ const ep = await Endpoints.fromJsonLd({
731
+ "@context": "https://www.w3.org/ns/activitystreams",
732
+ "type": "as:Endpoints",
733
+ "sharedInbox": "https://example.com/inbox",
734
+ }, {
735
+ documentLoader: mockDocumentLoader,
736
+ contextLoader: mockDocumentLoader,
737
+ });
738
+ assertInstanceOf(ep, Endpoints);
739
+ deepStrictEqual(ep.sharedInbox?.href, "https://example.com/inbox");
740
+ });
741
+
742
+ test("Source.fromJsonLd() accepts input with @type (backward compat)", async () => {
743
+ const src = await Source.fromJsonLd({
744
+ "@context": "https://www.w3.org/ns/activitystreams",
745
+ "type": "as:Source",
746
+ "content": "Hello",
747
+ "mediaType": "text/plain",
748
+ }, {
749
+ documentLoader: mockDocumentLoader,
750
+ contextLoader: mockDocumentLoader,
751
+ });
752
+ assertInstanceOf(src, Source);
753
+ deepStrictEqual(src.content, "Hello");
754
+ deepStrictEqual(src.mediaType, "text/plain");
755
+ });
756
+
757
+ test("Endpoints with all properties set omits type", async () => {
758
+ const ep = new Endpoints({
759
+ proxyUrl: new URL("https://example.com/proxy"),
760
+ oauthAuthorizationEndpoint: new URL("https://example.com/oauth/authorize"),
761
+ oauthTokenEndpoint: new URL("https://example.com/oauth/token"),
762
+ provideClientKey: new URL("https://example.com/provide-key"),
763
+ signClientKey: new URL("https://example.com/sign-key"),
764
+ sharedInbox: new URL("https://example.com/inbox"),
765
+ });
766
+
767
+ // Compact heuristic path
768
+ const compact = await ep.toJsonLd() as Record<string, unknown>;
769
+ ok(!("type" in compact), "compact output should not have 'type'");
770
+ deepStrictEqual(compact["proxyUrl"], "https://example.com/proxy");
771
+ deepStrictEqual(
772
+ compact["oauthAuthorizationEndpoint"],
773
+ "https://example.com/oauth/authorize",
774
+ );
775
+ deepStrictEqual(
776
+ compact["oauthTokenEndpoint"],
777
+ "https://example.com/oauth/token",
778
+ );
779
+ deepStrictEqual(
780
+ compact["provideClientKey"],
781
+ "https://example.com/provide-key",
782
+ );
783
+ deepStrictEqual(compact["signClientKey"], "https://example.com/sign-key");
784
+ deepStrictEqual(compact["sharedInbox"], "https://example.com/inbox");
785
+
786
+ // Round-trip all three formats
787
+ for (
788
+ const format of [undefined, "compact" as const, "expand" as const]
789
+ ) {
790
+ const jsonLd = await ep.toJsonLd({
791
+ format,
792
+ contextLoader: mockDocumentLoader,
793
+ });
794
+ const restored = await Endpoints.fromJsonLd(jsonLd, {
795
+ documentLoader: mockDocumentLoader,
796
+ contextLoader: mockDocumentLoader,
797
+ });
798
+ deepStrictEqual(
799
+ restored,
800
+ ep,
801
+ `round-trip failed for format=${format ?? "heuristic"}`,
802
+ );
803
+ }
804
+ });
805
+
806
+ test("Empty Endpoints omits type", async () => {
807
+ const ep = new Endpoints({});
808
+
809
+ const compact = await ep.toJsonLd() as Record<string, unknown>;
810
+ ok(!("type" in compact), "empty compact output should not have 'type'");
811
+
812
+ const expanded = await ep.toJsonLd({
813
+ format: "expand",
814
+ contextLoader: mockDocumentLoader,
815
+ }) as Record<string, unknown>[];
816
+ ok(
817
+ !("@type" in (expanded[0] ?? {})),
818
+ "empty expanded output should not have '@type'",
819
+ );
820
+ });
821
+
822
+ test("Empty Source omits type", async () => {
823
+ const src = new Source({});
824
+
825
+ const compact = await src.toJsonLd() as Record<string, unknown>;
826
+ ok(!("type" in compact), "empty compact output should not have 'type'");
827
+
828
+ const expanded = await src.toJsonLd({
829
+ format: "expand",
830
+ contextLoader: mockDocumentLoader,
831
+ }) as Record<string, unknown>[];
832
+ ok(
833
+ !("@type" in (expanded[0] ?? {})),
834
+ "empty expanded output should not have '@type'",
835
+ );
836
+ });
837
+
838
+ test("Person.toJsonLd() embeds Endpoints without type", async () => {
839
+ const person = new Person({
840
+ id: new URL("https://example.com/person/1"),
841
+ endpoints: new Endpoints({
842
+ sharedInbox: new URL("https://example.com/inbox"),
843
+ }),
844
+ });
845
+
846
+ // Compact heuristic path (the real-world code path)
847
+ const compact = await person.toJsonLd() as Record<string, unknown>;
848
+ const endpoints = compact["endpoints"] as Record<string, unknown>;
849
+ ok(endpoints != null, "endpoints should be present");
850
+ ok(
851
+ !("type" in endpoints),
852
+ "embedded endpoints should not have 'type'",
853
+ );
854
+ deepStrictEqual(endpoints["sharedInbox"], "https://example.com/inbox");
855
+
856
+ // Round-trip
857
+ const restored = await Person.fromJsonLd(compact, {
858
+ documentLoader: mockDocumentLoader,
859
+ contextLoader: mockDocumentLoader,
860
+ });
861
+ deepStrictEqual(restored.id, person.id);
862
+ deepStrictEqual(
863
+ restored.endpoints?.sharedInbox,
864
+ person.endpoints?.sharedInbox,
865
+ );
866
+
867
+ // Expanded format
868
+ const expanded = await person.toJsonLd({
869
+ format: "expand",
870
+ contextLoader: mockDocumentLoader,
871
+ }) as Record<string, unknown>[];
872
+ const expandedEndpoints =
873
+ (expanded[0]["https://www.w3.org/ns/activitystreams#endpoints"] as Record<
874
+ string,
875
+ unknown
876
+ >[])?.[0];
877
+ ok(expandedEndpoints != null, "expanded endpoints should be present");
878
+ ok(
879
+ !("@type" in expandedEndpoints),
880
+ "expanded embedded endpoints should not have '@type'",
881
+ );
882
+
883
+ // Expanded round-trip
884
+ const restored2 = await Person.fromJsonLd(expanded, {
885
+ documentLoader: mockDocumentLoader,
886
+ contextLoader: mockDocumentLoader,
887
+ });
888
+ deepStrictEqual(
889
+ restored2.endpoints?.sharedInbox,
890
+ person.endpoints?.sharedInbox,
891
+ );
892
+
893
+ // Compact via JSON-LD library
894
+ const compactLib = await person.toJsonLd({
895
+ format: "compact",
896
+ contextLoader: mockDocumentLoader,
897
+ context: "https://www.w3.org/ns/activitystreams",
898
+ }) as Record<string, unknown>;
899
+ const endpointsLib = compactLib["endpoints"] as Record<string, unknown>;
900
+ ok(endpointsLib != null, "compact-lib endpoints should be present");
901
+ ok(
902
+ !("type" in endpointsLib),
903
+ "compact-lib endpoints should not have 'type'",
904
+ );
905
+
906
+ // Compact library round-trip
907
+ const restored3 = await Person.fromJsonLd(compactLib, {
908
+ documentLoader: mockDocumentLoader,
909
+ contextLoader: mockDocumentLoader,
910
+ });
911
+ deepStrictEqual(
912
+ restored3.endpoints?.sharedInbox,
913
+ person.endpoints?.sharedInbox,
914
+ );
915
+ });
916
+
917
+ test("Object.toJsonLd() embeds Source without type", async () => {
918
+ const obj = new Object({
919
+ id: new URL("https://example.com/object/1"),
920
+ source: new Source({
921
+ content: "Hello, world!",
922
+ mediaType: "text/plain",
923
+ }),
924
+ });
925
+
926
+ // Compact heuristic path
927
+ const compact = await obj.toJsonLd() as Record<string, unknown>;
928
+ const source = compact["source"] as Record<string, unknown>;
929
+ ok(source != null, "source should be present");
930
+ ok(!("type" in source), "embedded source should not have 'type'");
931
+ deepStrictEqual(source["mediaType"], "text/plain");
932
+
933
+ // Round-trip
934
+ const restored = await Object.fromJsonLd(compact, {
935
+ documentLoader: mockDocumentLoader,
936
+ contextLoader: mockDocumentLoader,
937
+ });
938
+ deepStrictEqual(restored.source?.content, "Hello, world!");
939
+ deepStrictEqual(restored.source?.mediaType, "text/plain");
940
+ });
941
+
942
+ test("Person.fromJsonLd() with Mastodon-style endpoints (no type)", async () => {
943
+ // Mastodon serializes endpoints without a type field
944
+ const person = await Person.fromJsonLd({
945
+ "@context": [
946
+ "https://www.w3.org/ns/activitystreams",
947
+ "https://w3id.org/security/v1",
948
+ ],
949
+ "id": "https://mastodon.social/users/testuser",
950
+ "type": "Person",
951
+ "preferredUsername": "testuser",
952
+ "inbox": "https://mastodon.social/users/testuser/inbox",
953
+ "outbox": "https://mastodon.social/users/testuser/outbox",
954
+ "endpoints": {
955
+ "sharedInbox": "https://mastodon.social/inbox",
956
+ },
957
+ }, {
958
+ documentLoader: mockDocumentLoader,
959
+ contextLoader: mockDocumentLoader,
960
+ });
961
+ assertInstanceOf(person, Person);
962
+ deepStrictEqual(
963
+ person.endpoints?.sharedInbox?.href,
964
+ "https://mastodon.social/inbox",
965
+ );
966
+ });
967
+
968
+ test("Person.fromJsonLd() with old Fedify-style endpoints (with type)", async () => {
969
+ // Older Fedify versions serialized endpoints with type: "as:Endpoints"
970
+ const person = await Person.fromJsonLd({
971
+ "@context": [
972
+ "https://www.w3.org/ns/activitystreams",
973
+ "https://w3id.org/security/v1",
974
+ ],
975
+ "id": "https://example.com/users/testuser",
976
+ "type": "Person",
977
+ "endpoints": {
978
+ "type": "as:Endpoints",
979
+ "sharedInbox": "https://example.com/inbox",
980
+ },
981
+ }, {
982
+ documentLoader: mockDocumentLoader,
983
+ contextLoader: mockDocumentLoader,
984
+ });
985
+ assertInstanceOf(person, Person);
986
+ deepStrictEqual(
987
+ person.endpoints?.sharedInbox?.href,
988
+ "https://example.com/inbox",
989
+ );
990
+ });
991
+
992
+ test("Source with LanguageString content omits type", async () => {
993
+ const src = new Source({
994
+ contents: [
995
+ new LanguageString("Hello", "en"),
996
+ new LanguageString("Bonjour", "fr"),
997
+ ],
998
+ mediaType: "text/plain",
999
+ });
1000
+
1001
+ const compact = await src.toJsonLd() as Record<string, unknown>;
1002
+ ok(!("type" in compact), "source with LanguageString should not have 'type'");
1003
+
1004
+ // Round-trip
1005
+ const restored = await Source.fromJsonLd(compact, {
1006
+ documentLoader: mockDocumentLoader,
1007
+ contextLoader: mockDocumentLoader,
1008
+ });
1009
+ deepStrictEqual(restored, src);
1010
+ });
1011
+
1012
+ test("Cross-format round-trip for Endpoints", async () => {
1013
+ const ep = new Endpoints({
1014
+ sharedInbox: new URL("https://example.com/inbox"),
1015
+ proxyUrl: new URL("https://example.com/proxy"),
1016
+ });
1017
+
1018
+ // compact heuristic → expanded → compact heuristic
1019
+ const compact1 = await ep.toJsonLd();
1020
+ const restored1 = await Endpoints.fromJsonLd(compact1, {
1021
+ documentLoader: mockDocumentLoader,
1022
+ contextLoader: mockDocumentLoader,
1023
+ });
1024
+ const expanded = await restored1.toJsonLd({
1025
+ format: "expand",
1026
+ contextLoader: mockDocumentLoader,
1027
+ });
1028
+ const restored2 = await Endpoints.fromJsonLd(expanded, {
1029
+ documentLoader: mockDocumentLoader,
1030
+ contextLoader: mockDocumentLoader,
1031
+ });
1032
+ const compact2 = await restored2.toJsonLd({
1033
+ contextLoader: mockDocumentLoader,
1034
+ });
1035
+ const restored3 = await Endpoints.fromJsonLd(compact2, {
1036
+ documentLoader: mockDocumentLoader,
1037
+ contextLoader: mockDocumentLoader,
1038
+ });
1039
+ deepStrictEqual(restored3, ep);
1040
+ });
1041
+
659
1042
  test("Collection.fromJsonLd()", async () => {
660
1043
  const collection = await Collection.fromJsonLd({
661
1044
  "@context": [
@@ -1860,18 +2243,29 @@ for (const typeUri in types) {
1860
2243
  "@type": typeUri,
1861
2244
  },
1862
2245
  );
1863
- deepStrictEqual(
1864
- await instance.toJsonLd({
2246
+ if (type.typeless) {
2247
+ const compactJsonLd = await instance.toJsonLd({
1865
2248
  format: "compact",
1866
2249
  contextLoader: mockDocumentLoader,
1867
- }),
1868
- {
1869
- "@context": type.defaultContext,
1870
- "id": "https://example.com/",
1871
- "type": type.compactName ??
1872
- (type.name === "DataIntegrityProof" ? type.name : type.uri),
1873
- },
1874
- );
2250
+ }) as Record<string, unknown>;
2251
+ ok(
2252
+ !("type" in compactJsonLd),
2253
+ `${type.name} is typeless; compact output should not have 'type'`,
2254
+ );
2255
+ } else {
2256
+ deepStrictEqual(
2257
+ await instance.toJsonLd({
2258
+ format: "compact",
2259
+ contextLoader: mockDocumentLoader,
2260
+ }),
2261
+ {
2262
+ "@context": type.defaultContext,
2263
+ "id": "https://example.com/",
2264
+ "type": type.compactName ??
2265
+ (type.name === "DataIntegrityProof" ? type.name : type.uri),
2266
+ },
2267
+ );
2268
+ }
1875
2269
 
1876
2270
  if (type.extends != null) {
1877
2271
  await rejects(() =>