@fedify/vocab 2.3.9-dev.2048 → 2.3.9

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.
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  globalThis.addEventListener = () => {};
3
- import { A as Question, C as Offer, D as Place, E as Person, F as Source, I as Tombstone, M as QuoteRequest, N as Reject, O as PropertyValue, S as Object$1, _ as InteractionPolicy, b as Measure, c as Create, d as Document, f as Endpoints, g as Intent, h as Hashtag, i as Announce, j as QuoteAuthorization, k as Proposal, l as CryptographicKey, n as Activity, o as Collection, p as Follow, r as Agreement, s as Commitment, t as Accept, u as Delete, v as InteractionRule, w as OrderedCollectionPage, x as Note, y as Link, z as vocab_exports } from "./vocab-BhXsxjRa.mjs";
3
+ import { A as Question, C as Offer, D as Place, E as Person, F as Source, I as Tombstone, M as QuoteRequest, N as Reject, O as PropertyValue, S as Object$1, _ as InteractionPolicy, b as Measure, c as Create, d as Document, f as Endpoints, g as Intent, h as Hashtag, i as Announce, j as QuoteAuthorization, k as Proposal, l as CryptographicKey, n as Activity, o as Collection, p as Follow, r as Agreement, s as Commitment, t as Accept, u as Delete, v as InteractionRule, w as OrderedCollectionPage, x as Note, y as Link, z as vocab_exports } from "./vocab-BeDwBlW3.mjs";
4
4
  import { t as assertInstanceOf } from "./utils-CE8Dk5hm.mjs";
5
5
  import { mockDocumentLoader, test } from "@fedify/fixture";
6
6
  import { deepStrictEqual, notDeepStrictEqual, ok, rejects, throws } from "node:assert/strict";
@@ -412,6 +412,72 @@ test({
412
412
  deepStrictEqual(objects2[0].name, "Second object");
413
413
  }
414
414
  });
