@fedify/fedify 2.0.0-dev.237 → 2.0.0-dev.279

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 (71) hide show
  1. package/dist/{builder-D9GtbwtV.js → builder-B7WWCOdc.js} +3 -3
  2. package/dist/compat/mod.d.cts +2 -2
  3. package/dist/compat/mod.d.ts +2 -2
  4. package/dist/compat/transformers.test.js +12 -12
  5. package/dist/{context-C7vzWilY.d.ts → context-BNNWbaZL.d.ts} +51 -8
  6. package/dist/{context-Bns6uTJq.js → context-CZ5llAss.js} +12 -12
  7. package/dist/{context-CrB9RFy5.d.cts → context-D7JEVvXJ.d.cts} +51 -8
  8. package/dist/{deno-RfS7RWmL.js → deno-fe0u4LiE.js} +8 -2
  9. package/dist/{docloader-DvECEZ5H.js → docloader-C9Dmxf-K.js} +2 -2
  10. package/dist/federation/builder.test.js +3 -3
  11. package/dist/federation/handler.test.js +13 -13
  12. package/dist/federation/idempotency.test.js +12 -12
  13. package/dist/federation/inbox.test.js +2 -2
  14. package/dist/federation/middleware.test.js +64 -12
  15. package/dist/federation/mod.cjs +5 -5
  16. package/dist/federation/mod.d.cts +2 -2
  17. package/dist/federation/mod.d.ts +2 -2
  18. package/dist/federation/mod.js +5 -5
  19. package/dist/federation/mq.test.js +162 -10
  20. package/dist/federation/send.test.js +5 -5
  21. package/dist/federation/webfinger.test.js +13 -13
  22. package/dist/{federation-B431K2gm.cjs → federation-CE0CJ_0G.cjs} +94 -10
  23. package/dist/{federation-BbZwNNWj.js → federation-D6FVaeAR.js} +94 -10
  24. package/dist/{http-Cc7n4Qoh.js → http-7r8B3dF_.js} +8 -2
  25. package/dist/{http-8TlrsZoL.cjs → http-DMi5iyiI.cjs} +8 -2
  26. package/dist/{http-CE72P83O.js → http-oCBlFLKT.js} +2 -2
  27. package/dist/{inbox-CgoFwrkP.js → inbox-Dm1rfkdg.js} +1 -1
  28. package/dist/{key-COCrawtF.js → key-BGzsKfpZ.js} +1 -1
  29. package/dist/{kv-cache-BEeqyGER.js → kv-cache-B__dHl7g.js} +1 -1
  30. package/dist/{kv-cache-BV0LKQyf.cjs → kv-cache-C0AvpI7U.cjs} +2 -2
  31. package/dist/{kv-cache-DRGsvqdV.js → kv-cache-CETRZwoP.js} +2 -2
  32. package/dist/{ld-BaGGiySp.js → ld-BaO1-A5Y.js} +2 -2
  33. package/dist/{middleware-C1a3efyY.js → middleware-BDJNulB_.js} +4 -4
  34. package/dist/{middleware-BNd2OPEY.js → middleware-BlOT9luD.js} +66 -23
  35. package/dist/{middleware-DJQ_PWmN.cjs → middleware-C0Vj7vNo.cjs} +59 -16
  36. package/dist/{middleware-CmBe73ju.js → middleware-CnGBoMop.js} +12 -12
  37. package/dist/middleware-DT3JkH-g.cjs +12 -0
  38. package/dist/{middleware-B_oUqHDl.js → middleware-R0w-WauH.js} +59 -16
  39. package/dist/{mod-0qnPv4EC.d.cts → mod-054TSUXs.d.cts} +1 -1
  40. package/dist/{mod-0p9zUdzg.d.cts → mod-BHXq4Q3x.d.cts} +1 -1
  41. package/dist/{mod-C3SOvTD1.d.ts → mod-BhDb3RBS.d.ts} +1 -1
  42. package/dist/{mod-D6pS5_xJ.d.cts → mod-C74sRHP8.d.cts} +1 -1
  43. package/dist/{mod-xc20HhMD.d.ts → mod-DZmuPaKv.d.ts} +1 -1
  44. package/dist/{mod-waqu-BL_.d.ts → mod-YpmzboJc.d.ts} +1 -1
  45. package/dist/mod.cjs +5 -5
  46. package/dist/mod.d.cts +4 -4
  47. package/dist/mod.d.ts +4 -4
  48. package/dist/mod.js +5 -5
  49. package/dist/nodeinfo/handler.test.js +13 -13
  50. package/dist/{owner-UVBc7UEt.js → owner-BqIhDQWW.js} +1 -1
  51. package/dist/{proof-CPysx9bF.js → proof-Cg6AAAWI.js} +1 -1
  52. package/dist/{proof-DSF9PSv9.js → proof-DoHt7qrS.js} +2 -2
  53. package/dist/{proof-Cu_UY89a.cjs → proof-vSvvLbTh.cjs} +1 -1
  54. package/dist/{send-DoACJ0D8.js → send-CO2ZYT96.js} +2 -2
  55. package/dist/sig/http.test.js +3 -3
  56. package/dist/sig/key.test.js +2 -2
  57. package/dist/sig/ld.test.js +3 -3
  58. package/dist/sig/mod.cjs +2 -2
  59. package/dist/sig/mod.js +2 -2
  60. package/dist/sig/owner.test.js +3 -3
  61. package/dist/sig/proof.test.js +3 -3
  62. package/dist/testing/mod.d.ts +23 -7
  63. package/dist/testing/mod.js +1 -1
  64. package/dist/utils/docloader.test.js +4 -4
  65. package/dist/utils/kv-cache.test.js +1 -1
  66. package/dist/utils/mod.cjs +2 -2
  67. package/dist/utils/mod.d.cts +1 -1
  68. package/dist/utils/mod.d.ts +1 -1
  69. package/dist/utils/mod.js +2 -2
  70. package/package.json +12 -9
  71. package/dist/middleware-XqO4Y-9s.cjs +0 -12
