@fedify/fedify 1.5.0-dev.730 → 1.5.0-dev.732

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGES.md CHANGED
@@ -19,6 +19,9 @@ To be released.
19
19
  undefined`).
20
20
  - Added `SendActivityOptions.fanout` option.
21
21
  - Added OpenTelemetry instrumented span `activitypub.fanout`.
22
+ - The `ForwardActivityOptions` interface became a type alias of
23
+ `Omit<SendActivityOptions, "fanout"> & { skipIfUnsigned: boolean }`,
24
+ which is still compatible with the previous version.
22
25
 
23
26
  - A `Federation` object now can have a canonical origin for web URLs and
24
27
  a canonical host for fediverse handles. This affects the URLs constructed
@@ -61,6 +64,9 @@ To be released.
61
64
  - Deprecated the fourth parameter of the `ObjectAuthorizePredicate` type
62
65
  in favor of the `RequestContext.getSignedKeyOwner()` method.
63
66
 
67
+ - Added an optional method `enqueueMany()` to `MessageQueue` interface
68
+ for sending multiple activities at once.
69
+
64
70
  - Fixed a bug of the `fedify inbox` command where it had failed to render
65
71
  the web interface when the `fedify` command was installed using
66
72
  `deno install` command from JSR.
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "@fedify/fedify",
3
- "version": "1.5.0-dev.730+390610bf",
3
+ "version": "1.5.0-dev.732+7b169275",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./mod.ts",
@@ -322,6 +322,11 @@ export class FederationImpl {
322
322
  });
323
323
  }
324
324
  async #listenFanoutMessage(data, message) {
325
+ const logger = getLogger(["fedify", "federation", "fanout"]);
326
+ logger.debug("Fanning out activity {activityId} to {inboxes} inbox(es)...", {
327
+ activityId: message.activityId,
328
+ inboxes: globalThis.Object.keys(message.inboxes).length,
329
+ });
325
330
  const keys = await Promise.all(message.keys.map(async ({ keyId, privateKey }) => ({
326
331
  keyId: new URL(keyId),
327
332
  privateKey: await importJwk(privateKey, "private"),
@@ -1284,7 +1289,7 @@ export class FederationImpl {
1284
1289
  this._startQueueInternal(ctx.data);
1285
1290
  const carrier = {};
1286
1291
  propagation.inject(context.active(), carrier);
1287
- const promises = [];
1292
+ const messages = [];
1288
1293
  for (const inbox in inboxes) {
1289
1294
  const message = {
1290
1295
  type: "outbox",
@@ -1303,18 +1308,31 @@ export class FederationImpl {
1303
1308
  },
1304
1309
  traceContext: carrier,
1305
1310
  };
1306
- promises.push(this.outboxQueue.enqueue(message));
1311
+ messages.push(message);
1312
+ }
1313
+ const { outboxQueue } = this;
1314
+ if (outboxQueue.enqueueMany == null) {
1315
+ const promises = messages.map((m) => outboxQueue.enqueue(m));
1316
+ const results = await Promise.allSettled(promises);
1317
+ const errors = results
1318
+ .filter((r) => r.status === "rejected")
1319
+ .map((r) => r.reason);
1320
+ if (errors.length > 0) {
1321
+ logger.error("Failed to enqueue activity {activityId} to send later: {errors}", { activityId: activity.id.href, errors });
1322
+ if (errors.length > 1) {
1323
+ throw new AggregateError(errors, `Failed to enqueue activity ${activityId} to send later.`);
1324
+ }
1325
+ throw errors[0];
1326
+ }
1307
1327
  }
1308
- const results = await Promise.allSettled(promises);
1309
- const errors = results
1310
- .filter((r) => r.status === "rejected")
1311
- .map((r) => r.reason);
1312
- if (errors.length > 0) {
1313
- logger.error("Failed to enqueue activity {activityId} to send later: {errors}", { activityId: activity.id.href, errors });
1314
- if (errors.length > 1) {
1315
- throw new AggregateError(errors, `Failed to enqueue activity ${activityId} to send later.`);
1328
+ else {
1329
+ try {
1330
+ await outboxQueue.enqueueMany(messages);
1331
+ }
1332
+ catch (error) {
1333
+ logger.error("Failed to enqueue activity {activityId} to send later: {error}", { activityId: activity.id.href, error });
1334
+ throw error;
1316
1335
  }
1317
- throw errors[0];
1318
1336
  }
1319
1337
  }
1320
1338
  fetch(request, options) {
@@ -2511,7 +2529,7 @@ export class InboxContextImpl extends ContextImpl {
2511
2529
  }
2512
2530
  const carrier = {};
2513
2531
  propagation.inject(context.active(), carrier);
2514
- const promises = [];
2532
+ const messages = [];
2515
2533
  for (const inbox in inboxes) {
2516
2534
  const message = {
2517
2535
  type: "outbox",
@@ -2528,18 +2546,31 @@ export class InboxContextImpl extends ContextImpl {
2528
2546
  headers: {},
2529
2547
  traceContext: carrier,
2530
2548
  };
2531
- promises.push(this.federation.outboxQueue.enqueue(message));
2532
- }
2533
- const results = await Promise.allSettled(promises);
2534
- const errors = results
2535
- .filter((r) => r.status === "rejected")
2536
- .map((r) => r.reason);
2537
- if (errors.length > 0) {
2538
- logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", { activityId: this.activityId, errors });
2539
- if (errors.length > 1) {
2540
- throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
2541
- }
2542
- throw errors[0];
2549
+ messages.push(message);
2550
+ }
2551
+ const { outboxQueue } = this.federation;
2552
+ if (outboxQueue.enqueueMany == null) {
2553
+ const promises = messages.map((m) => outboxQueue.enqueue(m));
2554
+ const results = await Promise.allSettled(promises);
2555
+ const errors = results
2556
+ .filter((r) => r.status === "rejected")
2557
+ .map((r) => r.reason);
2558
+ if (errors.length > 0) {
2559
+ logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", { activityId: this.activityId, errors });
2560
+ if (errors.length > 1) {
2561
+ throw new AggregateError(errors, `Failed to enqueue activity ${this.activityId} to forward later.`);
2562
+ }
2563
+ throw errors[0];
2564
+ }
2565
+ }
2566
+ else {
2567
+ try {
2568
+ await outboxQueue.enqueueMany(messages);
2569
+ }
2570
+ catch (error) {
2571
+ logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", { activityId: this.activityId, error });
2572
+ throw error;
2573
+ }
2543
2574
  }
2544
2575
  }
2545
2576
  }
@@ -39,6 +39,22 @@ export class InProcessMessageQueue {
39
39
  }
40
40
  return Promise.resolve();
41
41
  }
42
+ enqueueMany(messages, options) {
43
+ if (messages.length === 0)
44
+ return Promise.resolve();
45
+ const delay = options?.delay == null
46
+ ? 0
47
+ : Math.max(options.delay.total("millisecond"), 0);
48
+ if (delay > 0) {
49
+ setTimeout(() => this.enqueueMany(messages, { ...options, delay: undefined }), delay);
50
+ return Promise.resolve();
51
+ }
52
+ this.#messages.push(...messages);
53
+ for (const monitorId in this.#monitors) {
54
+ this.#monitors[monitorId]();
55
+ }
56
+ return Promise.resolve();
57
+ }
42
58
  async listen(handler, options = {}) {
43
59
  const signal = options.signal;
44
60
  while (signal == null || !signal.aborted) {
@@ -107,6 +123,21 @@ export class ParallelMessageQueue {
107
123
  enqueue(message, options) {
108
124
  return this.queue.enqueue(message, options);
109
125
  }
126
+ async enqueueMany(messages, options) {
127
+ if (this.queue.enqueueMany == null) {
128
+ const results = await Promise.allSettled(messages.map((message) => this.queue.enqueue(message, options)));
129
+ const errors = results
130
+ .filter((r) => r.status === "rejected")
131
+ .map((r) => r.reason);
132
+ if (errors.length > 1) {
133
+ throw new AggregateError(errors, "Failed to enqueue messages.");
134
+ }
135
+ else if (errors.length === 1)
136
+ throw errors[0];
137
+ return;
138
+ }
139
+ await this.queue.enqueueMany(messages, options);
140
+ }
110
141
  listen(handler, options = {}) {
111
142
  const workers = new Map();
112
143
  return this.queue.listen(async (message) => {