@fedify/fedify 2.3.9-dev.2048 → 2.3.10

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 (85) hide show
  1. package/dist/{builder-BfY2wZ4V.mjs → builder-Bal9bfuM.mjs} +10 -2
  2. package/dist/{client-DYDoDpMa.mjs → client-Ccbsek6u.mjs} +7 -3
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{context-CAKnQy1x.d.ts → context-BWbdE_MZ.d.ts} +1 -1
  7. package/dist/{context-BepjCFHN.mjs → context-CxbCIptr.mjs} +2 -1
  8. package/dist/{context-CSIggG9_.d.cts → context-MOrCC7yl.d.cts} +1 -1
  9. package/dist/{deno-ayal9buz.mjs → deno-DUgFsCmM.mjs} +1 -1
  10. package/dist/docloader-w72ipM49.mjs +79 -0
  11. package/dist/federation/builder.test.mjs +38 -2
  12. package/dist/federation/circuit-breaker.test.mjs +1 -1
  13. package/dist/federation/collection.test.mjs +1 -1
  14. package/dist/federation/handler.test.mjs +7 -7
  15. package/dist/federation/idempotency.test.mjs +3 -3
  16. package/dist/federation/kv.test.mjs +1 -1
  17. package/dist/federation/metrics.test.mjs +2 -2
  18. package/dist/federation/middleware.test.mjs +302 -8
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +3 -3
  21. package/dist/federation/mod.d.ts +3 -3
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/mq.test.mjs +98 -43
  24. package/dist/federation/negotiation.test.mjs +1 -1
  25. package/dist/federation/router.test.mjs +1 -1
  26. package/dist/federation/send.test.mjs +85 -4
  27. package/dist/federation/temporal.test.mjs +2 -2
  28. package/dist/federation/webfinger.test.mjs +3 -3
  29. package/dist/{http-B2u6PvNX.mjs → http-BNDHRNJP.mjs} +8 -4
  30. package/dist/{http-BPPCh0xN.js → http-BY8PpQzP.js} +6 -2
  31. package/dist/{http-BGP2QCbe.cjs → http-CTYio50W.cjs} +6 -2
  32. package/dist/{key-C9DkrWwb.mjs → key-BbuFo4CW.mjs} +2 -2
  33. package/dist/{kv-D6hNiMTK.d.ts → kv-CZKl1IUS.d.cts} +3 -2
  34. package/dist/{kv-gJ8LYbxX.d.cts → kv-CxaI4eGo.d.ts} +3 -2
  35. package/dist/{kv-cache-qJSjfLGm.mjs → kv-cache-CNZOaZYq.mjs} +1 -1
  36. package/dist/{kv-cache-C1TC_e9y.cjs → kv-cache-CrcqGkT-.cjs} +34 -8
  37. package/dist/{kv-cache-Cl1q1aA9.js → kv-cache-DNnjnc9t.js} +35 -9
  38. package/dist/{ld-DNl7KwLY.mjs → ld-BYV2Luge.mjs} +4 -4
  39. package/dist/{metrics-Ce55disp.mjs → metrics-QrVsKSG9.mjs} +1 -1
  40. package/dist/{middleware-C96wm_7x.mjs → middleware-54KSGAQA.mjs} +1 -1
  41. package/dist/{middleware-C_utyVa_.js → middleware-B4FNnN3W.js} +36 -11
  42. package/dist/{middleware-BCG2BX1n.mjs → middleware-Bj9s3J_0.mjs} +22 -19
  43. package/dist/{middleware-BZojifnF.cjs → middleware-Dv-L7Plm.cjs} +36 -11
  44. package/dist/{mod-PVR75MeU.d.cts → mod-B6PwkUMU.d.cts} +1 -1
  45. package/dist/{mod-DHgc4ygG.d.ts → mod-Br8yJNcE.d.ts} +3 -1
  46. package/dist/{mod-1TuoXkGs.d.cts → mod-Co1M3Ux0.d.cts} +3 -1
  47. package/dist/{mod-CFuyfw3c.d.ts → mod-LmD03uRA.d.ts} +1 -1
  48. package/dist/mod.cjs +5 -5
  49. package/dist/mod.d.cts +4 -4
  50. package/dist/mod.d.ts +4 -4
  51. package/dist/mod.js +5 -5
  52. package/dist/nodeinfo/client.test.mjs +50 -2
  53. package/dist/nodeinfo/handler.test.mjs +3 -3
  54. package/dist/nodeinfo/mod.cjs +1 -1
  55. package/dist/nodeinfo/mod.js +1 -1
  56. package/dist/nodeinfo/types.test.mjs +1 -1
  57. package/dist/otel/exporter.test.mjs +1 -1
  58. package/dist/otel/mod.d.cts +1 -1
  59. package/dist/otel/mod.d.ts +1 -1
  60. package/dist/{owner-DqqDBw-l.mjs → owner-BsBzJADO.mjs} +2 -2
  61. package/dist/{proof-Dz9ObvUq.js → proof-BxS2Xa8B.js} +2 -2
  62. package/dist/{proof-I6YcKRRq.cjs → proof-CGKQFT2M.cjs} +2 -2
  63. package/dist/{proof-D5vP7VIN.mjs → proof-cXVkPBIU.mjs} +3 -3
  64. package/dist/{send-CBtR8DQK.mjs → send-omt23uTA.mjs} +18 -4
  65. package/dist/sig/http.test.mjs +43 -3
  66. package/dist/sig/key.test.mjs +2 -2
  67. package/dist/sig/ld.test.mjs +5 -2
  68. package/dist/sig/mod.cjs +2 -2
  69. package/dist/sig/mod.js +2 -2
  70. package/dist/sig/owner.test.mjs +2 -2
  71. package/dist/sig/proof.test.mjs +2 -2
  72. package/dist/{std__assert-BBjXFNOb.mjs → std__assert-BvGuO4xO.mjs} +2 -27
  73. package/dist/{temporal-B9t2tY9A.mjs → temporal-ZUAQe754.mjs} +1 -1
  74. package/dist/testing/mod.mjs +1 -1
  75. package/dist/{types-DWs-qZaY.cjs → types-DRPjmHVV.cjs} +7 -3
  76. package/dist/{types-sXITXcWa.js → types-miAYfiGO.js} +7 -3
  77. package/dist/utils/docloader.test.mjs +299 -3
  78. package/dist/utils/kv-cache.test.mjs +1 -1
  79. package/dist/utils/mod.cjs +1 -1
  80. package/dist/utils/mod.d.cts +1 -1
  81. package/dist/utils/mod.d.ts +1 -1
  82. package/dist/utils/mod.js +1 -1
  83. package/dist/vocab/cjs.test.mjs +1 -1
  84. package/package.json +7 -7
  85. package/dist/docloader-C-JrNL14.mjs +0 -53