@@ -3,12 +3,12 @@
3
3
  const { URLPattern } = require("urlpattern-polyfill");
4
4
 
5
5
  require('../transformers-BjBg6Lag.cjs');
6
- require('../http-8TlrsZoL.cjs');
7
- const require_middleware = require('../middleware-DJQ_PWmN.cjs');
8
- require('../proof-Cu_UY89a.cjs');
9
- const require_federation = require('../federation-B431K2gm.cjs');
6
+ require('../http-DMi5iyiI.cjs');
7
+ const require_middleware = require('../middleware-C0Vj7vNo.cjs');
8
+ require('../proof-vSvvLbTh.cjs');
9
+ const require_federation = require('../federation-CE0CJ_0G.cjs');
10
10
  require('../types-B6z6CqIz.cjs');
11
- require('../kv-cache-BV0LKQyf.cjs');
11
+ require('../kv-cache-C0AvpI7U.cjs');
12
12
 
13
13
  exports.InProcessMessageQueue = require_federation.InProcessMessageQueue;
14
14
  exports.MemoryKvStore = require_federation.MemoryKvStore;
@@ -1,7 +1,7 @@
1
1
  import "../client-by-PEGAJ.cjs";
2
2
  import "../http-ClB3pLcL.cjs";
3
3
  import "../owner-C-zfmVAD.cjs";
4
- import { ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CreateFederationOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, Message, MessageQueue, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxErrorHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, respondWithObject, respondWithObjectIfAcceptable } from "../context-CrB9RFy5.cjs";
4
+ import { ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CreateFederationOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, Message, MessageQueue, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxErrorHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, respondWithObject, respondWithObjectIfAcceptable } from "../context-D7JEVvXJ.cjs";
5
5
  import { KvKey, KvStore, KvStoreListEntry, KvStoreSetOptions, MemoryKvStore } from "../kv-B4vFhIYL.cjs";
6
- import { WebFingerHandlerParameters, handleWebFinger } from "../mod-0qnPv4EC.cjs";
6
+ import { WebFingerHandlerParameters, handleWebFinger } from "../mod-054TSUXs.cjs";
7
7
  export { ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CreateFederationOptions, 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, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, WebFingerHandlerParameters, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, handleWebFinger, respondWithObject, respondWithObjectIfAcceptable };
