@fedify/vocab 2.1.0-dev.405 → 2.1.0-dev.408

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/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(() =>
package/tsdown.config.ts CHANGED
@@ -7,7 +7,7 @@ export default [
7
7
  entry: [
8
8
  "./src/mod.ts",
9
9
  ],
10
- dts: true,
10
+ dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } },
11
11
  format: ["esm", "cjs"],
12
12
  platform: "neutral",
13
13
  external: [/^node:/],
@@ -27,7 +27,6 @@ export default [
27
27
  defineConfig({
28
28
  entry: (await Array.fromAsync(glob(`src/**/*.test.ts`)))
29
29
  .map((f) => f.replace(sep, "/")),
30
- dts: true,
31
30
  external: [/^node:/],
32
31
  inputOptions: {
33
32
  onwarn(warning, defaultHandler) {