@@ -3,21 +3,21 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-vrlUxr60.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
- import { r as assertExists } from "../std__assert-BBjXFNOb.mjs";
6
+ import { n as assertExists } from "../std__assert-BvGuO4xO.mjs";
7
7
  import { r as assertFalse, t as assertRejects } from "../assert_rejects-DN60FHPX.mjs";
8
8
  import { n as assertInstanceOf, t as assert } from "../assert-jSdZMYam.mjs";
9
9
  import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";
10
10
  import { t as assertNotEquals } from "../assert_not_equals-DkVK8oqV.mjs";
11
11
  import { t as assertStrictEquals } from "../assert_strict_equals-XEgZAlrj.mjs";
12
- import { g as recordInboxActivity } from "../metrics-Ce55disp.mjs";
13
- import { l as verifyRequest, s as signRequest } from "../http-B2u6PvNX.mjs";
12
+ import { g as recordInboxActivity } from "../metrics-QrVsKSG9.mjs";
13
+ import { l as verifyRequest, s as signRequest } from "../http-BNDHRNJP.mjs";
14
14
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
15
- import { t as getAuthenticatedDocumentLoader } from "../docloader-C-JrNL14.mjs";
16
- import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-DNl7KwLY.mjs";
17
- import { t as doesActorOwnKey } from "../owner-DqqDBw-l.mjs";
18
- import { i as verifyObject, r as signObject } from "../proof-D5vP7VIN.mjs";
15
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-w72ipM49.mjs";
16
+ import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-BYV2Luge.mjs";
17
+ import { t as doesActorOwnKey } from "../owner-BsBzJADO.mjs";
18
+ import { i as verifyObject, r as signObject } from "../proof-cXVkPBIU.mjs";
19
19
  import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