@@ -3,7 +3,7 @@ import { URLPattern } from "urlpattern-polyfill";
3
3
  import "../client-CUTUGgvJ.js";
4
4
  import "../http-DLBDPal9.js";
5
5
  import "../owner-BgI8C-VY.js";
6
- import { ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CreateFederationOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, Message, MessageQueue, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxErrorHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, respondWithObject, respondWithObjectIfAcceptable } from "../context-C7vzWilY.js";
6
+ import { ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CreateFederationOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, Message, MessageQueue, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxErrorHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, respondWithObject, respondWithObjectIfAcceptable } from "../context-BNNWbaZL.js";
7
7
  import { KvKey, KvStore, KvStoreListEntry, KvStoreSetOptions, MemoryKvStore } from "../kv-CYySNrsn.js";
8
- import { WebFingerHandlerParameters, handleWebFinger } from "../mod-C3SOvTD1.js";
8
+ import { WebFingerHandlerParameters, handleWebFinger } from "../mod-BhDb3RBS.js";
9
9
  export { ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CreateFederationOptions, 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, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, WebFingerHandlerParameters, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, handleWebFinger, respondWithObject, respondWithObjectIfAcceptable };
@@ -3,11 +3,11 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
 
5
5
  import "../transformers-N_ip_y4P.js";
6
- import "../http-Cc7n4Qoh.js";
7
- import { Router, RouterError, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, handleWebFinger, respondWithObject, respondWithObjectIfAcceptable } from "../middleware-B_oUqHDl.js";
8
- import "../proof-CPysx9bF.js";
9
- import { InProcessMessageQueue, MemoryKvStore, ParallelMessageQueue } from "../federation-BbZwNNWj.js";
6
+ import "../http-7r8B3dF_.js";
7
+ import { Router, RouterError, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, handleWebFinger, respondWithObject, respondWithObjectIfAcceptable } from "../middleware-R0w-WauH.js";
8
+ import "../proof-Cg6AAAWI.js";
9
+ import { InProcessMessageQueue, MemoryKvStore, ParallelMessageQueue } from "../federation-D6FVaeAR.js";
10
10
  import "../types-8l28uC8o.js";
11
- import "../kv-cache-DRGsvqdV.js";
11
+ import "../kv-cache-CETRZwoP.js";
12
12
 
13
13
  export { InProcessMessageQueue, MemoryKvStore, ParallelMessageQueue, Router, RouterError, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, handleWebFinger, respondWithObject, respondWithObjectIfAcceptable };
@@ -26,6 +26,11 @@ var InProcessMessageQueue = class {
26
26
  #monitors;
27
27
  #pollIntervalMs;
28
28
  /**
29
+ * Tracks which ordering keys are currently being processed to ensure
30
+ * sequential processing for messages with the same key.
31
+ */
32
+ #processingKeys;
33
+ /**
29
34
  * In-process message queue does not provide native retry mechanisms.
30
35
  * @since 1.7.0
31
36
  */
@@ -38,6 +43,7 @@ var InProcessMessageQueue = class {
38
43
  this.#messages = [];
39
44
  this.#monitors = {};
40
45
  this.#pollIntervalMs = Temporal.Duration.from(options.pollInterval ?? { seconds: 5 }).total("millisecond");
46
+ this.#processingKeys = /* @__PURE__ */ new Set();
41
47
  }
42
48
  enqueue(message, options) {
43
49
  const delay$1 = options?.delay == null ? 0 : Math.max(options.delay.total("millisecond"), 0);
@@ -48,7 +54,11 @@ var InProcessMessageQueue = class {
48
54
  }), delay$1);
49
55
  return Promise.resolve();
50
56
  }
51
- this.#messages.push(message);
57
+ const orderingKey = options?.orderingKey ?? null;
58
+ this.#messages.push({
59
+ message,
60
+ orderingKey
61
+ });
52
62
  for (const monitorId in this.#monitors) this.#monitors[monitorId]();
53
63
  return Promise.resolve();
54
64
  }
@@ -62,18 +72,29 @@ var InProcessMessageQueue = class {
62
72
  }), delay$1);
