@fedify/fedify 2.0.36-dev.2598 → 2.0.36-dev.2647

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.
Files changed (57) hide show
  1. package/dist/{builder-B5hI5Xsn.mjs → builder-Cv8baeaM.mjs} +3 -3
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{context-lxjaUTAI.d.ts → context-C00pWP8x.d.ts} +6 -0
  6. package/dist/{context-CnoWxuoB.d.cts → context-ao-qb3eP.d.cts} +6 -0
  7. package/dist/{deno--OYa_lxj.mjs → deno-Bk4jrob6.mjs} +1 -1
  8. package/dist/{docloader-BXR2f83H.mjs → docloader-w8xGNWvP.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +6 -6
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/inbox.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +602 -6
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.d.cts +2 -2
  16. package/dist/federation/mod.d.ts +2 -2
  17. package/dist/federation/mod.js +1 -1
  18. package/dist/federation/send.test.mjs +3 -3
  19. package/dist/federation/temporal.test.mjs +1 -1
  20. package/dist/federation/webfinger.test.mjs +1 -1
  21. package/dist/{http-CO_IVq7x.cjs → http-B9ZWGTaN.cjs} +1 -1
  22. package/dist/{http-CfyVeGgF.js → http-C1v_vvt4.js} +1 -1
  23. package/dist/{http-C3d2DXwF.mjs → http-CDK9iAjb.mjs} +2 -2
  24. package/dist/{inbox-huUE3IP8.mjs → inbox-DAm48Q_C.mjs} +1 -1
  25. package/dist/{key-D5e9c_yV.mjs → key-C8OKWl8g.mjs} +1 -1
  26. package/dist/{kv-cache-Zw0soD-7.js → kv-cache-B1Y7_B63.js} +1 -1
  27. package/dist/{kv-cache-BMlo_kyv.cjs → kv-cache-BFFwfQPZ.cjs} +1 -1
  28. package/dist/{ld-9qfz9rP_.mjs → ld-gUJKeHbH.mjs} +2 -2
  29. package/dist/{middleware-Ad6kTSBf.cjs → middleware-3t8hmtzt.cjs} +1 -1
  30. package/dist/{middleware-BKs_a9P1.js → middleware-D-DIq_jJ.js} +85 -34
  31. package/dist/{middleware-BhYpLGA_.mjs → middleware-DLxdIffl.mjs} +93 -42
  32. package/dist/{middleware-CT1Lbohd.mjs → middleware-LgcMsPlF.mjs} +1 -1
  33. package/dist/{middleware-CXoG7ZAW.cjs → middleware-_yMwRAE9.cjs} +86 -35
  34. package/dist/{mod-w1oyHseQ.d.cts → mod-6uJWdCc0.d.cts} +1 -1
  35. package/dist/{mod-I-34OMN3.d.ts → mod-DtRh015i.d.ts} +1 -1
  36. package/dist/mod.cjs +4 -4
  37. package/dist/mod.d.cts +2 -2
  38. package/dist/mod.d.ts +2 -2
  39. package/dist/mod.js +4 -4
  40. package/dist/nodeinfo/handler.test.mjs +1 -1
  41. package/dist/{owner-R4ffU9Ht.mjs → owner-BenEndHH.mjs} +2 -2
  42. package/dist/{proof-CiBs9Q4l.mjs → proof-Bo9pmjQJ.mjs} +2 -2
  43. package/dist/{proof-CE8nwTF6.js → proof-BvtZYI3N.js} +1 -1
  44. package/dist/{proof-BrreJTRF.cjs → proof-DF1RSNYg.cjs} +1 -1
  45. package/dist/{send-DpvZgVSA.mjs → send-PTFa5PEa.mjs} +2 -2
  46. package/dist/sig/http.test.mjs +2 -2
  47. package/dist/sig/key.test.mjs +2 -2
  48. package/dist/sig/ld.test.mjs +2 -2
  49. package/dist/sig/mod.cjs +2 -2
  50. package/dist/sig/mod.js +2 -2
  51. package/dist/sig/owner.test.mjs +1 -1
  52. package/dist/sig/proof.test.mjs +2 -2
  53. package/dist/{temporal-Dd-J410Z.mjs → temporal-CNbx4A18.mjs} +1 -1
  54. package/dist/utils/docloader.test.mjs +2 -2
  55. package/dist/utils/mod.cjs +1 -1
  56. package/dist/utils/mod.js +1 -1
  57. package/package.json +5 -5
@@ -9,18 +9,20 @@ import { r as assertFalse, t as assertThrows } from "../assert_throws-D4DNWrux.m
9
9
  import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
10
10
  import { t as assertNotEquals } from "../assert_not_equals-BkxW5QJI.mjs";
11
11
  import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
12
- import { c as signRequest, u as verifyRequest } from "../http-C3d2DXwF.mjs";
12
+ import { c as signRequest, u as verifyRequest } from "../http-CDK9iAjb.mjs";
13
13
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CCxRJhWa.mjs";
14
- import { n as getAuthenticatedDocumentLoader } from "../docloader-BXR2f83H.mjs";
15
- import { a as compactJsonLd, f as signJsonLd, i as attachSignature, m as verifyJsonLd, s as detachSignature } from "../ld-9qfz9rP_.mjs";
16
- import { t as doesActorOwnKey } from "../owner-R4ffU9Ht.mjs";
17
- import { n as signObject, r as verifyObject } from "../proof-CiBs9Q4l.mjs";
14
+ import { n as getAuthenticatedDocumentLoader } from "../docloader-w8xGNWvP.mjs";
15
+ import { a as compactJsonLd, f as signJsonLd, i as attachSignature, m as verifyJsonLd, s as detachSignature } from "../ld-gUJKeHbH.mjs";
16
+ import { t as doesActorOwnKey } from "../owner-BenEndHH.mjs";
17
+ import { n as signObject, r as verifyObject } from "../proof-Bo9pmjQJ.mjs";
18
18
  import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
19
- import { a as createFederation, i as KvSpecDeterminer, n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-BhYpLGA_.mjs";
19
+ import { a as createFederation, i as KvSpecDeterminer, n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-DLxdIffl.mjs";
20
+ import { t as SendActivityError } from "../send-PTFa5PEa.mjs";
20
21
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
21
22
  import * as vocab from "@fedify/vocab";
22
23
  import { Create, Offer, Person, getTypeId, lookupObject } from "@fedify/vocab";
23
24
  import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
25
+ import { configure, reset } from "@logtape/logtape";
24
26
  import dns from "node:dns/promises";
25
27
  //#region ../fixture/src/fixtures/example.com/create.json
26
28
  var id$2 = "https://example.com/create";
@@ -1575,6 +1577,320 @@ test("FederationImpl.sendActivity()", async (t) => {
1575
1577
  });
1576
1578
  esm_default.hardReset();
1577
1579
  });
1580
+ const QUEUE_STARTUP_WARNING = "Failed to start queue workers automatically: {error}";
1581
+ /**
1582
+ * Captures unhandled promise rejections for the duration of a test step.
1583
+ *
1584
+ * Deno dispatches `unhandledrejection` on `globalThis`, while Node.js and Bun
1585
+ * dispatch `unhandledRejection` on `process`. Bun's test runner currently
1586
+ * fires neither, so there the native runner's own unhandled-rejection failure
1587
+ * remains the signal; the assertion is harmless otherwise.
1588
+ */
1589
+ function captureUnhandledRejections() {
1590
+ const reasons = [];
1591
+ const isDeno = "Deno" in globalThis;
1592
+ const onRejectionEvent = (event) => {
1593
+ event.preventDefault();
1594
+ reasons.push(event.reason);
1595
+ };
1596
+ const onProcessRejection = (reason) => {
1597
+ reasons.push(reason);
1598
+ };
1599
+ const globalTarget = globalThis;
1600
+ const proc = globalThis.process;
1601
+ if (isDeno) globalTarget.addEventListener?.("unhandledrejection", onRejectionEvent);
1602
+ else proc?.on?.("unhandledRejection", onProcessRejection);
1603
+ async function settle() {
1604
+ await new Promise((resolve) => setTimeout(resolve, 0));
1605
+ await new Promise((resolve) => setTimeout(resolve, 0));
1606
+ }
1607
+ return {
1608
+ reasons,
1609
+ settle,
1610
+ stop() {
1611
+ if (isDeno) globalTarget.removeEventListener?.("unhandledrejection", onRejectionEvent);
1612
+ else proc?.off?.("unhandledRejection", onProcessRejection);
1613
+ }
1614
+ };
1615
+ }
1616
+ async function configureQueueStartupWarningCapture(records) {
1617
+ await configure({
1618
+ sinks: { buffer(record) {
1619
+ records.push(record);
1620
+ } },
1621
+ loggers: [{
1622
+ category: "fedify",
1623
+ sinks: ["buffer"],
1624
+ lowestLevel: "warning"
1625
+ }, {
1626
+ category: ["logtape", "meta"],
1627
+ sinks: []
1628
+ }],
1629
+ reset: true
1630
+ });
1631
+ }
1632
+ function queueStartupWarnings(records, error) {
1633
+ return records.filter((record) => record.level === "warning" && record.category.length === 3 && record.category[0] === "fedify" && record.category[1] === "federation" && record.category[2] === "queue" && record.rawMessage === QUEUE_STARTUP_WARNING && record.properties.error === error);
1634
+ }
1635
+ test("Federation.fetch() reports automatic queue startup failures", async (t) => {
1636
+ const listenError = /* @__PURE__ */ new TypeError("simulated listen() failure");
1637
+ const queue = {
1638
+ enqueue() {
1639
+ return Promise.resolve();
1640
+ },
1641
+ listen() {
1642
+ throw listenError;
1643
+ }
1644
+ };
1645
+ const capture = captureUnhandledRejections();
1646
+ const records = [];
1647
+ await configureQueueStartupWarningCapture(records);
1648
+ try {
1649
+ const federation = createFederation({
1650
+ kv: new MemoryKvStore(),
1651
+ queue,
1652
+ skipSignatureVerification: true,
1653
+ documentLoaderFactory: () => mockDocumentLoader,
1654
+ contextLoaderFactory: () => mockDocumentLoader
1655
+ });
1656
+ federation.setActorDispatcher("/actors/{identifier}", () => new Person({ id: new URL("https://example.com/actor") }));
1657
+ federation.setInboxListeners("/actors/{identifier}/inbox", "/inbox").on(Create, () => {});
1658
+ const post = (id) => federation.fetch(new Request("https://example.com/inbox", {
1659
+ method: "POST",
1660
+ headers: { "Content-Type": "application/activity+json" },
1661
+ body: JSON.stringify({
1662
+ "@context": "https://www.w3.org/ns/activitystreams",
1663
+ id,
1664
+ type: "Create",
1665
+ actor: "https://example.com/actor"
1666
+ })
1667
+ }), { contextData: void 0 });
1668
+ await t.step("synchronous failure", async () => {
1669
+ assertEquals((await post("https://example.com/activities/queued-1")).status, 202);
1670
+ await capture.settle();
1671
+ assertEquals(queueStartupWarnings(records, listenError).length, 1);
1672
+ assertEquals(capture.reasons, []);
1673
+ assertEquals((await post("https://example.com/activities/queued-2")).status, 202);
1674
+ await capture.settle();
1675
+ assertEquals(queueStartupWarnings(records, listenError).length, 1, "the warning must be logged only once per federation instance");
1676
+ assertEquals(capture.reasons, []);
1677
+ });
1678
+ await t.step("asynchronous failure", async () => {
1679
+ const asyncError = /* @__PURE__ */ new TypeError("simulated async listen() failure");
1680
+ let rejectListen;
1681
+ const asyncFederation = createFederation({
1682
+ kv: new MemoryKvStore(),
1683
+ queue: {
1684
+ enqueue() {
1685
+ return Promise.resolve();
1686
+ },
1687
+ listen() {
1688
+ return new Promise((_resolve, reject) => {
1689
+ rejectListen = reject;
1690
+ });
1691
+ }
1692
+ },
1693
+ skipSignatureVerification: true,
1694
+ documentLoaderFactory: () => mockDocumentLoader,
1695
+ contextLoaderFactory: () => mockDocumentLoader
1696
+ });
1697
+ asyncFederation.setActorDispatcher("/actors/{identifier}", () => new Person({ id: new URL("https://example.com/actor") }));
1698
+ asyncFederation.setInboxListeners("/actors/{identifier}/inbox", "/inbox").on(Create, () => {});
1699
+ assertEquals((await asyncFederation.fetch(new Request("https://example.com/inbox", {
1700
+ method: "POST",
1701
+ headers: { "Content-Type": "application/activity+json" },
1702
+ body: JSON.stringify({
1703
+ "@context": "https://www.w3.org/ns/activitystreams",
1704
+ id: "https://example.com/activities/queued-async",
1705
+ type: "Create",
1706
+ actor: "https://example.com/actor"
1707
+ })
1708
+ }), { contextData: void 0 })).status, 202);
1709
+ await capture.settle();
1710
+ assertEquals(queueStartupWarnings(records, asyncError).length, 0);
1711
+ assert(rejectListen != null);
1712
+ rejectListen(asyncError);
1713
+ await capture.settle();
1714
+ assertEquals(queueStartupWarnings(records, asyncError).length, 1, "a listener that rejects after startup must still be reported");
1715
+ assertEquals(capture.reasons, []);
1716
+ });
1717
+ } finally {
1718
+ await capture.settle();
1719
+ capture.stop();
1720
+ await reset();
1721
+ }
1722
+ });
1723
+ test("Federation.startQueue() rejects when a queue cannot listen", async () => {
1724
+ const listenError = /* @__PURE__ */ new TypeError("simulated listen() failure");
1725
+ const federation = createFederation({
1726
+ kv: new MemoryKvStore(),
1727
+ queue: {
1728
+ enqueue() {
1729
+ return Promise.resolve();
1730
+ },
1731
+ listen() {
1732
+ throw listenError;
1733
+ }
1734
+ },
1735
+ manuallyStartQueue: true
1736
+ });
1737
+ assertStrictEquals(await assertRejects(() => federation.startQueue(void 0)), listenError);
1738
+ });
1739
+ test("Federation.startQueue() keeps other queue workers running", async () => {
1740
+ const outboxError = /* @__PURE__ */ new TypeError("simulated outbox listen() failure");
1741
+ const lateInboxError = /* @__PURE__ */ new TypeError("simulated late inbox failure");
1742
+ let rejectInbox;
1743
+ let fanoutStarted = false;
1744
+ const inbox = {
1745
+ enqueue() {
1746
+ return Promise.resolve();
1747
+ },
1748
+ listen() {
1749
+ return new Promise((_resolve, reject) => {
1750
+ rejectInbox = reject;
1751
+ });
1752
+ }
1753
+ };
1754
+ const outbox = {
1755
+ enqueue() {
1756
+ return Promise.resolve();
1757
+ },
1758
+ listen() {
1759
+ throw outboxError;
1760
+ }
1761
+ };
1762
+ const fanout = {
1763
+ enqueue() {
1764
+ return Promise.resolve();
1765
+ },
1766
+ listen() {
1767
+ fanoutStarted = true;
1768
+ return new Promise(() => {});
1769
+ }
1770
+ };
1771
+ const capture = captureUnhandledRejections();
1772
+ try {
1773
+ const federation = createFederation({
1774
+ kv: new MemoryKvStore(),
1775
+ queue: {
1776
+ inbox,
1777
+ outbox,
1778
+ fanout
1779
+ },
1780
+ manuallyStartQueue: true
1781
+ });
1782
+ assertStrictEquals(await assertRejects(() => federation.startQueue(void 0)), outboxError);
1783
+ assert(fanoutStarted, "a synchronous throw from one queue must not prevent later queues from starting");
1784
+ assert(rejectInbox != null);
1785
+ rejectInbox(lateInboxError);
1786
+ await capture.settle();
1787
+ assertEquals(capture.reasons, [], "an earlier listener rejection must remain handled after startup fails");
1788
+ } finally {
1789
+ await capture.settle();
1790
+ capture.stop();
1791
+ }
1792
+ });
1793
+ test("ContextImpl.sendActivity() reports automatic queue startup failures", async (t) => {
1794
+ const sender = [{
1795
+ privateKey: ed25519PrivateKey,
1796
+ keyId: ed25519Multikey.id
1797
+ }];
1798
+ const recipient = {
1799
+ id: new URL("https://example.com/recipient"),
1800
+ inboxId: new URL("https://example.com/inbox")
1801
+ };
1802
+ const createActivity = () => new Create({
1803
+ id: new URL("https://example.com/activities/queue-startup-send"),
1804
+ actor: new URL("https://example.com/actor")
1805
+ });
1806
+ await t.step("outbox", async () => {
1807
+ const listenError = /* @__PURE__ */ new TypeError("simulated outbox listen() failure");
1808
+ const messages = [];
1809
+ const queue = {
1810
+ enqueue(message) {
1811
+ messages.push(message);
1812
+ return Promise.resolve();
1813
+ },
1814
+ listen() {
1815
+ throw listenError;
1816
+ }
1817
+ };
1818
+ const capture = captureUnhandledRejections();
1819
+ const records = [];
1820
+ await configureQueueStartupWarningCapture(records);
1821
+ try {
1822
+ await new ContextImpl({
1823
+ data: void 0,
1824
+ federation: new FederationImpl({
1825
+ kv: new MemoryKvStore(),
1826
+ queue: { outbox: queue },
1827
+ documentLoaderFactory: () => mockDocumentLoader,
1828
+ contextLoaderFactory: () => mockDocumentLoader
1829
+ }),
1830
+ url: new URL("https://example.com/"),
1831
+ documentLoader: mockDocumentLoader,
1832
+ contextLoader: mockDocumentLoader
1833
+ }).sendActivity(sender, recipient, createActivity());
1834
+ assertEquals(messages.map((message) => message.type), ["outbox"]);
1835
+ await capture.settle();
1836
+ assertEquals(queueStartupWarnings(records, listenError).length, 1);
1837
+ assertEquals(capture.reasons, []);
1838
+ } finally {
1839
+ await capture.settle();
1840
+ capture.stop();
1841
+ await reset();
1842
+ }
1843
+ });
1844
+ await t.step("fanout", async () => {
1845
+ const listenError = /* @__PURE__ */ new TypeError("simulated fanout listen() failure");
1846
+ const messages = [];
1847
+ const outbox = {
1848
+ enqueue() {
1849
+ return Promise.resolve();
1850
+ },
1851
+ listen() {
1852
+ return Promise.resolve();
1853
+ }
1854
+ };
1855
+ const fanout = {
1856
+ enqueue(message) {
1857
+ messages.push(message);
1858
+ return Promise.resolve();
1859
+ },
1860
+ listen() {
1861
+ throw listenError;
1862
+ }
1863
+ };
1864
+ const capture = captureUnhandledRejections();
1865
+ const records = [];
1866
+ await configureQueueStartupWarningCapture(records);
1867
+ try {
1868
+ await new ContextImpl({
1869
+ data: void 0,
1870
+ federation: new FederationImpl({
1871
+ kv: new MemoryKvStore(),
1872
+ queue: {
1873
+ outbox,
1874
+ fanout
1875
+ },
1876
+ documentLoaderFactory: () => mockDocumentLoader,
1877
+ contextLoaderFactory: () => mockDocumentLoader
1878
+ }),
1879
+ url: new URL("https://example.com/"),
1880
+ documentLoader: mockDocumentLoader,
1881
+ contextLoader: mockDocumentLoader
1882
+ }).sendActivity(sender, recipient, createActivity(), { fanout: "force" });
1883
+ assertEquals(messages.map((message) => message.type), ["fanout"]);
1884
+ await capture.settle();
1885
+ assertEquals(queueStartupWarnings(records, listenError).length, 1);
1886
+ assertEquals(capture.reasons, []);
1887
+ } finally {
1888
+ await capture.settle();
1889
+ capture.stop();
1890
+ await reset();
1891
+ }
1892
+ });
1893
+ });
1578
1894
  test("FederationImpl.processQueuedTask()", async (t) => {
1579
1895
  await t.step("with MessageQueue having nativeRetrial", async () => {
1580
1896
  const kv = new MemoryKvStore();
@@ -4759,6 +5075,286 @@ test("ContextImpl.sendActivity() honors the private-address policy", async (t) =
4759
5075
  }
4760
5076
  });
4761
5077
  });
5078
+ function deferredSignal() {
5079
+ let resolve;
5080
+ return {
5081
+ promise: new Promise((r) => resolve = r),
5082
+ resolve
5083
+ };
5084
+ }
5085
+ test("ContextImpl.sendActivity() waits for sibling deliveries on failure", async () => {
5086
+ const failingInbox = "http://127.0.0.1:3001/inbox/fail";
5087
+ const slowInbox = "http://127.0.0.1:3002/inbox/slow";
5088
+ const failingEntered = deferredSignal();
5089
+ const slowEntered = deferredSignal();
5090
+ const slowCompleted = deferredSignal();
5091
+ let releaseSlow;
5092
+ const slowGate = new Promise((resolve) => releaseSlow = resolve);
5093
+ let slowStarted = false;
5094
+ let slowSettled = false;
5095
+ esm_default.spyGlobal();
5096
+ esm_default.post(failingInbox, () => {
5097
+ failingEntered.resolve();
5098
+ return new Response(null, { status: 500 });
5099
+ });
5100
+ esm_default.post(slowInbox, async () => {
5101
+ slowStarted = true;
5102
+ slowEntered.resolve();
5103
+ await slowGate;
5104
+ slowSettled = true;
5105
+ slowCompleted.resolve();
5106
+ return new Response(null, { status: 202 });
5107
+ });
5108
+ let observed;
5109
+ let released = false;
5110
+ try {
5111
+ const ctx = createFederation({
5112
+ kv: new MemoryKvStore(),
5113
+ allowPrivateAddress: true
5114
+ }).createContext(new URL("https://example.com/"));
5115
+ const activity = new Create({
5116
+ id: new URL("https://example.com/activity/fanout"),
5117
+ actor: new URL("https://example.com/person")
5118
+ });
5119
+ let outerSettled = false;
5120
+ let outerError;
5121
+ observed = ctx.sendActivity([{
5122
+ privateKey: ed25519PrivateKey,
5123
+ keyId: ed25519Multikey.id
5124
+ }], [new Person({
5125
+ id: new URL("https://example.com/recipients/failing"),
5126
+ inbox: new URL(failingInbox)
5127
+ }), new Person({
5128
+ id: new URL("https://example.com/recipients/slow"),
5129
+ inbox: new URL(slowInbox)
5130
+ })], activity, { immediate: true }).then(() => {
5131
+ outerSettled = true;
5132
+ }, (error) => {
5133
+ outerSettled = true;
5134
+ outerError = error;
5135
+ });
5136
+ await Promise.all([failingEntered.promise, slowEntered.promise]);
5137
+ await new Promise((resolve) => setTimeout(resolve, 0));
5138
+ assertFalse(outerSettled, "sendActivity() must wait for sibling deliveries before rejecting");
5139
+ releaseSlow();
5140
+ released = true;
5141
+ await observed;
5142
+ assert(slowSettled, "the delayed delivery should have settled");
5143
+ assertInstanceOf(outerError, SendActivityError);
5144
+ } finally {
5145
+ if (!released) releaseSlow();
5146
+ if (slowStarted) await slowCompleted.promise;
5147
+ await observed;
5148
+ esm_default.hardReset();
5149
+ }
5150
+ });
5151
+ test("InboxContextImpl.forwardActivity() waits for sibling deliveries on failure", async () => {
5152
+ const failingInbox = "http://127.0.0.1:3003/inbox/fail";
5153
+ const slowInbox = "http://127.0.0.1:3004/inbox/slow";
5154
+ const failingEntered = deferredSignal();
5155
+ const slowEntered = deferredSignal();
5156
+ const slowCompleted = deferredSignal();
5157
+ let releaseSlow;
5158
+ const slowGate = new Promise((resolve) => releaseSlow = resolve);
5159
+ let slowStarted = false;
5160
+ let slowSettled = false;
5161
+ esm_default.spyGlobal();
5162
+ esm_default.post(failingInbox, () => {
5163
+ failingEntered.resolve();
5164
+ return new Response(null, { status: 500 });
5165
+ });
5166
+ esm_default.post(slowInbox, async () => {
5167
+ slowStarted = true;
5168
+ slowEntered.resolve();
5169
+ await slowGate;
5170
+ slowSettled = true;
5171
+ slowCompleted.resolve();
5172
+ return new Response(null, { status: 202 });
5173
+ });
5174
+ let observed;
5175
+ let released = false;
5176
+ try {
5177
+ const federation = new FederationImpl({
5178
+ kv: new MemoryKvStore(),
5179
+ allowPrivateAddress: true
5180
+ });
5181
+ const activity = await signObject(new vocab.Create({
5182
+ id: new URL("https://example.com/activity/forward"),
5183
+ actor: new URL("https://example.com/person2")
5184
+ }), ed25519PrivateKey, ed25519Multikey.id, {
5185
+ contextLoader: mockDocumentLoader,
5186
+ documentLoader: mockDocumentLoader
5187
+ });
5188
+ const ctx = new InboxContextImpl(null, await activity.toJsonLd({ contextLoader: mockDocumentLoader }), activity.id?.href, getTypeId(activity).href, {
5189
+ data: void 0,
5190
+ federation,
5191
+ url: new URL("https://example.com/"),
5192
+ documentLoader: mockDocumentLoader,
5193
+ contextLoader: mockDocumentLoader
5194
+ });
5195
+ let outerSettled = false;
5196
+ let outerError;
5197
+ observed = ctx.forwardActivity([{
5198
+ privateKey: ed25519PrivateKey,
5199
+ keyId: ed25519Multikey.id
5200
+ }], [{
5201
+ id: new URL("https://example.com/recipients/failing"),
5202
+ inboxId: new URL(failingInbox)
5203
+ }, {
5204
+ id: new URL("https://example.com/recipients/slow"),
5205
+ inboxId: new URL(slowInbox)
5206
+ }], {
5207
+ immediate: true,
5208
+ skipIfUnsigned: true
5209
+ }).then(() => {
5210
+ outerSettled = true;
5211
+ }, (error) => {
5212
+ outerSettled = true;
5213
+ outerError = error;
5214
+ });
5215
+ await Promise.all([failingEntered.promise, slowEntered.promise]);
5216
+ await new Promise((resolve) => setTimeout(resolve, 0));
5217
+ assertFalse(outerSettled, "forwardActivity() must wait for sibling deliveries before rejecting");
5218
+ releaseSlow();
5219
+ released = true;
5220
+ await observed;
5221
+ assert(slowSettled, "the delayed delivery should have settled");
5222
+ assertInstanceOf(outerError, SendActivityError);
5223
+ } finally {
5224
+ if (!released) releaseSlow();
5225
+ if (slowStarted) await slowCompleted.promise;
5226
+ await observed;
5227
+ esm_default.hardReset();
5228
+ }
5229
+ });
5230
+ test("FederationImpl.sendActivity() drains deliveries when fan-out setup fails", async () => {
5231
+ const slowInbox = "http://127.0.0.1:3005/inbox/slow";
5232
+ const slowEntered = deferredSignal();
5233
+ const slowCompleted = deferredSignal();
5234
+ let releaseSlow;
5235
+ const slowGate = new Promise((resolve) => releaseSlow = resolve);
5236
+ let slowStarted = false;
5237
+ let slowSettled = false;
5238
+ esm_default.spyGlobal();
5239
+ esm_default.post(slowInbox, async () => {
5240
+ slowStarted = true;
5241
+ slowEntered.resolve();
5242
+ await slowGate;
5243
+ slowSettled = true;
5244
+ slowCompleted.resolve();
5245
+ return new Response(null, { status: 202 });
5246
+ });
5247
+ let observed;
5248
+ let released = false;
5249
+ try {
5250
+ const federation = new FederationImpl({
5251
+ kv: new MemoryKvStore(),
5252
+ allowPrivateAddress: true
5253
+ });
5254
+ const context = federation.createContext(new URL("https://example.com/"));
5255
+ const activity = new Create({
5256
+ id: new URL("https://example.com/activity/setup-failure"),
5257
+ actor: new URL("https://example.com/person")
5258
+ });
5259
+ let outerSettled = false;
5260
+ let outerError;
5261
+ observed = federation.sendActivity([{
5262
+ privateKey: ed25519PrivateKey,
5263
+ keyId: ed25519Multikey.id
5264
+ }], {
5265
+ [slowInbox]: {
5266
+ actorIds: ["https://example.com/recipients/slow"],
5267
+ sharedInbox: false
5268
+ },
5269
+ "not a url": {
5270
+ actorIds: ["https://example.com/recipients/invalid"],
5271
+ sharedInbox: false
5272
+ }
5273
+ }, activity, {
5274
+ context,
5275
+ immediate: true
5276
+ }).then(() => {
5277
+ outerSettled = true;
5278
+ }, (error) => {
5279
+ outerSettled = true;
5280
+ outerError = error;
5281
+ });
5282
+ await slowEntered.promise;
5283
+ await new Promise((resolve) => setTimeout(resolve, 0));
5284
+ assertFalse(outerSettled, "sendActivity() must drain started deliveries when fan-out setup fails");
5285
+ releaseSlow();
5286
+ released = true;
5287
+ await observed;
5288
+ assert(slowSettled, "the started delivery should have settled");
5289
+ assertInstanceOf(outerError, TypeError);
5290
+ } finally {
5291
+ if (!released) releaseSlow();
5292
+ if (slowStarted) await slowCompleted.promise;
5293
+ await observed;
5294
+ esm_default.hardReset();
5295
+ }
5296
+ });
5297
+ test("FederationImpl.sendActivity() handles rejections while building headers", async () => {
5298
+ const federation = new FederationImpl({ kv: new MemoryKvStore() });
5299
+ const context = federation.createContext(new URL("https://example.com/"));
5300
+ const activity = await signObject(new Create({
5301
+ id: new URL("https://example.com/activity/header-failure"),
5302
+ actor: new URL("https://example.com/person")
5303
+ }), ed25519PrivateKey, ed25519Multikey.id, {
5304
+ contextLoader: mockDocumentLoader,
5305
+ documentLoader: mockDocumentLoader
5306
+ });
5307
+ const subtle = crypto.subtle;
5308
+ const originalDigest = subtle.digest;
5309
+ let digestCalls = 0;
5310
+ let releaseSecondDigest;
5311
+ const secondDigestGate = new Promise((resolve) => {
5312
+ releaseSecondDigest = resolve;
5313
+ });
5314
+ let secondDigestStarted;
5315
+ const secondDigestEntered = new Promise((resolve) => {
5316
+ secondDigestStarted = resolve;
5317
+ });
5318
+ subtle.digest = async (algorithm, data) => {
5319
+ digestCalls++;
5320
+ if (digestCalls === 2) {
5321
+ secondDigestStarted();
5322
+ await secondDigestGate;
5323
+ }
5324
+ return await originalDigest.call(subtle, algorithm, data);
5325
+ };
5326
+ const capture = captureUnhandledRejections();
5327
+ let released = false;
5328
+ try {
5329
+ const observed = federation.sendActivity([{
5330
+ privateKey: ed25519PrivateKey,
5331
+ keyId: ed25519Multikey.id
5332
+ }], {
5333
+ "ftp://example.com/fail": {
5334
+ actorIds: ["https://example.com/recipients/failing"],
5335
+ sharedInbox: false
5336
+ },
5337
+ "http://127.0.0.1:3006/inbox": {
5338
+ actorIds: ["https://example.com/recipients/slow"],
5339
+ sharedInbox: false
5340
+ }
5341
+ }, activity, {
5342
+ context,
5343
+ immediate: true,
5344
+ collectionSync: "https://example.com/followers"
5345
+ }).catch((error) => error);
5346
+ await secondDigestEntered;
5347
+ await capture.settle();
5348
+ assertEquals(capture.reasons, [], "an early rejection must not surface as an unhandled rejection");
5349
+ releaseSecondDigest();
5350
+ released = true;
5351
+ await observed;
5352
+ } finally {
5353
+ if (!released) releaseSecondDigest();
5354
+ subtle.digest = originalDigest;
5355
+ capture.stop();
5356
+ }
5357
+ });
4762
5358
  test("Federation.fetch() bounds inbox bodies before dispatch", async () => {
4763
5359
  let dispatched = false;
4764
5360
  const json = JSON.stringify({
@@ -2,7 +2,7 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("../chunk-DDcVe30Y.cjs");
5
- const require_middleware = require("../middleware-CXoG7ZAW.cjs");
5
+ const require_middleware = require("../middleware-_yMwRAE9.cjs");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  //#region src/federation/kv.ts
8
8
  /**
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { $ as AuthorizePredicate, A as CreateExponentialBackoffPolicyOptions, B as RespondWithObjectOptions, C as InboxListenerSetters, D as FederationOrigin, E as FederationKvPrefixes, F as createFederationBuilder, G as MessageQueue, H as respondWithObjectIfAcceptable, I as Router, J as ParallelMessageQueue, K as MessageQueueEnqueueOptions, L as RouterError, M as RetryPolicy, N as createExponentialBackoffPolicy, O as FederationQueueOptions, P as Message, Q as ActorKeyPairsDispatcher, R as RouterOptions, S as IdempotencyStrategy, T as Rfc6570Expression, U as InProcessMessageQueue, V as respondWithObject, W as InProcessMessageQueueOptions, X as ActorDispatcher, Y as ActorAliasMapper, Z as ActorHandleMapper, _ as FederationBuilder, _t as PageItems, a as InboxContext, at as CustomCollectionDispatcher, b as FederationStartQueueOptions, c as RouteActivityOptions, ct as NodeInfoDispatcher, d as ActorCallbackSetters, dt as OutboxErrorHandler, et as CollectionCounter, f as CollectionCallbackSetters, ft as OutboxPermanentFailureHandler, g as Federation, gt as SenderKeyPair, h as Federatable, ht as SendActivityError, i as GetSignedKeyOptions, it as CustomCollectionCursor, j as RetryContext, k as createFederation, l as SendActivityOptions, lt as ObjectAuthorizePredicate, m as CustomCollectionCallbackSetters, mt as WebFingerLinksDispatcher, n as Context, nt as CollectionDispatcher, o as ParseUriResult, ot as InboxErrorHandler, p as ConstructorWithTypeId, pt as SharedInboxKeyDispatcher, q as MessageQueueListenOptions, r as ForwardActivityOptions, rt as CustomCollectionCounter, s as RequestContext, st as InboxListener, t as ActorKeyPair, tt as CollectionCursor, u as SendActivityOptionsForCollection, ut as ObjectDispatcher, v as FederationFetchOptions, vt as buildCollectionSynchronizationHeader, w as ObjectCallbackSetters, x as IdempotencyKeyCallback, y as FederationOptions, yt as digest, z as RouterRouteResult } from "../context-CnoWxuoB.cjs";
2
+ import { $ as AuthorizePredicate, A as CreateExponentialBackoffPolicyOptions, B as RespondWithObjectOptions, C as InboxListenerSetters, D as FederationOrigin, E as FederationKvPrefixes, F as createFederationBuilder, G as MessageQueue, H as respondWithObjectIfAcceptable, I as Router, J as ParallelMessageQueue, K as MessageQueueEnqueueOptions, L as RouterError, M as RetryPolicy, N as createExponentialBackoffPolicy, O as FederationQueueOptions, P as Message, Q as ActorKeyPairsDispatcher, R as RouterOptions, S as IdempotencyStrategy, T as Rfc6570Expression, U as InProcessMessageQueue, V as respondWithObject, W as InProcessMessageQueueOptions, X as ActorDispatcher, Y as ActorAliasMapper, Z as ActorHandleMapper, _ as FederationBuilder, _t as PageItems, a as InboxContext, at as CustomCollectionDispatcher, b as FederationStartQueueOptions, c as RouteActivityOptions, ct as NodeInfoDispatcher, d as ActorCallbackSetters, dt as OutboxErrorHandler, et as CollectionCounter, f as CollectionCallbackSetters, ft as OutboxPermanentFailureHandler, g as Federation, gt as SenderKeyPair, h as Federatable, ht as SendActivityError, i as GetSignedKeyOptions, it as CustomCollectionCursor, j as RetryContext, k as createFederation, l as SendActivityOptions, lt as ObjectAuthorizePredicate, m as CustomCollectionCallbackSetters, mt as WebFingerLinksDispatcher, n as Context, nt as CollectionDispatcher, o as ParseUriResult, ot as InboxErrorHandler, p as ConstructorWithTypeId, pt as SharedInboxKeyDispatcher, q as MessageQueueListenOptions, r as ForwardActivityOptions, rt as CustomCollectionCounter, s as RequestContext, st as InboxListener, t as ActorKeyPair, tt as CollectionCursor, u as SendActivityOptionsForCollection, ut as ObjectDispatcher, v as FederationFetchOptions, vt as buildCollectionSynchronizationHeader, w as ObjectCallbackSetters, x as IdempotencyKeyCallback, y as FederationOptions, yt as digest, z as RouterRouteResult } from "../context-ao-qb3eP.cjs";
3
3
  import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "../kv-CZKl1IUS.cjs";
4
- import { n as handleWebFinger, t as WebFingerHandlerParameters } from "../mod-w1oyHseQ.cjs";
4
+ import { n as handleWebFinger, t as WebFingerHandlerParameters } from "../mod-6uJWdCc0.cjs";
5
5
  export { ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, KvKey, KvStore, KvStoreListEntry, KvStoreSetOptions, MemoryKvStore, Message, MessageQueue, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxErrorHandler, OutboxPermanentFailureHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityError, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, WebFingerHandlerParameters, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, handleWebFinger, respondWithObject, respondWithObjectIfAcceptable };
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { $ as AuthorizePredicate, A as CreateExponentialBackoffPolicyOptions, B as RespondWithObjectOptions, C as InboxListenerSetters, D as FederationOrigin, E as FederationKvPrefixes, F as createFederationBuilder, G as MessageQueue, H as respondWithObjectIfAcceptable, I as Router, J as ParallelMessageQueue, K as MessageQueueEnqueueOptions, L as RouterError, M as RetryPolicy, N as createExponentialBackoffPolicy, O as FederationQueueOptions, P as Message, Q as ActorKeyPairsDispatcher, R as RouterOptions, S as IdempotencyStrategy, T as Rfc6570Expression, U as InProcessMessageQueue, V as respondWithObject, W as InProcessMessageQueueOptions, X as ActorDispatcher, Y as ActorAliasMapper, Z as ActorHandleMapper, _ as FederationBuilder, _t as PageItems, a as InboxContext, at as CustomCollectionDispatcher, b as FederationStartQueueOptions, c as RouteActivityOptions, ct as NodeInfoDispatcher, d as ActorCallbackSetters, dt as OutboxErrorHandler, et as CollectionCounter, f as CollectionCallbackSetters, ft as OutboxPermanentFailureHandler, g as Federation, gt as SenderKeyPair, h as Federatable, ht as SendActivityError, i as GetSignedKeyOptions, it as CustomCollectionCursor, j as RetryContext, k as createFederation, l as SendActivityOptions, lt as ObjectAuthorizePredicate, m as CustomCollectionCallbackSetters, mt as WebFingerLinksDispatcher, n as Context, nt as CollectionDispatcher, o as ParseUriResult, ot as InboxErrorHandler, p as ConstructorWithTypeId, pt as SharedInboxKeyDispatcher, q as MessageQueueListenOptions, r as ForwardActivityOptions, rt as CustomCollectionCounter, s as RequestContext, st as InboxListener, t as ActorKeyPair, tt as CollectionCursor, u as SendActivityOptionsForCollection, ut as ObjectDispatcher, v as FederationFetchOptions, vt as buildCollectionSynchronizationHeader, w as ObjectCallbackSetters, x as IdempotencyKeyCallback, y as FederationOptions, yt as digest, z as RouterRouteResult } from "../context-lxjaUTAI.js";
2
+ import { $ as AuthorizePredicate, A as CreateExponentialBackoffPolicyOptions, B as RespondWithObjectOptions, C as InboxListenerSetters, D as FederationOrigin, E as FederationKvPrefixes, F as createFederationBuilder, G as MessageQueue, H as respondWithObjectIfAcceptable, I as Router, J as ParallelMessageQueue, K as MessageQueueEnqueueOptions, L as RouterError, M as RetryPolicy, N as createExponentialBackoffPolicy, O as FederationQueueOptions, P as Message, Q as ActorKeyPairsDispatcher, R as RouterOptions, S as IdempotencyStrategy, T as Rfc6570Expression, U as InProcessMessageQueue, V as respondWithObject, W as InProcessMessageQueueOptions, X as ActorDispatcher, Y as ActorAliasMapper, Z as ActorHandleMapper, _ as FederationBuilder, _t as PageItems, a as InboxContext, at as CustomCollectionDispatcher, b as FederationStartQueueOptions, c as RouteActivityOptions, ct as NodeInfoDispatcher, d as ActorCallbackSetters, dt as OutboxErrorHandler, et as CollectionCounter, f as CollectionCallbackSetters, ft as OutboxPermanentFailureHandler, g as Federation, gt as SenderKeyPair, h as Federatable, ht as SendActivityError, i as GetSignedKeyOptions, it as CustomCollectionCursor, j as RetryContext, k as createFederation, l as SendActivityOptions, lt as ObjectAuthorizePredicate, m as CustomCollectionCallbackSetters, mt as WebFingerLinksDispatcher, n as Context, nt as CollectionDispatcher, o as ParseUriResult, ot as InboxErrorHandler, p as ConstructorWithTypeId, pt as SharedInboxKeyDispatcher, q as MessageQueueListenOptions, r as ForwardActivityOptions, rt as CustomCollectionCounter, s as RequestContext, st as InboxListener, t as ActorKeyPair, tt as CollectionCursor, u as SendActivityOptionsForCollection, ut as ObjectDispatcher, v as FederationFetchOptions, vt as buildCollectionSynchronizationHeader, w as ObjectCallbackSetters, x as IdempotencyKeyCallback, y as FederationOptions, yt as digest, z as RouterRouteResult } from "../context-C00pWP8x.js";
3
3
  import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "../kv-CxaI4eGo.js";
4
- import { n as handleWebFinger, t as WebFingerHandlerParameters } from "../mod-I-34OMN3.js";
4
+ import { n as handleWebFinger, t as WebFingerHandlerParameters } from "../mod-DtRh015i.js";
5
5
  export { ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, KvKey, KvStore, KvStoreListEntry, KvStoreSetOptions, MemoryKvStore, Message, MessageQueue, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxErrorHandler, OutboxPermanentFailureHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityError, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, WebFingerHandlerParameters, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, handleWebFinger, respondWithObject, respondWithObjectIfAcceptable };
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-BKs_a9P1.js";
3
+ import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-D-DIq_jJ.js";
4
4
  import { isEqual } from "es-toolkit";
5
5
  //#region src/federation/kv.ts
6
6
  /**