20
- import { g as handleBenchmarkTrigger, i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-BCG2BX1n.mjs";
20
+ import { g as handleBenchmarkTrigger, i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-Bj9s3J_0.mjs";
21
21
  import { t as CircuitBreaker } from "../circuit-breaker-CDABzqVf.mjs";
22
22
  import { t as markCircuitBreakerLegacySweepDone } from "../circuit-breaker-test-utils-Dm5MM6l9.mjs";
23
23
  import { configure, reset } from "@logtape/logtape";
@@ -27,6 +27,7 @@ import { Create, Offer, Person, getTypeId, lookupObject } from "@fedify/vocab";
27
27
  import { SpanStatusCode, metrics } from "@opentelemetry/api";
28
28
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
29
29
  import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
30
+ import dns from "node:dns/promises";
30
31
  import serialize from "json-canon";
31
32
  import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
32
33
  //#region ../fixture/src/fixtures/example.com/create.json
@@ -1168,6 +1169,31 @@ test({
1168
1169
  esm_default.hardReset();
1169
1170
  }
1170
1171
  });
1172
+ test("Federation.fetch() [rfc9421] multiple POST signatures", async (t) => {
1173
+ for (const validSecond of [true, false]) await t.step(validSecond ? "valid second signature" : "both signatures invalid", async () => {
1174
+ const inbox = [];
1175
+ const federation = createFederation({
1176
+ kv: new MemoryKvStore(),
1177
+ documentLoaderFactory: () => mockDocumentLoader,
1178
+ contextLoaderFactory: () => mockDocumentLoader
1179
+ });
1180
+ federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => new vocab.Person({ id: ctx.getActorUri(identifier) }));
1181
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, (_ctx, activity) => {
1182
+ inbox.push(activity.id.href);
1183
+ });
1184
+ const signed = await signRequest(new Request("https://example.com/inbox", {
1185
+ method: "POST",
1186
+ headers: { "Content-Type": "application/activity+json" },
1187
+ body: JSON.stringify(create_default)
1188
+ }), rsaPrivateKey2, rsaPublicKey2.id, { spec: "rfc9421" });
1189
+ const input = signed.headers.get("Signature-Input");
1190
+ const signature = signed.headers.get("Signature");
1191
+ signed.headers.set("Signature-Input", `${input}, ${input.replace(/^sig1=/, "sig2=")}`);
1192
+ signed.headers.set("Signature", `sig1=:AAAAAA==:, ${validSecond ? signature.replace(/^sig1=/, "sig2=") : "sig2=:AAAAAA==:"}`);
1193
+ assertEquals((await federation.fetch(signed, { contextData: void 0 })).status, validSecond ? 202 : 401);
1194
+ assertEquals(inbox, validSecond ? [id$2] : []);
1195
+ });
1196
+ });
1171
1197
  test("Federation.fetch()", async (t) => {
1172
1198
  esm_default.spyGlobal();
1173
1199
  esm_default.get("https://example.com/key2", {
@@ -2655,6 +2681,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
2655
2681
  const kv = new MemoryKvStore();
2656
2682
  const queuedMessages = [];
2657
2683
  const federation = new FederationImpl({
2684
+ allowPrivateAddress: true,
2658
2685
  kv,
2659
2686
  queue: {
2660
2687
  nativeRetrial: true,
@@ -2717,6 +2744,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
2717
2744
  const kv = new MemoryKvStore();
2718
2745
  let queuedMessages = [];
2719
2746
  const federation = new FederationImpl({
2747
+ allowPrivateAddress: true,
2720
2748
  kv,
2721
2749
  queue: {
2722
2750
  enqueue(message, _options) {
@@ -4484,6 +4512,157 @@ test("FederationImpl.processQueuedTask()", async (t) => {
4484
4512
  assertEquals(queuedMessages, []);
4485
4513
  });
4486
4514
  });
4515
+ test({
4516
+ name: "FederationImpl.processQueuedTask() accounts for destination validation failures",
4517
+ ignore: "Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted",
4518
+ async fn(t) {
4519
+ for (const result of [
4520
+ "throws",
4521
+ "empty",
4522
+ "private"
4523
+ ]) for (const failureThreshold of [1, 5]) for (const redirected of [false, true]) await t.step(`${result}, threshold: ${failureThreshold}, redirected: ${redirected}`, async () => {
4524
+ const destination = "https://delivery.invalid/inbox";
4525
+ const publicInbox = "https://8.8.8.8/inbox";
4526
+ const queuedMessages = [];
4527
+ const delays = [];
4528
+ const errors = [];
4529
+ const activities = [];
4530
+ const attempts = [];
4531
+ const delay = Temporal.Duration.from({ seconds: 5 });
4532
+ const kv = new MemoryKvStore();
4533
+ await markCircuitBreakerLegacySweepDone(kv);
4534
+ const [meterProvider, recorder] = createTestMeterProvider();
4535
+ const recoveryDelay = Temporal.Duration.from({ minutes: 30 });
4536
+ const federation = new FederationImpl({
4537
+ kv,
4538
+ meterProvider,
4539
+ circuitBreaker: {
4540
+ failureThreshold,
4541
+ recoveryDelay
4542
+ },
4543
+ queue: {
4544
+ enqueue(message, options) {
4545
+ queuedMessages.push(message);
4546
+ delays.push(options?.delay);
4547
+ return Promise.resolve();
4548
+ },
4549
+ listen() {
4550
+ return Promise.resolve();
4551
+ }
4552
+ },
4553
+ documentLoaderFactory: () => mockDocumentLoader,
4554
+ contextLoaderFactory: () => mockDocumentLoader,
4555
+ onOutboxError(error, activity) {
4556
+ errors.push(error);
4557
+ activities.push(activity);
4558
+ },
4559
+ outboxRetryPolicy(options) {
4560
+ attempts.push(options.attempts);
4561
+ return delay;
4562
+ }
4563
+ });
4564
+ const message = {
4565
+ type: "outbox",
4566
+ id: crypto.randomUUID(),
4567
+ baseUrl: "https://example.com",
4568
+ keys: [],
4569
+ activity: {
4570
+ "@context": "https://www.w3.org/ns/activitystreams",
4571
+ type: "Create",
4572
+ id: "https://example.com/activities/dns-failure",
4573
+ actor: "https://example.com/users/alice",
4574
+ object: {
4575
+ type: "Note",
4576
+ content: "test"
4577
+ }
4578
+ },
4579
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
4580
+ inbox: redirected ? publicInbox : destination,
4581
+ sharedInbox: false,
4582
+ started: (/* @__PURE__ */ new Date()).toISOString(),
4583
+ attempt: 0,
4584
+ headers: {},
4585
+ traceContext: {}
4586
+ };
4587
+ const originalLookup = dns.lookup;
4588
+ const resolverError = /* @__PURE__ */ new Error("Resolver unavailable");
4589
+ const lookups = [];
4590
+ dns.lookup = ((hostname) => {
4591
+ lookups.push(hostname);
4592
+ assertEquals(hostname, "delivery.invalid");
4593
+ if (result === "throws") return Promise.reject(resolverError);
4594
+ return Promise.resolve(result === "empty" ? [] : [{
4595
+ address: "127.0.0.1",
4596
+ family: 4
4597
+ }]);
4598
+ });
4599
+ try {
4600
+ esm_default.mockGlobal().catch(202);
4601
+ if (redirected) esm_default.route(publicInbox, {
4602
+ status: 307,
4603
+ headers: { Location: destination }
4604
+ });
4605
+ await federation.processQueuedTask(void 0, message);
4606
+ assertEquals(errors.length, 1);
4607
+ const error = errors[0];
4608
+ if (result === "private") {
4609
+ assertInstanceOf(error, UrlError);
4610
+ assertEquals(error.reason, "disallowed");
4611
+ } else {
4612
+ assertInstanceOf(error, FetchError);
4613
+ assertEquals(error.url.href, destination);
4614
+ assertInstanceOf(error.cause, UrlError);
4615
+ assertEquals(error.cause.reason, "dns");
4616
+ assertStrictEquals(error.cause.cause, result === "throws" ? resolverError : void 0);
4617
+ }
4618
+ assertEquals(activities.length, 1);
4619
+ assertInstanceOf(activities[0], Create);
4620
+ assertEquals(activities[0].id?.href, "https://example.com/activities/dns-failure");
4621
+ assertEquals(attempts, [0]);
4622
+ const host = new URL(message.inbox).host;
4623
+ const state = await kv.get([
4624
+ "_fedify",
4625
+ "circuit",
4626
+ host
4627
+ ]);
4628
+ if (result === "private") assertEquals(state, void 0);
4629
+ else {
4630
+ assertExists(state);
4631
+ assertEquals(state.failures.length, 1);
4632
+ assertEquals(state.state, failureThreshold === 1 ? "open" : "closed");
4633
+ }
4634
+ if (result !== "private" && failureThreshold === 1) {
4635
+ assertEquals(queuedMessages.length, 1);
4636
+ const held = queuedMessages[0];
4637
+ assertEquals(held.attempt, 0);
4638
+ assertEquals(held.circuitHeld, true);
4639
+ assertExists(held.circuitHeldSince);
4640
+ assertEquals(delays, [recoveryDelay]);
4641
+ } else {
4642
+ assertEquals(queuedMessages, [{
4643
+ ...message,
4644
+ attempt: 1
4645
+ }]);
4646
+ assertEquals(delays, [delay]);
4647
+ }
4648
+ const expectedCount = result === "private" && !redirected ? 0 : 1;
4649
+ const sent = recorder.getMeasurements("activitypub.delivery.sent");
4650
+ assertEquals(sent.length, expectedCount);
4651
+ if (expectedCount === 1) {
4652
+ assertEquals(sent[0].value, 1);
4653
+ assertEquals(sent[0].attributes["activitypub.delivery.success"], false);
4654
+ assertEquals(sent[0].attributes["activitypub.remote.host"], host);
4655
+ }
4656
+ assertEquals(lookups, ["delivery.invalid"]);
4657
+ assertEquals(esm_default.callHistory.calls(destination).length, 0);
4658
+ assertEquals(esm_default.callHistory.calls().length, redirected ? 1 : 0);
4659
+ } finally {
4660
+ dns.lookup = originalLookup;
4661
+ esm_default.hardReset();
4662
+ }
4663
+ });
4664
+ }
4665
+ });
4487
4666
  test("FederationImpl.processQueuedTask() permanent failure", async (t) => {
4488
4667
  esm_default.spyGlobal();
4489
4668
  esm_default.post("https://gone.example/inbox", {
@@ -6656,6 +6835,80 @@ test({
6656
6835
  assertEquals(rejected[1].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Offer");
6657
6836
  }
6658
6837
  });
6838
+ for (const queued of [false, true]) test(`ContextImpl.routeActivity() routes the fetched document (queued: ${queued})`, async () => {
6839
+ const id = new URL("https://example.com/verified-create");
6840
+ const genuine = new Create({
6841
+ id,
6842
+ actor: new URL("https://example.com/person"),
6843
+ object: new vocab.Note({ content: "Genuine content" }),
6844
+ to: new URL("https://example.com/recipient")
6845
+ });
6846
+ const genuineJson = await genuine.toJsonLd({ contextLoader: mockDocumentLoader });
6847
+ const forged = new Create({
6848
+ id,
6849
+ actor: new URL("https://victim.example/actor"),
6850
+ object: new vocab.Note({ content: "Forged content" }),
6851
+ to: new URL("https://attacker.example/recipient")
6852
+ });
6853
+ const messages = [];
6854
+ const queue = {
6855
+ enqueue(message) {
6856
+ messages.push(message);
6857
+ return Promise.resolve();
6858
+ },
6859
+ async listen() {}
6860
+ };
6861
+ const federation = createFederation({
6862
+ kv: new MemoryKvStore(),
6863
+ documentLoaderFactory: () => async (url, options) => url === id.href ? {
6864
+ document: genuineJson,
6865
+ documentUrl: id.href,
6866
+ contextUrl: null
6867
+ } : await mockDocumentLoader(url, options),
6868
+ contextLoaderFactory: () => mockDocumentLoader,
6869
+ queue: {
6870
+ inbox: queued ? queue : void 0,
6871
+ outbox: queue
6872
+ },
6873
+ manuallyStartQueue: true
6874
+ });
6875
+ const received = [];
6876
+ const contextDocuments = [];
6877
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, async (ctx, activity) => {
6878
+ received.push(await activity.toJsonLd({ contextLoader: mockDocumentLoader }));
6879
+ assertInstanceOf(ctx, InboxContextImpl);
6880
+ contextDocuments.push(ctx.activity);
6881
+ await ctx.forwardActivity({
6882
+ privateKey: ed25519PrivateKey,
6883
+ keyId: ed25519Multikey.id
6884
+ }, {
6885
+ id: new URL("https://example.com/recipient"),
6886
+ inboxId: new URL("https://example.com/inbox")
6887
+ });
6888
+ });
6889
+ const ctx = federation.createContext(new URL("https://local.example/"));
6890
+ async function processInbox() {
6891
+ const inboxMessages = messages.filter((message) => message.type === "inbox");
6892
+ for (const message of inboxMessages) await federation.processQueuedTask(void 0, message);
6893
+ }
6894
+ assert(await ctx.routeActivity(null, forged));
6895
+ if (queued) {
6896
+ assertEquals(received, []);
6897
+ assertEquals(messages.length, 1);
6898
+ assertEquals(messages[0].activity, genuineJson);
6899
+ await processInbox();
6900
+ }
6901
+ assertEquals(received, [genuineJson]);
6902
+ assertEquals(contextDocuments, [genuineJson]);
6903
+ const forwarded = messages.filter((message) => message.type === "outbox");
6904
+ assertEquals(forwarded.length, 1);
6905
+ assertEquals(forwarded[0].activity, genuineJson);
6906
+ assert(await ctx.routeActivity(null, genuine));
6907
+ if (queued) await processInbox();
6908
+ assertEquals(received, [genuineJson]);
6909
+ assertEquals(contextDocuments, [genuineJson]);
6910
+ assertEquals(messages.filter((message) => message.type === "outbox").length, 1);
6911
+ });
6659
6912
  test("ContextImpl.routeActivity() marks queued signed activities as non-LDS", async () => {
6660
6913
  let queuedMessage = null;
6661
6914
  const federation = new FederationImpl({
@@ -6715,6 +6968,47 @@ test("ContextImpl.getCollectionUri()", () => {
6715
6968
  assertThrows(() => ctx.getCollectionUri(Symbol(notReg), values));
6716
6969
  assertThrows(() => ctx.getCollectionUri(Symbol.for(notReg), values));
6717
6970
  });
6971
+ test("symbol-named custom collections are served and parsed", async () => {
6972
+ const federation = createFederation({ kv: new MemoryKvStore() });
6973
+ const names = [Symbol("bookmarks"), Symbol("bookmarks")];
6974
+ const paths = ["bookmarks", "ordered-bookmarks"];
6975
+ const calls = [];
6976
+ federation.setCollectionDispatcher(names[0], vocab.Object, "/users/{identifier}/bookmarks", (_ctx, _values, cursor) => {
6977
+ calls.push([0, cursor]);
6978
+ return { items: [] };
6979
+ });
6980
+ federation.setOrderedCollectionDispatcher(names[1], vocab.Object, "/users/{identifier}/ordered-bookmarks", (_ctx, _values, cursor) => {
6981
+ calls.push([1, cursor]);
6982
+ return { items: [] };
6983
+ });
6984
+ const ctx = federation.createContext(new URL("https://example.com/"));
6985
+ for (const [index, name] of names.entries()) {
6986
+ const uri = ctx.getCollectionUri(name, { identifier: "alice" });
6987
+ assertEquals(uri.pathname, `/users/alice/${paths[index]}`);
6988
+ const parsed = ctx.parseUri(uri);
6989
+ assertEquals(parsed?.type, index === 0 ? "collection" : "orderedCollection");
6990
+ if (parsed?.type !== "collection" && parsed?.type !== "orderedCollection") throw new Error("Expected a custom collection URI");
6991
+ assertStrictEquals(parsed.name, name);
6992
+ assertStrictEquals(parsed.class, vocab.Object);
6993
+ assertEquals(parsed.typeId, vocab.Object.typeId);
6994
+ assertEquals(parsed.values, { identifier: "alice" });
6995
+ for (const cursor of [null, "next"]) {
6996
+ const pageUri = new URL(uri);
6997
+ if (cursor != null) pageUri.searchParams.set("cursor", cursor);
6998
+ const response = await federation.fetch(new Request(pageUri, { headers: { accept: "application/activity+json" } }), { contextData: void 0 });
6999
+ assertEquals(response.status, 200);
7000
+ const body = await response.json();
7001
+ assert(body !== null && typeof body === "object" && "type" in body);
7002
+ assertEquals(body.type, `${index === 0 ? "Collection" : "OrderedCollection"}${cursor == null ? "" : "Page"}`);
7003
+ }
7004
+ }
7005
+ assertEquals(calls, [
7006
+ [0, null],
7007
+ [0, "next"],
7008
+ [1, null],
7009
+ [1, "next"]
7010
+ ]);
7011
+ });
6718
7012
  test("InboxContextImpl.forwardActivity()", async (t) => {
6719
7013
  esm_default.spyGlobal();
6720
7014
  let verified = null;
@@ -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-BZojifnF.cjs");
5
+ const require_middleware = require("../middleware-Dv-L7Plm.cjs");
6
6
  let _logtape_logtape = require("@logtape/logtape");
7
7
  let _fedify_uri_template = require("@fedify/uri-template");
8
8
  let es_toolkit = require("es-toolkit");
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { $ as CustomCollectionCursor, A as Rfc6570Expression, At as parseCircuitBreakerKvState, B as createFederationBuilder, C as FederationStartQueueOptions, Ct as CircuitBreakerFailurePolicy, D as InboxListenerSetters, Dt as CircuitBreakerStateChange, E as InboxChallengePolicy, Et as CircuitBreakerState, F as CreateExponentialBackoffPolicyOptions, G as ActorDispatcher, H as respondWithObject, I as RetryContext, J as AuthorizePredicate, K as ActorHandleMapper, L as RetryPolicy, M as FederationOrigin, N as FederationQueueOptions, O as ObjectCallbackSetters, Ot as NormalizedCircuitBreakerOptions, P as createFederation, Q as CustomCollectionCounter, R as createExponentialBackoffPolicy, S as FederationOptions, St as CircuitBreakerCreateOptions, T as IdempotencyStrategy, Tt as CircuitBreakerOptions, U as respondWithObjectIfAcceptable, V as RespondWithObjectOptions, W as ActorAliasMapper, X as CollectionCursor, Y as CollectionCounter, Z as CollectionDispatcher, _ as Federatable, _t as buildCollectionSynchronizationHeader, a as GetSignedKeyOptions, at as ObjectDispatcher, b as FederationBuilder, bt as CircuitBreakerActivityDrop, c as ParseUriResult, ct as OutboxListenerErrorHandler, d as SendActivityOptions, dt as UnverifiedActivityHandler, et as CustomCollectionDispatcher, f as SendActivityOptionsForCollection, ft as UnverifiedActivityReason, g as CustomCollectionCallbackSetters, gt as PageItems, h as ConstructorWithTypeId, ht as SenderKeyPair, i as GetActorOptions, it as ObjectAuthorizePredicate, j as FederationKvPrefixes, k as OutboxListenerSetters, kt as normalizeCircuitBreakerOptions, l as RequestContext, lt as OutboxPermanentFailureHandler, m as CollectionCallbackSetters, mt as SendActivityError, n as Context, nt as InboxListener, o as InboxContext, ot as OutboxErrorHandler, p as ActorCallbackSetters, pt as WebFingerLinksDispatcher, q as ActorKeyPairsDispatcher, r as ForwardActivityOptions, rt as NodeInfoDispatcher, s as OutboxContext, st as OutboxListener, t as ActorKeyPair, tt as InboxErrorHandler, u as RouteActivityOptions, ut as SharedInboxKeyDispatcher, v as Federation, vt as digest, w as IdempotencyKeyCallback, wt as CircuitBreakerKvState, x as FederationFetchOptions, xt as CircuitBreakerBeforeSendDecision, y as FederationBenchmarkOptions, yt as CircuitBreaker, z as Message } from "../context-CSIggG9_.cjs";
3
- import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "../kv-gJ8LYbxX.cjs";
2
+ import { $ as CustomCollectionCursor, A as Rfc6570Expression, At as parseCircuitBreakerKvState, B as createFederationBuilder, C as FederationStartQueueOptions, Ct as CircuitBreakerFailurePolicy, D as InboxListenerSetters, Dt as CircuitBreakerStateChange, E as InboxChallengePolicy, Et as CircuitBreakerState, F as CreateExponentialBackoffPolicyOptions, G as ActorDispatcher, H as respondWithObject, I as RetryContext, J as AuthorizePredicate, K as ActorHandleMapper, L as RetryPolicy, M as FederationOrigin, N as FederationQueueOptions, O as ObjectCallbackSetters, Ot as NormalizedCircuitBreakerOptions, P as createFederation, Q as CustomCollectionCounter, R as createExponentialBackoffPolicy, S as FederationOptions, St as CircuitBreakerCreateOptions, T as IdempotencyStrategy, Tt as CircuitBreakerOptions, U as respondWithObjectIfAcceptable, V as RespondWithObjectOptions, W as ActorAliasMapper, X as CollectionCursor, Y as CollectionCounter, Z as CollectionDispatcher, _ as Federatable, _t as buildCollectionSynchronizationHeader, a as GetSignedKeyOptions, at as ObjectDispatcher, b as FederationBuilder, bt as CircuitBreakerActivityDrop, c as ParseUriResult, ct as OutboxListenerErrorHandler, d as SendActivityOptions, dt as UnverifiedActivityHandler, et as CustomCollectionDispatcher, f as SendActivityOptionsForCollection, ft as UnverifiedActivityReason, g as CustomCollectionCallbackSetters, gt as PageItems, h as ConstructorWithTypeId, ht as SenderKeyPair, i as GetActorOptions, it as ObjectAuthorizePredicate, j as FederationKvPrefixes, k as OutboxListenerSetters, kt as normalizeCircuitBreakerOptions, l as RequestContext, lt as OutboxPermanentFailureHandler, m as CollectionCallbackSetters, mt as SendActivityError, n as Context, nt as InboxListener, o as InboxContext, ot as OutboxErrorHandler, p as ActorCallbackSetters, pt as WebFingerLinksDispatcher, q as ActorKeyPairsDispatcher, r as ForwardActivityOptions, rt as NodeInfoDispatcher, s as OutboxContext, st as OutboxListener, t as ActorKeyPair, tt as InboxErrorHandler, u as RouteActivityOptions, ut as SharedInboxKeyDispatcher, v as Federation, vt as digest, w as IdempotencyKeyCallback, wt as CircuitBreakerKvState, x as FederationFetchOptions, xt as CircuitBreakerBeforeSendDecision, y as FederationBenchmarkOptions, yt as CircuitBreaker, z as Message } from "../context-MOrCC7yl.cjs";
3
+ import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "../kv-CZKl1IUS.cjs";
4
4
  import { a as MessageQueueEnqueueOptions, i as MessageQueueDepth, n as InProcessMessageQueueOptions, o as MessageQueueListenOptions, r as MessageQueue, s as ParallelMessageQueue, t as InProcessMessageQueue } from "../mq-D8uSFzxe.cjs";
5
- import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "../mod-PVR75MeU.cjs";
5
+ import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "../mod-B6PwkUMU.cjs";
6
6
  export { ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CircuitBreaker, CircuitBreakerActivityDrop, CircuitBreakerBeforeSendDecision, CircuitBreakerCreateOptions, CircuitBreakerFailurePolicy, CircuitBreakerKvState, CircuitBreakerOptions, CircuitBreakerState, CircuitBreakerStateChange, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBenchmarkOptions, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetActorOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxChallengePolicy, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, KvKey, KvStore, KvStoreListEntry, KvStoreSetOptions, MemoryKvStore, Message, MessageQueue, MessageQueueDepth, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, NormalizedCircuitBreakerOptions, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxContext, OutboxErrorHandler, OutboxListener, OutboxListenerErrorHandler, OutboxListenerSetters, OutboxPermanentFailureHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityError, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, UnverifiedActivityHandler, UnverifiedActivityReason, WebFingerHandlerParameters, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, handleWebFinger, normalizeCircuitBreakerOptions, parseCircuitBreakerKvState, respondWithObject, respondWithObjectIfAcceptable };
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { $ as CustomCollectionCursor, A as Rfc6570Expression, At as parseCircuitBreakerKvState, B as createFederationBuilder, C as FederationStartQueueOptions, Ct as CircuitBreakerFailurePolicy, D as InboxListenerSetters, Dt as CircuitBreakerStateChange, E as InboxChallengePolicy, Et as CircuitBreakerState, F as CreateExponentialBackoffPolicyOptions, G as ActorDispatcher, H as respondWithObject, I as RetryContext, J as AuthorizePredicate, K as ActorHandleMapper, L as RetryPolicy, M as FederationOrigin, N as FederationQueueOptions, O as ObjectCallbackSetters, Ot as NormalizedCircuitBreakerOptions, P as createFederation, Q as CustomCollectionCounter, R as createExponentialBackoffPolicy, S as FederationOptions, St as CircuitBreakerCreateOptions, T as IdempotencyStrategy, Tt as CircuitBreakerOptions, U as respondWithObjectIfAcceptable, V as RespondWithObjectOptions, W as ActorAliasMapper, X as CollectionCursor, Y as CollectionCounter, Z as CollectionDispatcher, _ as Federatable, _t as buildCollectionSynchronizationHeader, a as GetSignedKeyOptions, at as ObjectDispatcher, b as FederationBuilder, bt as CircuitBreakerActivityDrop, c as ParseUriResult, ct as OutboxListenerErrorHandler, d as SendActivityOptions, dt as UnverifiedActivityHandler, et as CustomCollectionDispatcher, f as SendActivityOptionsForCollection, ft as UnverifiedActivityReason, g as CustomCollectionCallbackSetters, gt as PageItems, h as ConstructorWithTypeId, ht as SenderKeyPair, i as GetActorOptions, it as ObjectAuthorizePredicate, j as FederationKvPrefixes, k as OutboxListenerSetters, kt as normalizeCircuitBreakerOptions, l as RequestContext, lt as OutboxPermanentFailureHandler, m as CollectionCallbackSetters, mt as SendActivityError, n as Context, nt as InboxListener, o as InboxContext, ot as OutboxErrorHandler, p as ActorCallbackSetters, pt as WebFingerLinksDispatcher, q as ActorKeyPairsDispatcher, r as ForwardActivityOptions, rt as NodeInfoDispatcher, s as OutboxContext, st as OutboxListener, t as ActorKeyPair, tt as InboxErrorHandler, u as RouteActivityOptions, ut as SharedInboxKeyDispatcher, v as Federation, vt as digest, w as IdempotencyKeyCallback, wt as CircuitBreakerKvState, x as FederationFetchOptions, xt as CircuitBreakerBeforeSendDecision, y as FederationBenchmarkOptions, yt as CircuitBreaker, z as Message } from "../context-CAKnQy1x.js";
3
- import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "../kv-D6hNiMTK.js";
2
+ import { $ as CustomCollectionCursor, A as Rfc6570Expression, At as parseCircuitBreakerKvState, B as createFederationBuilder, C as FederationStartQueueOptions, Ct as CircuitBreakerFailurePolicy, D as InboxListenerSetters, Dt as CircuitBreakerStateChange, E as InboxChallengePolicy, Et as CircuitBreakerState, F as CreateExponentialBackoffPolicyOptions, G as ActorDispatcher, H as respondWithObject, I as RetryContext, J as AuthorizePredicate, K as ActorHandleMapper, L as RetryPolicy, M as FederationOrigin, N as FederationQueueOptions, O as ObjectCallbackSetters, Ot as NormalizedCircuitBreakerOptions, P as createFederation, Q as CustomCollectionCounter, R as createExponentialBackoffPolicy, S as FederationOptions, St as CircuitBreakerCreateOptions, T as IdempotencyStrategy, Tt as CircuitBreakerOptions, U as respondWithObjectIfAcceptable, V as RespondWithObjectOptions, W as ActorAliasMapper, X as CollectionCursor, Y as CollectionCounter, Z as CollectionDispatcher, _ as Federatable, _t as buildCollectionSynchronizationHeader, a as GetSignedKeyOptions, at as ObjectDispatcher, b as FederationBuilder, bt as CircuitBreakerActivityDrop, c as ParseUriResult, ct as OutboxListenerErrorHandler, d as SendActivityOptions, dt as UnverifiedActivityHandler, et as CustomCollectionDispatcher, f as SendActivityOptionsForCollection, ft as UnverifiedActivityReason, g as CustomCollectionCallbackSetters, gt as PageItems, h as ConstructorWithTypeId, ht as SenderKeyPair, i as GetActorOptions, it as ObjectAuthorizePredicate, j as FederationKvPrefixes, k as OutboxListenerSetters, kt as normalizeCircuitBreakerOptions, l as RequestContext, lt as OutboxPermanentFailureHandler, m as CollectionCallbackSetters, mt as SendActivityError, n as Context, nt as InboxListener, o as InboxContext, ot as OutboxErrorHandler, p as ActorCallbackSetters, pt as WebFingerLinksDispatcher, q as ActorKeyPairsDispatcher, r as ForwardActivityOptions, rt as NodeInfoDispatcher, s as OutboxContext, st as OutboxListener, t as ActorKeyPair, tt as InboxErrorHandler, u as RouteActivityOptions, ut as SharedInboxKeyDispatcher, v as Federation, vt as digest, w as IdempotencyKeyCallback, wt as CircuitBreakerKvState, x as FederationFetchOptions, xt as CircuitBreakerBeforeSendDecision, y as FederationBenchmarkOptions, yt as CircuitBreaker, z as Message } from "../context-BWbdE_MZ.js";
3
+ import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreListEntry, t as KvKey } from "../kv-CxaI4eGo.js";
4
4
  import { a as MessageQueueEnqueueOptions, i as MessageQueueDepth, n as InProcessMessageQueueOptions, o as MessageQueueListenOptions, r as MessageQueue, s as ParallelMessageQueue, t as InProcessMessageQueue } from "../mq-D-nlpY04.js";
5
- import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "../mod-CFuyfw3c.js";
5
+ import { a as RouterOptions, i as RouterError, n as handleWebFinger, o as RouterRouteResult, r as Router, t as WebFingerHandlerParameters } from "../mod-LmD03uRA.js";
6
6
  export { ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CircuitBreaker, CircuitBreakerActivityDrop, CircuitBreakerBeforeSendDecision, CircuitBreakerCreateOptions, CircuitBreakerFailurePolicy, CircuitBreakerKvState, CircuitBreakerOptions, CircuitBreakerState, CircuitBreakerStateChange, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBenchmarkOptions, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetActorOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxChallengePolicy, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, KvKey, KvStore, KvStoreListEntry, KvStoreSetOptions, MemoryKvStore, Message, MessageQueue, MessageQueueDepth, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, NormalizedCircuitBreakerOptions, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxContext, OutboxErrorHandler, OutboxListener, OutboxListenerErrorHandler, OutboxListenerSetters, OutboxPermanentFailureHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityError, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, UnverifiedActivityHandler, UnverifiedActivityReason, WebFingerHandlerParameters, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, handleWebFinger, normalizeCircuitBreakerOptions, parseCircuitBreakerKvState, 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 SendActivityError, c as buildCollectionSynchronizationHeader, d as normalizeCircuitBreakerOptions, f as parseCircuitBreakerKvState, i as createExponentialBackoffPolicy, l as digest, o as respondWithObject, p as createFederationBuilder, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as CircuitBreaker } from "../middleware-C_utyVa_.js";
3
+ import { a as SendActivityError, c as buildCollectionSynchronizationHeader, d as normalizeCircuitBreakerOptions, f as parseCircuitBreakerKvState, i as createExponentialBackoffPolicy, l as digest, o as respondWithObject, p as createFederationBuilder, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as CircuitBreaker } from "../middleware-B4FNnN3W.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { Router as Router$1, RouterError as RouterError$1, assertPath, isPath } from "@fedify/uri-template";
6
6
  import { isEqual } from "es-toolkit";
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import { n as assertGreater } from "../std__assert-BBjXFNOb.mjs";
5
+ import "../std__assert-BvGuO4xO.mjs";
6
6
  import { n as assertGreaterOrEqual, r as assertFalse } from "../assert_rejects-DN60FHPX.mjs";
7
7
  import { t as assert } from "../assert-jSdZMYam.mjs";
8
8
  import { test } from "@fedify/fixture";
@@ -294,50 +294,105 @@ test("InProcessMessageQueue", async (t) => {
294
294
  const listening = mq.listen((message) => {
295
295
  messages.push(message);
296
296
  }, controller);
297
- await t.step("enqueue()", async () => {
298
- await mq.enqueue("Hello, world!");
299
- });
300
- await waitFor(() => messages.length > 0, 15e3);
301
- await t.step("listen()", () => {
302
- assertEquals(messages, ["Hello, world!"]);
303
- });
304
- let started = 0;
305
- await t.step("enqueue() with delay", async () => {
306
- started = Date.now();
307
- await mq.enqueue("Delayed message", { delay: Temporal.Duration.from({ seconds: 3 }) });
308
- assertEquals(messages, ["Hello, world!"]);
309
- });
310
- await waitFor(() => messages.length > 1, 15e3);
311
- await t.step("listen() with delay", () => {
312
- assertEquals(messages, ["Hello, world!", "Delayed message"]);
313
- assertGreater(Date.now() - started, 3e3);
314
- });
315
- while (messages.length > 0) messages.pop();
316
- await t.step("enqueueMany()", async () => {
317
- const testMessages = Array.from({ length: 5 }, (_, i) => `Batch message ${i}!`);
318
- await mq.enqueueMany(testMessages);
319
- });
320
- await waitFor(() => messages.length >= 5, 15e3);
321
- await t.step("listen() [multiple]", () => {
322
- assertEquals(messages.length, 5);
323
- for (let i = 0; i < 5; i++) assertEquals(messages[i], `Batch message ${i}!`);
324
- });
325
- while (messages.length > 0) messages.pop();
326
- started = 0;
327
- await t.step("enqueueMany() with delay", async () => {
328
- started = Date.now();
329
- const testMessages = Array.from({ length: 3 }, (_, i) => `Delayed batch ${i}!`);
330
- await mq.enqueueMany(testMessages, { delay: Temporal.Duration.from({ seconds: 2 }) });
331
- assertEquals(messages.length, 0);
297
+ try {
298
+ await t.step("enqueue()", async () => {
299
+ await mq.enqueue("Hello, world!");
300
+ });
301
+ await waitFor(() => messages.length > 0, 15e3);
302
+ await t.step("listen()", () => {
303
+ assertEquals(messages, ["Hello, world!"]);
304
+ });
305
+ await t.step("enqueue() with delay", async () => {
306
+ const { timers, enqueued } = captureTimers(() => mq.enqueue("Delayed message", { delay: Temporal.Duration.from({ seconds: 3 }) }));
307
+ await enqueued;
308
+ assertEquals(timers.length, 1);
309
+ assertEquals(timers[0].delay, 3e3);
310
+ await mq.enqueue("Single delay marker");
311
+ await waitFor(() => messages.includes("Single delay marker"), 15e3);
312
+ assertEquals(messages, ["Hello, world!", "Single delay marker"]);
313
+ timers[0].callback();
314
+ });
315
+ await waitFor(() => messages.length >= 3, 15e3);
316
+ await t.step("listen() with delay", () => {
317
+ assertEquals(messages, [
318
+ "Hello, world!",
319
+ "Single delay marker",
320
+ "Delayed message"
321
+ ]);
322
+ });
323
+ while (messages.length > 0) messages.pop();
324
+ await t.step("enqueueMany()", async () => {
325
+ const testMessages = Array.from({ length: 5 }, (_, i) => `Batch message ${i}!`);
326
+ await mq.enqueueMany(testMessages);
327
+ });
328
+ await waitFor(() => messages.length >= 5, 15e3);
329
+ await t.step("listen() [multiple]", () => {
330
+ assertEquals(messages.length, 5);
331
+ for (let i = 0; i < 5; i++) assertEquals(messages[i], `Batch message ${i}!`);
332
+ });
333
+ while (messages.length > 0) messages.pop();
334
+ await t.step("enqueueMany() with delay", async () => {
335
+ const testMessages = Array.from({ length: 3 }, (_, i) => `Delayed batch ${i}!`);
336
+ const { timers, enqueued } = captureTimers(() => mq.enqueueMany(testMessages, { delay: Temporal.Duration.from({ seconds: 2 }) }));
337
+ await enqueued;
338
+ assertEquals(timers.length, 1);
339
+ assertEquals(timers[0].delay, 2e3);
340
+ await mq.enqueue("Batch delay marker");
341
+ await waitFor(() => messages.includes("Batch delay marker"), 15e3);
342
+ assertEquals(messages, ["Batch delay marker"]);
343
+ timers[0].callback();
344
+ });
345
+ await waitFor(() => messages.length >= 4, 15e3);
346
+ await t.step("listen() [delayed multiple]", () => {
347
+ assertEquals(messages.length, 4);
348
+ assertEquals(messages[0], "Batch delay marker");
349
+ for (let i = 0; i < 3; i++) assertEquals(messages[i + 1], `Delayed batch ${i}!`);
350
+ });
351
+ } finally {
352
+ controller.abort();
353
+ await listening;
354
+ }
355
+ });
356
+ function captureTimers(enqueue) {
357
+ const timers = [];
358
+ const original = globalThis.setTimeout;
359
+ globalThis.setTimeout = ((callback, delay) => {
360
+ timers.push({
361
+ callback,
362
+ delay
363
+ });
364
+ return 0;
332
365
  });
333
- await waitFor(() => messages.length >= 3, 15e3);
334
- await t.step("listen() [delayed multiple]", () => {
335
- assertEquals(messages.length, 3);
336
- assertGreater(Date.now() - started, 2e3);
337
- for (let i = 0; i < 3; i++) assertEquals(messages[i], `Delayed batch ${i}!`);
366
+ try {
367
+ return {
368
+ timers,
369
+ enqueued: enqueue()
370
+ };
371
+ } finally {
372
+ globalThis.setTimeout = original;
373
+ }
374
+ }
375
+ test("InProcessMessageQueue real delay", async (t) => {
376
+ for (const batch of [false, true]) await t.step(batch ? "enqueueMany()" : "enqueue()", async () => {
377
+ const mq = new InProcessMessageQueue();
378
+ const messages = [];
379
+ const controller = new AbortController();
380
+ const listening = mq.listen((message) => {
381
+ messages.push(message);
382
+ }, controller);
383
+ try {
384
+ const delayed = batch ? ["Delayed 1", "Delayed 2"] : ["Delayed"];
385
+ const options = { delay: Temporal.Duration.from({ milliseconds: 100 }) };
386
+ if (batch) await mq.enqueueMany(delayed, options);
387
+ else await mq.enqueue(delayed[0], options);
388
+ await mq.enqueue("Immediate");
389
+ await waitFor(() => messages.length >= delayed.length + 1, 15e3);
390
+ assertEquals(messages, ["Immediate", ...delayed]);
391
+ } finally {
392
+ controller.abort();
393
+ await listening;
394
+ }
338
395
  });
339
- controller.abort();
340
- await listening;
341
396
  });
342
397
  test("InProcessMessageQueue.getDepth()", async () => {
343
398
  const mq = new InProcessMessageQueue();
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import "../std__assert-BBjXFNOb.mjs";
4
+ import "../std__assert-BvGuO4xO.mjs";
5
5
  import { r as assertFalse } from "../assert_rejects-DN60FHPX.mjs";
6
6
  import { t as assert } from "../assert-jSdZMYam.mjs";
7
7
  import { t as acceptsJsonLd } from "../negotiation-CDW-_gUU.mjs";
@@ -2,7 +2,7 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BBjXFNOb.mjs";
5
+ import "../std__assert-BvGuO4xO.mjs";
6
6
  import { r as assertFalse } from "../assert_rejects-DN60FHPX.mjs";
7
7
  import { t as assert } from "../assert-jSdZMYam.mjs";
8
8
  import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";