63
73
  return Promise.resolve();
64
74
  }
65
- this.#messages.push(...messages);
75
+ const orderingKey = options?.orderingKey ?? null;
76
+ for (const message of messages) this.#messages.push({
77
+ message,
78
+ orderingKey
79
+ });
66
80
  for (const monitorId in this.#monitors) this.#monitors[monitorId]();
67
81
  return Promise.resolve();
68
82
  }
69
83
  async listen(handler, options = {}) {
70
84
  const signal = options.signal;
71
85
  while (signal == null || !signal.aborted) {
72
- while (this.#messages.length > 0) {
73
- const message = this.#messages.shift();
74
- await handler(message);
75
- }
76
- await this.#wait(this.#pollIntervalMs, signal);
86
+ const idx = this.#messages.findIndex((m) => m.orderingKey == null || !this.#processingKeys.has(m.orderingKey));
87
+ if (idx >= 0) {
88
+ const queued = this.#messages.splice(idx, 1)[0];
89
+ const { message, orderingKey } = queued;
90
+ if (orderingKey != null) this.#processingKeys.add(orderingKey);
91
+ try {
92
+ await handler(message);
93
+ } finally {
94
+ if (orderingKey != null) this.#processingKeys.delete(orderingKey);
95
+ }
96
+ } else if (this.#messages.length === 0) await this.#wait(this.#pollIntervalMs, signal);
97
+ else await this.#wait(10, signal);
77
98
  }
78
99
  }
79
100
  #wait(ms, signal) {
@@ -105,6 +126,21 @@ var InProcessMessageQueue = class {
105
126
  * for I/O-bound tasks, but not for CPU-bound tasks, which is okay for Fedify's
106
127
  * workloads.
107
128
  *
129
+ * When using `ParallelMessageQueue`, the ordering guarantee is preserved
130
+ * *only if* the underlying queue implementation delivers messages in a wrapper
131
+ * format that includes the `__fedify_ordering_key__` property. Currently,
132
+ * only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
133
+ * For other queue implementations (e.g., `InProcessMessageQueue`,
134
+ * `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
135
+ * `AmqpMessageQueue`), the ordering key cannot be detected by
136
+ * `ParallelMessageQueue`, so ordering guarantees are handled by those
137
+ * implementations directly rather than at the `ParallelMessageQueue` level.
138
+ *
139
+ * Messages with the same ordering key will never be processed concurrently
140
+ * by different workers, ensuring sequential processing within each key.
141
+ * Messages with different ordering keys (or no ordering key) can still be
142
+ * processed in parallel.
143
+ *
108
144
  * @since 1.0.0
109
145
  */
110
146
  var ParallelMessageQueue = class ParallelMessageQueue {
@@ -116,6 +152,15 @@ var ParallelMessageQueue = class ParallelMessageQueue {
116
152
  */
117
153
  nativeRetrial;
118
154
  /**
155
+ * Tracks which ordering keys are currently being processed to ensure
156
+ * sequential processing for messages with the same key.
157
+ */
158
+ #processingKeys = /* @__PURE__ */ new Set();
159
+ /**
160
+ * Pending messages waiting for their ordering key to become available.
161
+ */
162
+ #pendingMessages = [];
163
+ /**
119
164
  * Constructs a new {@link ParallelMessageQueue} with the given queue and
120
165
  * number of workers.
121
166
  * @param queue The message queue to use under the hood. Note that
@@ -143,6 +188,25 @@ var ParallelMessageQueue = class ParallelMessageQueue {
143
188
  }
144
189
  await this.queue.enqueueMany(messages, options);
145
190
  }
191
+ /**
192
+ * Extracts ordering key from a message if present.
193
+ *
194
+ * This method only works for queue implementations that deliver messages
195
+ * in the wrapper format with `__fedify_ordering_key__` property. Currently,
196
+ * only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
197
+ *
198
+ * For other queue implementations (`InProcessMessageQueue`,
199
+ * `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
200
+ * `AmqpMessageQueue`), messages are delivered as raw payloads without the
201
+ * wrapper, so the ordering key cannot be detected here. Those
202
+ * implementations handle ordering guarantees internally.
203
+ */
204
+ #extractOrderingKey(message) {
205
+ if (message != null && typeof message === "object") {
206
+ if ("__fedify_ordering_key__" in message) return message.__fedify_ordering_key__;
207
+ }
208
+ return void 0;
209
+ }
146
210
  listen(handler, options = {}) {
147
211
  const workers = /* @__PURE__ */ new Map();
148
212
  return this.queue.listen(async (message) => {
@@ -151,13 +215,33 @@ var ParallelMessageQueue = class ParallelMessageQueue {
151
215
  workers.delete(consumedId);
152
216
  }
153
217
  const workerId = crypto.randomUUID();
154
- const promise = this.#work(workerId, handler, message);
218
+ const orderingKey = this.#extractOrderingKey(message);
219
+ if (orderingKey != null && this.#processingKeys.has(orderingKey)) await new Promise((resolve) => {
220
+ this.#pendingMessages.push({
221
+ message,
222
+ orderingKey,
223
+ resolve
224
+ });
225
+ });
226
+ if (orderingKey != null) this.#processingKeys.add(orderingKey);
227
+ const promise = this.#work(workerId, handler, message, orderingKey);
155
228
  workers.set(workerId, promise);
156
229
  }, options);