415
+ function embeddedPropertyDocument(property) {
416
+ const context = "https://www.w3.org/ns/activitystreams";
417
+ return {
418
+ "@context": [context, { CustomImage: `${context}#Image` }],
419
+ type: property === "attachment" ? "Note" : "Create",
420
+ [property]: [
421
+ {
422
+ "@context": [context, { "@vocab": "https://example.com/" }],
423
+ type: "CustomImage",
424
+ name: "Malformed when reparsed"
425
+ },
426
+ {
427
+ "@context": context,
428
+ type: "Image",
429
+ name: "Cached image"
430
+ },
431
+ {
432
+ type: "Image",
433
+ name: "Decoded image"
434
+ }
435
+ ]
436
+ };
437
+ }
438
+ for (const suppressError of [
439
+ void 0,
440
+ false,
441
+ true
442
+ ]) {
443
+ test(`Note.getAttachments() cached parse failure (suppressError: ${suppressError})`, async () => {
444
+ const jsonLd = embeddedPropertyDocument("attachment");
445
+ const note = await Note.fromJsonLd(jsonLd);
446
+ if (suppressError) {
447
+ for (let i = 0; i < 2; i++) deepStrictEqual((await Array.fromAsync(note.getAttachments({ suppressError }))).map((attachment) => attachment.name), ["Cached image", "Decoded image"]);
448
+ await rejects(() => Array.fromAsync(note.getAttachments()), TypeError);
449
+ } else await rejects(() => Array.fromAsync(note.getAttachments({ suppressError })), TypeError);
450
+ deepStrictEqual(await note.toJsonLd(), jsonLd);
451
+ });
452
+ test(`Activity.getObject() cached parse failure (suppressError: ${suppressError})`, async () => {
453
+ const jsonLd = embeddedPropertyDocument("object");
454
+ const activity = await Activity.fromJsonLd(jsonLd);
455
+ if (suppressError) {
456
+ deepStrictEqual(await activity.getObject({ suppressError }), null);
457
+ deepStrictEqual(await activity.getObject({ suppressError }), null);
458
+ await rejects(() => activity.getObject(), TypeError);
459
+ } else await rejects(() => activity.getObject({ suppressError }), TypeError);
460
+ deepStrictEqual(await activity.toJsonLd(), jsonLd);
461
+ });
462
+ }
463
+ test("Note.getAttachments() suppresses cached context loader failures", async () => {
464
+ const note = await Note.fromJsonLd(embeddedPropertyDocument("attachment"));
465
+ const contextLoader = () => Promise.reject(/* @__PURE__ */ new Error("Context loader failed"));
466
+ await rejects(() => Array.fromAsync(note.getAttachments({ contextLoader })), (error) => error instanceof Error && !(error instanceof TypeError));
467
+ deepStrictEqual((await Array.fromAsync(note.getAttachments({
468
+ contextLoader,
469
+ suppressError: true
470
+ }))).map((attachment) => attachment.name), ["Decoded image"]);
471
+ });
472
+ test("Activity.getObject() suppresses cached context loader failures", async () => {
473
+ const activity = await Activity.fromJsonLd(embeddedPropertyDocument("object"));
474
+ const contextLoader = () => Promise.reject(/* @__PURE__ */ new Error("Context loader failed"));
475
+ await rejects(() => activity.getObject({ contextLoader }), (error) => error instanceof Error && !(error instanceof TypeError));
476
+ deepStrictEqual(await activity.getObject({
477
+ contextLoader,
478
+ suppressError: true
479
+ }), null);
480
+ });
415
481
  test("Activity.clone()", async () => {
416
482
  const activity = new Activity({
417
483
  actor: new Person({ name: "John Doe" }),
@@ -1080,6 +1146,83 @@ test("Delete.toJsonLd() compacts embedded QuoteRequest", async () => {
1080
1146
  type: "Delete"
1081
1147
  });
1082
1148
  });
1149
+ test("Note.quoteUrl (IRI-typed alias terms)", async () => {
1150
+ const jsonLd = {
1151
+ "@context": ["https://www.w3.org/ns/activitystreams", {
1152
+ fedibird: "http://fedibird.com/ns#",
1153
+ misskey: "https://misskey-hub.net/ns#",
1154
+ _misskey_quote: {
1155
+ "@id": "misskey:_misskey_quote",
1156
+ "@type": "@id"
1157
+ },
1158
+ quoteUri: {
1159
+ "@id": "fedibird:quoteUri",
1160
+ "@type": "@id"
1161
+ }
1162
+ }],
1163
+ id: "https://example.com/notes/1",
1164
+ type: "Note",
1165
+ _misskey_quote: "https://example.com/notes/quoted",
1166
+ quoteUri: "https://example.com/notes/quoted2"
1167
+ };
1168
+ deepStrictEqual((await Note.fromJsonLd(jsonLd)).quoteUrl, new URL("https://example.com/notes/quoted"));
1169
+ delete jsonLd._misskey_quote;
1170
+ deepStrictEqual((await Note.fromJsonLd(jsonLd)).quoteUrl, new URL("https://example.com/notes/quoted2"));
1171
+ });
1172
+ test("Note.quoteUrl (IRI-typed primary term)", async () => {
1173
+ const context = ["https://www.w3.org/ns/activitystreams", {
1174
+ misskey: "https://misskey-hub.net/ns#",
1175
+ quoteUrl: {
1176
+ "@id": "as:quoteUrl",
1177
+ "@type": "@id"
1178
+ },
1179
+ _misskey_quote: "misskey:_misskey_quote"
1180
+ }];
1181
+ deepStrictEqual((await Note.fromJsonLd({
1182
+ "@context": context,
1183
+ type: "Note",
1184
+ quoteUrl: "https://example.com/object"
1185
+ })).quoteUrl, new URL("https://example.com/object"));
1186
+ deepStrictEqual((await Note.fromJsonLd({
1187
+ "@context": context,
1188
+ type: "Note",
1189
+ quoteUrl: "https://example.com/object",
1190
+ _misskey_quote: "https://example.com/object2"
1191
+ })).quoteUrl, new URL("https://example.com/object"));
1192
+ });
1193
+ test("Note.quoteUrl (unparsable IRI-valued terms)", async () => {
1194
+ const context = ["https://www.w3.org/ns/activitystreams", {
1195
+ misskey: "https://misskey-hub.net/ns#",
1196
+ _misskey_quote: {
1197
+ "@id": "misskey:_misskey_quote",
1198
+ "@type": "@id"
1199
+ }
1200
+ }];
1201
+ for (const quote of [
1202
+ {
1203
+ type: "Note",
1204
+ content: "An inlined quote without an id"
1205
+ },
1206
+ "_:b0",
1207
+ "",
1208
+ "notes/relative"
1209
+ ]) {
1210
+ const loaded = await Note.fromJsonLd({
1211
+ "@context": context,
1212
+ id: "https://example.com/notes/1",
1213
+ type: "Note",
1214
+ content: "Hello",
1215
+ _misskey_quote: quote
1216
+ });
1217
+ deepStrictEqual(loaded.quoteUrl, null, JSON.stringify(quote));
1218
+ deepStrictEqual(loaded.content, "Hello");
1219
+ }
1220
+ deepStrictEqual((await Note.fromJsonLd({
1221
+ "@context": context,
1222
+ type: "Note",
1223
+ _misskey_quote: "at://did:plc:abc/app.bsky.feed.post/xyz"
1224
+ })).quoteUrl, new URL("at://" + encodeURIComponent("did:plc:abc/app.bsky.feed.post/xyz")));
1225
+ });
1083
1226
  test("Key.publicKey", async () => {
1084
1227
  const jwk = {
1085
1228
  kty: "RSA",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab",
3
- "version": "2.3.9-dev.2048+c88fcc8c",
3
+ "version": "2.3.9",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,9 +46,9 @@
46
46
  "es-toolkit": "1.46.1",
47
47
  "jsonld": "^9.0.0",
48
48
  "pkijs": "^3.3.3",
49
- "@fedify/vocab-tools": "2.3.9-dev.2048+c88fcc8c",
50
- "@fedify/webfinger": "2.3.9-dev.2048+c88fcc8c",
51
- "@fedify/vocab-runtime": "2.3.9-dev.2048+c88fcc8c"
49
+ "@fedify/webfinger": "2.3.9",
50
+ "@fedify/vocab-tools": "2.3.9",
51
+ "@fedify/vocab-runtime": "2.3.9"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^22.17.0",
@@ -80,7 +80,8 @@ test("lookupObject()", {
80
80
  );
81
81
  });
82
82
 
83
- fetchMock.removeRoutes();
83
+ // Workers load fixtures through fetch(), so keep the spy fallback between steps.
84
+ fetchMock.removeRoutes({ includeFallback: false });
84
85
  fetchMock.get("begin:https://example.com/.well-known/webfinger", {
85
86
  subject: "acct:janedoe@example.com",
86
87
  links: [
@@ -100,7 +101,7 @@ test("lookupObject()", {
100
101
  );
101
102
  });
102
103
 
103
- fetchMock.removeRoutes();
104
+ fetchMock.removeRoutes({ includeFallback: false });
104
105
  fetchMock.get(
105
106
  "begin:https://example.com/.well-known/webfinger",
106
107
  () =>
@@ -131,7 +132,7 @@ test("lookupObject()", {
131
132
  deepStrictEqual(await promise, null);
132
133
  });
133
134
 
134
- fetchMock.removeRoutes();
135
+ fetchMock.removeRoutes({ includeFallback: false });
135
136
  fetchMock.get(
136
137
  "begin:https://example.com/.well-known/webfinger",
137
138
  {
@@ -156,7 +157,7 @@ test("lookupObject()", {
156
157
  deepStrictEqual(person.id, new URL("https://example.com/person"));
157
158
  });
158
159
 
159
- fetchMock.removeRoutes();
160
+ fetchMock.removeRoutes({ includeFallback: false });
160
161
  fetchMock.get(
161
162
  "begin:https://example.com/.well-known/webfinger",
162
163
  () =>
@@ -187,7 +188,7 @@ test("lookupObject()", {
187
188
  deepStrictEqual(result, null);
188
189
  });
189
190
 
190
- fetchMock.removeRoutes();
191
+ fetchMock.removeRoutes({ includeFallback: false });
191
192
  fetchMock.get(
192
193
  "https://example.com/slow-object",
193
194
  () =>
package/src/vocab.test.ts CHANGED
@@ -622,6 +622,104 @@ test({
622
622
  },
623
623
  });
624
624
 
625
+ // The alias is inherited during initial expansion, but is unavailable when
626
+ // an embedded value is reparsed standalone from the cached JSON-LD.
627
+ function embeddedPropertyDocument(property: "attachment" | "object") {
628
+ const context = "https://www.w3.org/ns/activitystreams";
629
+ return {
630
+ "@context": [context, { CustomImage: `${context}#Image` }],
631
+ type: property === "attachment" ? "Note" : "Create",
632
+ [property]: [
633
+ {
634
+ "@context": [context, { "@vocab": "https://example.com/" }],
635
+ type: "CustomImage",
636
+ name: "Malformed when reparsed",
637
+ },
638
+ { "@context": context, type: "Image", name: "Cached image" },
639
+ { type: "Image", name: "Decoded image" },
640
+ ],
641
+ };
642
+ }
643
+
644
+ for (const suppressError of [undefined, false, true]) {
645
+ test(
646
+ `Note.getAttachments() cached parse failure (suppressError: ${suppressError})`,
647
+ async () => {
648
+ const jsonLd = embeddedPropertyDocument("attachment");
649
+ const note = await Note.fromJsonLd(jsonLd);
650
+ if (suppressError) {
651
+ for (let i = 0; i < 2; i++) {
652
+ const attachments: (Object | Link | PropertyValue)[] = await Array
653
+ .fromAsync(
654
+ note.getAttachments({ suppressError }),
655
+ );
656
+ deepStrictEqual(
657
+ attachments.map((attachment) => attachment.name),
658
+ ["Cached image", "Decoded image"],
659
+ );
660
+ }
661
+ // Suppression must not remove or replace the cached malformed value.
662
+ await rejects(() => Array.fromAsync(note.getAttachments()), TypeError);
663
+ } else {
664
+ await rejects(
665
+ () => Array.fromAsync(note.getAttachments({ suppressError })),
666
+ TypeError,
667
+ );
668
+ }
669
+ deepStrictEqual(await note.toJsonLd(), jsonLd);
670
+ },
671
+ );
672
+
673
+ test(
674
+ `Activity.getObject() cached parse failure (suppressError: ${suppressError})`,
675
+ async () => {
676
+ const jsonLd = embeddedPropertyDocument("object");
677
+ const activity = await Activity.fromJsonLd(jsonLd);
678
+ if (suppressError) {
679
+ deepStrictEqual(await activity.getObject({ suppressError }), null);
680
+ deepStrictEqual(await activity.getObject({ suppressError }), null);
681
+ await rejects(() => activity.getObject(), TypeError);
682
+ } else {
683
+ await rejects(() => activity.getObject({ suppressError }), TypeError);
684
+ }
685
+ deepStrictEqual(await activity.toJsonLd(), jsonLd);
686
+ },
687
+ );
688
+ }
689
+
690
+ test("Note.getAttachments() suppresses cached context loader failures", async () => {
691
+ const note = await Note.fromJsonLd(embeddedPropertyDocument("attachment"));
692
+ const contextLoader = () =>
693
+ Promise.reject(new Error("Context loader failed"));
694
+ await rejects(
695
+ () => Array.fromAsync(note.getAttachments({ contextLoader })),
696
+ (error: unknown) => error instanceof Error && !(error instanceof TypeError),
697
+ );
698
+ // Both cached values need the failing loader; the decoded value remains usable.
699
+ const attachments: (Object | Link | PropertyValue)[] = await Array.fromAsync(
700
+ note.getAttachments({ contextLoader, suppressError: true }),
701
+ );
702
+ deepStrictEqual(attachments.map((attachment) => attachment.name), [
703
+ "Decoded image",
704
+ ]);
705
+ });
706
+
707
+ test("Activity.getObject() suppresses cached context loader failures", async () => {
708
+ const activity = await Activity.fromJsonLd(
709
+ embeddedPropertyDocument("object"),
710
+ );
711
+ const contextLoader = () =>
712
+ Promise.reject(new Error("Context loader failed"));
713
+ await rejects(
714
+ () => activity.getObject({ contextLoader }),
715
+ (error: unknown) => error instanceof Error && !(error instanceof TypeError),
716
+ );
717
+ deepStrictEqual(
718
+ await activity.getObject({ contextLoader, suppressError: true }),
719
+ null,
720
+ );
721
+ });
722
+
625
723
  test("Activity.clone()", async () => {
626
724
  const activity = new Activity({
627
725
  actor: new Person({
@@ -1667,6 +1765,105 @@ test("Delete.toJsonLd() compacts embedded QuoteRequest", async () => {
1667
1765
  );
1668
1766
  });
1669
1767
 
1768
+ test("Note.quoteUrl (IRI-typed alias terms)", async () => {
1769
+ const jsonLd: Record<string, unknown> = {
1770
+ "@context": [
1771
+ "https://www.w3.org/ns/activitystreams",
1772
+ {
1773
+ fedibird: "http://fedibird.com/ns#",
1774
+ misskey: "https://misskey-hub.net/ns#",
1775
+ _misskey_quote: {
1776
+ "@id": "misskey:_misskey_quote",
1777
+ "@type": "@id",
1778
+ },
1779
+ quoteUri: {
1780
+ "@id": "fedibird:quoteUri",
1781
+ "@type": "@id",
1782
+ },
1783
+ },
1784
+ ],
1785
+ id: "https://example.com/notes/1",
1786
+ type: "Note",
1787
+ _misskey_quote: "https://example.com/notes/quoted",
1788
+ quoteUri: "https://example.com/notes/quoted2",
1789
+ };
1790
+
1791
+ const loaded = await Note.fromJsonLd(jsonLd);
1792
+ deepStrictEqual(loaded.quoteUrl, new URL("https://example.com/notes/quoted"));
1793
+
1794
+ delete jsonLd._misskey_quote;
1795
+ const loaded2 = await Note.fromJsonLd(jsonLd);
1796
+ deepStrictEqual(
1797
+ loaded2.quoteUrl,
1798
+ new URL("https://example.com/notes/quoted2"),
1799
+ );
1800
+ });
1801
+
1802
+ test("Note.quoteUrl (IRI-typed primary term)", async () => {
1803
+ const context = [
1804
+ "https://www.w3.org/ns/activitystreams",
1805
+ {
1806
+ misskey: "https://misskey-hub.net/ns#",
1807
+ quoteUrl: { "@id": "as:quoteUrl", "@type": "@id" },
1808
+ _misskey_quote: "misskey:_misskey_quote",
1809
+ },
1810
+ ];
1811
+
1812
+ const loaded = await Note.fromJsonLd({
1813
+ "@context": context,
1814
+ type: "Note",
1815
+ quoteUrl: "https://example.com/object",
1816
+ });
1817
+ deepStrictEqual(loaded.quoteUrl, new URL("https://example.com/object"));
1818
+
1819
+ // An IRI-valued quoteUrl still takes precedence over a plain-string alias:
1820
+ const loaded2 = await Note.fromJsonLd({
1821
+ "@context": context,
1822
+ type: "Note",
1823
+ quoteUrl: "https://example.com/object",
1824
+ _misskey_quote: "https://example.com/object2",
1825
+ });
1826
+ deepStrictEqual(loaded2.quoteUrl, new URL("https://example.com/object"));
1827
+ });
1828
+
1829
+ test("Note.quoteUrl (unparsable IRI-valued terms)", async () => {
1830
+ const context = [
1831
+ "https://www.w3.org/ns/activitystreams",
1832
+ {
1833
+ misskey: "https://misskey-hub.net/ns#",
1834
+ _misskey_quote: { "@id": "misskey:_misskey_quote", "@type": "@id" },
1835
+ },
1836
+ ];
1837
+ for (
1838
+ const quote of [
1839
+ { type: "Note", content: "An inlined quote without an id" },
1840
+ "_:b0",
1841
+ "",
1842
+ "notes/relative",
1843
+ ]
1844
+ ) {
1845
+ const loaded = await Note.fromJsonLd({
1846
+ "@context": context,
1847
+ id: "https://example.com/notes/1",
1848
+ type: "Note",
1849
+ content: "Hello",
1850
+ _misskey_quote: quote,
1851
+ });
1852
+ deepStrictEqual(loaded.quoteUrl, null, JSON.stringify(quote));
1853
+ deepStrictEqual(loaded.content, "Hello");
1854
+ }
1855
+
1856
+ const atUri = await Note.fromJsonLd({
1857
+ "@context": context,
1858
+ type: "Note",
1859
+ _misskey_quote: "at://did:plc:abc/app.bsky.feed.post/xyz",
1860
+ });
1861
+ deepStrictEqual(
1862
+ atUri.quoteUrl,
1863
+ new URL("at://" + encodeURIComponent("did:plc:abc/app.bsky.feed.post/xyz")),
1864
+ );
1865
+ });
1866
+
1670
1867
  test("Key.publicKey", async () => {
1671
1868
  const jwk = {
1672
1869
  kty: "RSA",