157
230
  }
158
- async #work(workerId, handler, message) {
231
+ async #work(workerId, handler, message, orderingKey) {
159
232
  await this.#sleep(0);
160
- await handler(message);
233
+ try {
234
+ await handler(message);
235
+ } finally {
236
+ if (orderingKey != null) {
237
+ this.#processingKeys.delete(orderingKey);
238
+ const pendingIdx = this.#pendingMessages.findIndex((p) => p.orderingKey === orderingKey);
239
+ if (pendingIdx >= 0) {
240
+ const pending = this.#pendingMessages.splice(pendingIdx, 1)[0];
241
+ pending.resolve();
242
+ }
243
+ }
244
+ }
161
245
  return workerId;
162
246
  }
163
247
  #sleep(ms) {
@@ -222,6 +306,74 @@ test("InProcessMessageQueue", async (t) => {
222
306
  controller.abort();
223
307
  await listening;
224
308
  });
309
+ test("InProcessMessageQueue orderingKey", async (t) => {
310
+ const mq = new InProcessMessageQueue();
311
+ const orderTracker = {
312
+ keyA: [],
313
+ keyB: [],
314
+ noKey: []
315
+ };
316
+ const allMessages = [];
317
+ const controller = new AbortController();
318
+ const listening = mq.listen((message) => {
319
+ allMessages.push(message);
320
+ const trackKey = message.key ?? "noKey";
321
+ if (trackKey in orderTracker) orderTracker[trackKey].push(message.value);
322
+ }, controller);
323
+ await t.step("enqueue with ordering key", async () => {
324
+ await mq.enqueue({
325
+ key: "keyA",
326
+ value: 1
327
+ }, { orderingKey: "keyA" });
328
+ await mq.enqueue({
329
+ key: "keyB",
330
+ value: 1
331
+ }, { orderingKey: "keyB" });
332
+ await mq.enqueue({
333
+ key: "keyA",
334
+ value: 2
335
+ }, { orderingKey: "keyA" });
336
+ await mq.enqueue({
337
+ key: "keyB",
338
+ value: 2
339
+ }, { orderingKey: "keyB" });
340
+ await mq.enqueue({
341
+ key: "keyA",
342
+ value: 3
343
+ }, { orderingKey: "keyA" });
344
+ await mq.enqueue({
345
+ key: "keyB",
346
+ value: 3
347
+ }, { orderingKey: "keyB" });
348
+ await mq.enqueue({
349
+ key: null,
350
+ value: 1
351
+ });
352
+ await mq.enqueue({
353
+ key: null,
354
+ value: 2
355
+ });
356
+ });
357
+ await waitFor(() => allMessages.length >= 8, 3e4);
358
+ await t.step("verify ordering key order", () => {
359
+ assertEquals(orderTracker.keyA, [
360
+ 1,
361
+ 2,
362
+ 3
363
+ ], "Messages with orderingKey 'keyA' should be processed in order");
364
+ assertEquals(orderTracker.keyB, [
365
+ 1,
366
+ 2,
367
+ 3
368
+ ], "Messages with orderingKey 'keyB' should be processed in order");
369
+ });
370
+ await t.step("verify messages without ordering key", () => {
371
+ assertEquals(orderTracker.noKey.length, 2, "Messages without ordering key should all be received");
372
+ assert(orderTracker.noKey.includes(1) && orderTracker.noKey.includes(2), "Messages without ordering key should contain values 1 and 2");
373
+ });
374
+ controller.abort();
375
+ await listening;
376
+ });
225
377
  test("MessageQueue.nativeRetrial", async (t) => {
226
378
  if ("Deno" in globalThis && "openKv" in globalThis.Deno && typeof globalThis.Deno.openKv === "function") await t.step("DenoKvMessageQueue", async () => {
227
379
  const packageName = () => "@fedify/denokv";
@@ -7,11 +7,11 @@ import { createTestTracerProvider, mockDocumentLoader, test } from "../dist-B5f6
7
7
  import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import { assert } from "../assert-MZs1qjMx.js";
9
9
  import "../assert_instance_of-DHz7EHNU.js";
10
- import "../deno-RfS7RWmL.js";
11
- import "../key-COCrawtF.js";
12
- import { verifyRequest } from "../http-CE72P83O.js";
13
- import { doesActorOwnKey } from "../owner-UVBc7UEt.js";
14
- import { extractInboxes, sendActivity } from "../send-DoACJ0D8.js";
10
+ import "../deno-fe0u4LiE.js";
11
+ import "../key-BGzsKfpZ.js";
12
+ import { verifyRequest } from "../http-oCBlFLKT.js";
13
+ import { doesActorOwnKey } from "../owner-BqIhDQWW.js";
14
+ import { extractInboxes, sendActivity } from "../send-CO2ZYT96.js";
15
15
  import "../std__assert-DWivtrGR.js";
16
16
  import { assertFalse, assertRejects } from "../assert_rejects-Ce45JcFg.js";
17
17
  import "../assert_throws-BNXdRGWP.js";
@@ -8,30 +8,30 @@ import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import "../assert-MZs1qjMx.js";
9
9
  import "../assert_instance_of-DHz7EHNU.js";
10
10
  import { MemoryKvStore } from "../kv-QzKcOQgP.js";
11
- import "../deno-RfS7RWmL.js";
12
- import { createFederation, handleWebFinger } from "../middleware-BNd2OPEY.js";
11
+ import "../deno-fe0u4LiE.js";
12
+ import { createFederation, handleWebFinger } from "../middleware-BlOT9luD.js";
13
13
  import "../client-Dg7OfUDA.js";
14
14
  import "../router-D9eI0s4b.js";
15
15
  import "../types-CPz01LGH.js";
16
- import "../key-COCrawtF.js";
17
- import "../http-CE72P83O.js";
18
- import "../ld-BaGGiySp.js";
19
- import "../owner-UVBc7UEt.js";
20
- import "../proof-DSF9PSv9.js";
21
- import "../docloader-DvECEZ5H.js";
22
- import "../kv-cache-BEeqyGER.js";
23
- import "../inbox-CgoFwrkP.js";
24
- import "../builder-D9GtbwtV.js";
16
+ import "../key-BGzsKfpZ.js";
17
+ import "../http-oCBlFLKT.js";
18
+ import "../ld-BaO1-A5Y.js";
19
+ import "../owner-BqIhDQWW.js";
20
+ import "../proof-DoHt7qrS.js";
21
+ import "../docloader-C9Dmxf-K.js";
22
+ import "../kv-cache-B__dHl7g.js";
23
+ import "../inbox-Dm1rfkdg.js";
24
+ import "../builder-B7WWCOdc.js";
25
25
  import "../collection-CcnIw1qY.js";
26
26
  import "../keycache-DRxpZ5r9.js";
27
27
  import "../negotiation-5NPJL6zp.js";
28
28
  import "../retry-D4GJ670a.js";
29
- import "../send-DoACJ0D8.js";
29
+ import "../send-CO2ZYT96.js";
30
30
  import "../std__assert-DWivtrGR.js";
31
31
  import "../assert_rejects-Ce45JcFg.js";
32
32
  import "../assert_throws-BNXdRGWP.js";
33
33
  import "../assert_not_equals-C80BG-_5.js";
34
- import { createRequestContext } from "../context-Bns6uTJq.js";
34
+ import { createRequestContext } from "../context-CZ5llAss.js";
35
35
  import { Image, Link, Person } from "@fedify/vocab";
36
36
 
37
37
  //#region src/federation/webfinger.test.ts
@@ -106,6 +106,11 @@ var InProcessMessageQueue = class {
106
106
  #monitors;
107
107
  #pollIntervalMs;
108
108
  /**
109
+ * Tracks which ordering keys are currently being processed to ensure
110
+ * sequential processing for messages with the same key.
111
+ */
112
+ #processingKeys;
113
+ /**
109
114
  * In-process message queue does not provide native retry mechanisms.
110
115
  * @since 1.7.0
111
116
  */
@@ -118,6 +123,7 @@ var InProcessMessageQueue = class {
118
123
  this.#messages = [];
119
124
  this.#monitors = {};
120
125
  this.#pollIntervalMs = Temporal.Duration.from(options.pollInterval ?? { seconds: 5 }).total("millisecond");
126
+ this.#processingKeys = /* @__PURE__ */ new Set();
121
127
  }
122
128
  enqueue(message, options) {
123
129
  const delay = options?.delay == null ? 0 : Math.max(options.delay.total("millisecond"), 0);
@@ -128,7 +134,11 @@ var InProcessMessageQueue = class {
128
134
  }), delay);
129
135
  return Promise.resolve();
130
136
  }
131
- this.#messages.push(message);
137
+ const orderingKey = options?.orderingKey ?? null;
138
+ this.#messages.push({
139
+ message,
140
+ orderingKey
141
+ });
132
142
  for (const monitorId in this.#monitors) this.#monitors[monitorId]();
133
143
  return Promise.resolve();
134
144
  }
@@ -142,18 +152,29 @@ var InProcessMessageQueue = class {
142
152
  }), delay);
143
153
  return Promise.resolve();
144
154
  }
145
- this.#messages.push(...messages);
155
+ const orderingKey = options?.orderingKey ?? null;
156
+ for (const message of messages) this.#messages.push({
157
+ message,
158
+ orderingKey
159
+ });
146
160
  for (const monitorId in this.#monitors) this.#monitors[monitorId]();
147
161
  return Promise.resolve();
148
162
  }
149
163
  async listen(handler, options = {}) {
150
164
  const signal = options.signal;
151
165
  while (signal == null || !signal.aborted) {
152
- while (this.#messages.length > 0) {
153
- const message = this.#messages.shift();
154
- await handler(message);
155
- }
156
- await this.#wait(this.#pollIntervalMs, signal);
166
+ const idx = this.#messages.findIndex((m) => m.orderingKey == null || !this.#processingKeys.has(m.orderingKey));
167
+ if (idx >= 0) {
168
+ const queued = this.#messages.splice(idx, 1)[0];
169
+ const { message, orderingKey } = queued;
170
+ if (orderingKey != null) this.#processingKeys.add(orderingKey);
171
+ try {
172
+ await handler(message);
173
+ } finally {
174
+ if (orderingKey != null) this.#processingKeys.delete(orderingKey);
175
+ }
176
+ } else if (this.#messages.length === 0) await this.#wait(this.#pollIntervalMs, signal);
177
+ else await this.#wait(10, signal);
157
178
  }
158
179
  }
159
180
  #wait(ms, signal) {
@@ -185,6 +206,21 @@ var InProcessMessageQueue = class {
185
206
  * for I/O-bound tasks, but not for CPU-bound tasks, which is okay for Fedify's
186
207
  * workloads.
187
208
  *
209
+ * When using `ParallelMessageQueue`, the ordering guarantee is preserved
210
+ * *only if* the underlying queue implementation delivers messages in a wrapper
211
+ * format that includes the `__fedify_ordering_key__` property. Currently,
212
+ * only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
213
+ * For other queue implementations (e.g., `InProcessMessageQueue`,
214
+ * `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
215
+ * `AmqpMessageQueue`), the ordering key cannot be detected by
216
+ * `ParallelMessageQueue`, so ordering guarantees are handled by those
217
+ * implementations directly rather than at the `ParallelMessageQueue` level.
218
+ *
219
+ * Messages with the same ordering key will never be processed concurrently
220
+ * by different workers, ensuring sequential processing within each key.
221
+ * Messages with different ordering keys (or no ordering key) can still be
222
+ * processed in parallel.
223
+ *
188
224
  * @since 1.0.0
189
225
  */
190
226
  var ParallelMessageQueue = class ParallelMessageQueue {
@@ -196,6 +232,15 @@ var ParallelMessageQueue = class ParallelMessageQueue {
196
232
  */
197
233
  nativeRetrial;
198
234
  /**
235
+ * Tracks which ordering keys are currently being processed to ensure
236
+ * sequential processing for messages with the same key.
237
+ */
238
+ #processingKeys = /* @__PURE__ */ new Set();
239
+ /**
240
+ * Pending messages waiting for their ordering key to become available.
241
+ */
242
+ #pendingMessages = [];
243
+ /**
199
244
  * Constructs a new {@link ParallelMessageQueue} with the given queue and
200
245
  * number of workers.
201
246
  * @param queue The message queue to use under the hood. Note that
@@ -223,6 +268,25 @@ var ParallelMessageQueue = class ParallelMessageQueue {
223
268
  }
224
269
  await this.queue.enqueueMany(messages, options);
225
270
  }
271
+ /**
272
+ * Extracts ordering key from a message if present.
273
+ *
274
+ * This method only works for queue implementations that deliver messages
275
+ * in the wrapper format with `__fedify_ordering_key__` property. Currently,
276
+ * only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
277
+ *
278
+ * For other queue implementations (`InProcessMessageQueue`,
279
+ * `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
280
+ * `AmqpMessageQueue`), messages are delivered as raw payloads without the
281
+ * wrapper, so the ordering key cannot be detected here. Those
282
+ * implementations handle ordering guarantees internally.
283
+ */
284
+ #extractOrderingKey(message) {
285
+ if (message != null && typeof message === "object") {
286
+ if ("__fedify_ordering_key__" in message) return message.__fedify_ordering_key__;
287
+ }
288
+ return void 0;
289
+ }
226
290
  listen(handler, options = {}) {
227
291
  const workers = /* @__PURE__ */ new Map();
228
292
  return this.queue.listen(async (message) => {
@@ -231,13 +295,33 @@ var ParallelMessageQueue = class ParallelMessageQueue {
231
295
  workers.delete(consumedId);
232
296
  }
233
297
  const workerId = crypto.randomUUID();
234
- const promise = this.#work(workerId, handler, message);
298
+ const orderingKey = this.#extractOrderingKey(message);
299
+ if (orderingKey != null && this.#processingKeys.has(orderingKey)) await new Promise((resolve) => {
300
+ this.#pendingMessages.push({
301
+ message,
302
+ orderingKey,
303
+ resolve
304
+ });
305
+ });
306
+ if (orderingKey != null) this.#processingKeys.add(orderingKey);
307
+ const promise = this.#work(workerId, handler, message, orderingKey);
235
308
  workers.set(workerId, promise);
236
309
  }, options);
237
310
  }
238
- async #work(workerId, handler, message) {
311
+ async #work(workerId, handler, message, orderingKey) {
239
312
  await this.#sleep(0);
240
- await handler(message);
313
+ try {
314
+ await handler(message);
315
+ } finally {
316
+ if (orderingKey != null) {
317
+ this.#processingKeys.delete(orderingKey);
318
+ const pendingIdx = this.#pendingMessages.findIndex((p) => p.orderingKey === orderingKey);
319
+ if (pendingIdx >= 0) {
320
+ const pending = this.#pendingMessages.splice(pendingIdx, 1)[0];
321
+ pending.resolve();
322
+ }
323
+ }
324
+ }
241
325
  return workerId;
242
326
  }
243
327
  #sleep(ms) {