@fedify/fedify 2.3.0-dev.1069 → 2.3.0-dev.1099

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 (113) hide show
  1. package/dist/{builder-DQ2zYTeA.mjs → builder-BkRRjxzb.mjs} +4 -4
  2. package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
  3. package/dist/{chunk-QSgtlS85.mjs → chunk-DNRtMIoB.mjs} +1 -1
  4. package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
  5. package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
  6. package/dist/compat/mod.d.cts +2 -1
  7. package/dist/compat/mod.d.ts +2 -3
  8. package/dist/compat/mod.js +3 -3
  9. package/dist/compat/outgoing-jsonld.test.mjs +2 -2
  10. package/dist/compat/public-audience.test.mjs +2 -2
  11. package/dist/compat/transformers.test.mjs +2 -2
  12. package/dist/{context-7Azky82W.mjs → context-BAE7AKLA.mjs} +61 -3
  13. package/dist/{context-DrNqYkPw.d.ts → context-C0C_sRha.d.cts} +6 -7
  14. package/dist/{context-BKLGj9QO.d.cts → context-Dqgt8saU.d.ts} +6 -5
  15. package/dist/{deno-CFXqOz6w.mjs → deno-DBabeupC.mjs} +1 -1
  16. package/dist/{docloader-QNtAtTZF.mjs → docloader-DA5FzJOR.mjs} +3 -3
  17. package/dist/{esm-DhnRLoG9.mjs → esm-sdtqOUPu.mjs} +5 -7
  18. package/dist/{execAsync-eck5rbtb.mjs → execAsync-DCBrgFiV.mjs} +2 -2
  19. package/dist/federation/builder.test.mjs +3 -3
  20. package/dist/federation/handler.test.mjs +86 -4
  21. package/dist/federation/idempotency.test.mjs +3 -3
  22. package/dist/federation/inbox.test.mjs +1 -1
  23. package/dist/federation/keycache.test.mjs +1 -1
  24. package/dist/federation/middleware.test.mjs +387 -20
  25. package/dist/federation/mod.cjs +1 -1
  26. package/dist/federation/mod.d.cts +4 -3
  27. package/dist/federation/mod.d.ts +4 -5
  28. package/dist/federation/mod.js +2 -2
  29. package/dist/federation/mq.test.mjs +1 -1
  30. package/dist/federation/negotiation.test.mjs +1 -1
  31. package/dist/federation/router.test.mjs +1 -1
  32. package/dist/federation/send.test.mjs +133 -139
  33. package/dist/federation/webfinger.test.mjs +3 -3
  34. package/dist/{getMachineId-bsd-DqZ4QRFp.mjs → getMachineId-bsd-etIyxDet.mjs} +3 -3
  35. package/dist/{getMachineId-darwin-DMbbW3m7.mjs → getMachineId-darwin-D23zTf4g.mjs} +3 -3
  36. package/dist/{getMachineId-linux-lyeD2ug3.mjs → getMachineId-linux-ObI47Hql.mjs} +2 -2
  37. package/dist/{getMachineId-unsupported-JuKr57jY.mjs → getMachineId-unsupported-Ddu-PFeh.mjs} +2 -2
  38. package/dist/{getMachineId-win-Dxyf5pJq.mjs → getMachineId-win-Dpap6v5i.mjs} +3 -3
  39. package/dist/{http-QHJGzUe8.mjs → http-5G18W3NP.mjs} +59 -11
  40. package/dist/{http-CrGuipxe.d.cts → http-BDZeS5om.d.ts} +2 -1
  41. package/dist/{http-aQzN9Ayi.d.ts → http-C87EWkO0.d.cts} +2 -3
  42. package/dist/{http-D2EDlTr2.js → http-Dzy5c472.js} +58 -10
  43. package/dist/{http-e1wtIlFo.cjs → http-W2u_KBoQ.cjs} +57 -9
  44. package/dist/{key-CpAxygvh.mjs → key-D9dUsyow.mjs} +2 -2
  45. package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
  46. package/dist/{kv-cache-B3GfB70S.cjs → kv-cache-BygrlQ1c.cjs} +2 -2
  47. package/dist/{kv-cache-KLjvIlKt.js → kv-cache-CBSgxEsZ.js} +2 -2
  48. package/dist/{kv-cache-Bmv7tUzz.mjs → kv-cache-CiiNwT6W.mjs} +2 -2
  49. package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
  50. package/dist/{ld-Ce_vkKjG.mjs → ld-hbxDLO1k.mjs} +3 -3
  51. package/dist/{middleware-DsGmXfXz.mjs → middleware-BXnhAGF9.mjs} +201 -119
  52. package/dist/{middleware-DOLrvK_b.cjs → middleware-Caj827xW.cjs} +358 -139
  53. package/dist/{middleware-BJMPv7_l.mjs → middleware-DZQsPMZb.mjs} +1 -1
  54. package/dist/{middleware-De241etq.js → middleware-vCF_cKAq.js} +346 -111
  55. package/dist/{mod-CLgIXe9w.d.ts → mod-B0rWmfW5.d.cts} +4 -5
  56. package/dist/{mod-CMEbIaNh.d.cts → mod-BhU_H1I_.d.ts} +4 -3
  57. package/dist/{mod-B8Z8mBLk.d.ts → mod-CLPnQPsv.d.cts} +2 -3
  58. package/dist/{mod-Cr3f-ACa.d.cts → mod-DHO9lk3D.d.ts} +3 -2
  59. package/dist/{mod-CR8soWa9.d.ts → mod-DXY9JF28.d.cts} +3 -4
  60. package/dist/{mod-DClCOv0M.d.cts → mod-Dx3-hqyo.d.ts} +2 -1
  61. package/dist/mod.cjs +4 -4
  62. package/dist/mod.d.cts +9 -8
  63. package/dist/mod.d.ts +9 -10
  64. package/dist/mod.js +9 -9
  65. package/dist/{negotiation-VnHNB0Q5.mjs → negotiation-DDstyBvc.mjs} +29 -0
  66. package/dist/nodeinfo/client.test.mjs +2 -2
  67. package/dist/nodeinfo/handler.test.mjs +2 -2
  68. package/dist/nodeinfo/mod.d.cts +2 -1
  69. package/dist/nodeinfo/mod.d.ts +2 -3
  70. package/dist/nodeinfo/mod.js +3 -3
  71. package/dist/otel/exporter.test.mjs +2 -2
  72. package/dist/otel/mod.d.cts +2 -1
  73. package/dist/otel/mod.d.ts +2 -3
  74. package/dist/otel/mod.js +2 -2
  75. package/dist/{outgoing-jsonld-Bi7n-dEy.mjs → outgoing-jsonld-BgFLCJQ_.mjs} +2 -2
  76. package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
  77. package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
  78. package/dist/{owner-DmgzyItA.mjs → owner-DwJe0BH9.mjs} +2 -2
  79. package/dist/{proof-BU1TpFYI.cjs → proof-CZCaAURh.cjs} +3 -3
  80. package/dist/{proof-DLDsFYfD.js → proof-DMJJZnKd.js} +2 -2
  81. package/dist/{proof-C3q2IhUr.mjs → proof-erpV_J_n.mjs} +5 -5
  82. package/dist/runtime/mod.d.cts +1 -0
  83. package/dist/runtime/mod.d.ts +1 -2
  84. package/dist/runtime/mod.js +3 -3
  85. package/dist/{send-CTi2iPXp.mjs → send-BOwz4Hw5.mjs} +128 -4
  86. package/dist/sig/http.test.mjs +140 -27
  87. package/dist/sig/key.test.mjs +2 -2
  88. package/dist/sig/ld.test.mjs +3 -3
  89. package/dist/sig/mod.cjs +2 -2
  90. package/dist/sig/mod.d.cts +4 -3
  91. package/dist/sig/mod.d.ts +4 -5
  92. package/dist/sig/mod.js +4 -4
  93. package/dist/sig/owner.test.mjs +2 -2
  94. package/dist/sig/proof.test.mjs +3 -3
  95. package/dist/testing/mod.d.mts +6 -1
  96. package/dist/testing/mod.mjs +1 -2
  97. package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
  98. package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
  99. package/dist/utils/docloader.test.mjs +3 -3
  100. package/dist/utils/kv-cache.test.mjs +2 -2
  101. package/dist/utils/mod.cjs +1 -1
  102. package/dist/utils/mod.d.cts +2 -1
  103. package/dist/utils/mod.d.ts +2 -3
  104. package/dist/utils/mod.js +3 -3
  105. package/dist/vocab/cjs.test.mjs +1 -1
  106. package/dist/vocab/mod.d.cts +1 -0
  107. package/dist/vocab/mod.d.ts +1 -2
  108. package/dist/vocab/mod.js +2 -2
  109. package/package.json +15 -15
  110. package/dist/middleware-CibncbiT.cjs +0 -4
  111. package/dist/{client-CIiz1WX7.mjs → client-B_A6mfn3.mjs} +1 -1
  112. package/dist/{public-audience-PVTwU_Ex.mjs → public-audience-N3pyOx2p.mjs} +1 -1
  113. /package/dist/{router-CrMLXoOr.mjs → router-BT_F5748.mjs} +0 -0
@@ -1,11 +1,11 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
- import "urlpattern-polyfill";
3
- import { t as __exportAll } from "./chunk-nlSIicah.js";
4
- import { r as getDefaultActivityTransformers } from "./transformers-ve6e2xcg.js";
5
- import { _ as version, a as verifyRequestDetailed, d as validateCryptoKey, f as formatAcceptSignature, g as name, i as verifyRequest, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, u as importJwk } from "./http-D2EDlTr2.js";
6
- import { c as getKeyOwner, d as detachSignature, f as hasSignatureLike, i as verifyObject, m as verifyJsonLd, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, p as signJsonLd, r as signObject, s as doesActorOwnKey } from "./proof-DLDsFYfD.js";
7
- import { n as getNodeInfo, t as nodeInfoToJson } from "./types-hvL8ElAs.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-KLjvIlKt.js";
2
+ import { URLPattern } from "urlpattern-polyfill";
3
+ import { t as __exportAll } from "./chunk-CRNNMoPX.js";
4
+ import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
5
+ import { _ as version, a as verifyRequestDetailed, d as validateCryptoKey, f as formatAcceptSignature, g as name, i as verifyRequest, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, u as importJwk } from "./http-Dzy5c472.js";
6
+ import { c as getKeyOwner, d as detachSignature, f as hasSignatureLike, i as verifyObject, m as verifyJsonLd, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, p as signJsonLd, r as signObject, s as doesActorOwnKey } from "./proof-DMJJZnKd.js";
7
+ import { n as getNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CBSgxEsZ.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
@@ -788,6 +788,12 @@ var FederationMetrics = class {
788
788
  inboxProcessingDuration;
789
789
  httpServerRequestCount;
790
790
  httpServerRequestDuration;
791
+ queueTaskEnqueued;
792
+ queueTaskStarted;
793
+ queueTaskCompleted;
794
+ queueTaskFailed;
795
+ queueTaskDuration;
796
+ queueTaskInFlight;
791
797
  constructor(meterProvider) {
792
798
  const meter = meterProvider.getMeter(name, version);
793
799
  this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
@@ -834,6 +840,46 @@ var FederationMetrics = class {
834
840
  1e4
835
841
  ] }
836
842
  });
843
+ this.queueTaskEnqueued = meter.createCounter("fedify.queue.task.enqueued", {
844
+ description: "Tasks Fedify enqueued for inbox, outbox, or fanout work.",
845
+ unit: "{task}"
846
+ });
847
+ this.queueTaskStarted = meter.createCounter("fedify.queue.task.started", {
848
+ description: "Tasks Fedify began processing as a queue worker.",
849
+ unit: "{task}"
850
+ });
851
+ this.queueTaskCompleted = meter.createCounter("fedify.queue.task.completed", {
852
+ description: "Queue tasks Fedify finished processing without throwing.",
853
+ unit: "{task}"
854
+ });
855
+ this.queueTaskFailed = meter.createCounter("fedify.queue.task.failed", {
856
+ description: "Queue tasks Fedify abandoned because processing threw.",
857
+ unit: "{task}"
858
+ });
859
+ this.queueTaskDuration = meter.createHistogram("fedify.queue.task.duration", {
860
+ description: "Duration of queue task processing in Fedify workers.",
861
+ unit: "ms",
862
+ advice: { explicitBucketBoundaries: [
863
+ 5,
864
+ 10,
865
+ 25,
866
+ 50,
867
+ 75,
868
+ 100,
869
+ 250,
870
+ 500,
871
+ 750,
872
+ 1e3,
873
+ 2500,
874
+ 5e3,
875
+ 7500,
876
+ 1e4
877
+ ] }
878
+ });
879
+ this.queueTaskInFlight = meter.createUpDownCounter("fedify.queue.task.in_flight", {
880
+ description: "Queue tasks currently being processed in this Fedify process.",
881
+ unit: "{task}"
882
+ });
837
883
  }
838
884
  recordDelivery(inbox, durationMs, success, activityType) {
839
885
  const deliveryAttributes = {
@@ -868,7 +914,85 @@ var FederationMetrics = class {
868
914
  this.httpServerRequestCount.add(1, attributes);
869
915
  this.httpServerRequestDuration.record(durationMs, attributes);
870
916
  }
917
+ recordQueueTaskEnqueued(common, attempt) {
918
+ const attributes = buildQueueTaskAttributes(common);
919
+ attributes["fedify.queue.task.attempt"] = attempt;
920
+ this.queueTaskEnqueued.add(1, attributes);
921
+ }
922
+ recordQueueTaskStarted(common) {
923
+ this.queueTaskStarted.add(1, buildQueueTaskAttributes(common));
924
+ }
925
+ incrementQueueTaskInFlight(common) {
926
+ this.queueTaskInFlight.add(1, buildQueueTaskInFlightAttributes(common));
927
+ }
928
+ decrementQueueTaskInFlight(common) {
929
+ this.queueTaskInFlight.add(-1, buildQueueTaskInFlightAttributes(common));
930
+ }
931
+ recordQueueTaskOutcome(common, result, durationMs) {
932
+ const attributes = buildQueueTaskAttributes(common);
933
+ attributes["fedify.queue.task.result"] = result;
934
+ if (result === "completed") this.queueTaskCompleted.add(1, attributes);
935
+ else if (result === "failed") this.queueTaskFailed.add(1, attributes);
936
+ this.queueTaskDuration.record(durationMs, attributes);
937
+ }
871
938
  };
939
+ function buildQueueTaskAttributes(common) {
940
+ const attributes = { "fedify.queue.role": common.role };
941
+ const backend = getQueueBackend(common.queue);
942
+ if (backend != null) attributes["fedify.queue.backend"] = backend;
943
+ const nativeRetrial = common.queue?.nativeRetrial;
944
+ if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
945
+ if (common.activityType != null) attributes["activitypub.activity.type"] = common.activityType;
946
+ return attributes;
947
+ }
948
+ function buildQueueTaskInFlightAttributes(common) {
949
+ return buildQueueTaskAttributes({
950
+ role: common.role,
951
+ queue: common.queue
952
+ });
953
+ }
954
+ /**
955
+ * Returns the constructor name of the given message queue, when it is a
956
+ * meaningful identifier. Used as a best-effort `fedify.queue.backend`
957
+ * attribute on queue task metrics; returns `undefined` for plain object
958
+ * literals (whose constructor is `Object`) so the attribute does not appear
959
+ * with a non-informative value.
960
+ * @since 2.3.0
961
+ */
962
+ function getQueueBackend(queue) {
963
+ const name = queue?.constructor?.name;
964
+ if (name == null || name === "" || name === "Object") return void 0;
965
+ return name;
966
+ }
967
+ /**
968
+ * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue.
969
+ *
970
+ * Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
971
+ * outbox messages with the same metric attributes (role, queue, activity
972
+ * type, attempt), so they share this helper rather than each defining a local
973
+ * closure.
974
+ * @since 2.3.0
975
+ */
976
+ function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
977
+ getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
978
+ role: "outbox",
979
+ queue: outboxQueue,
980
+ activityType: message.activityType
981
+ }, message.attempt);
982
+ }
983
+ /**
984
+ * Whether the given thrown value is an `AbortError`.
985
+ *
986
+ * `processQueuedTask` distinguishes aborted tasks (recorded as
987
+ * `fedify.queue.task.result=aborted`) from other failures so that backend
988
+ * shutdown signals do not inflate the `fedify.queue.task.failed` counter.
989
+ * @since 2.3.0
990
+ */
991
+ function isAbortError(error) {
992
+ if (error == null || typeof error !== "object") return false;
993
+ const name = error.name;
994
+ return typeof name === "string" && name === "AbortError";
995
+ }
872
996
  const KNOWN_HTTP_METHODS = new Set([
873
997
  "CONNECT",
874
998
  "DELETE",
@@ -990,6 +1114,11 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
990
1114
  });
991
1115
  throw error;
992
1116
  }
1117
+ getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
1118
+ role: "inbox",
1119
+ queue,
1120
+ activityType: getTypeId(activity).href
1121
+ }, 0);
993
1122
  logger.info("Activity {activityId} is enqueued.", {
994
1123
  activityId: activity.id?.href,
995
1124
  activity: json,
@@ -1150,6 +1279,35 @@ var KvKeyCache = class {
1150
1279
  };
1151
1280
  //#endregion
1152
1281
  //#region src/federation/negotiation.ts
1282
+ /*!
1283
+ * Adapted directly from negotiator at https://github.com/jshttp/negotiator/
1284
+ * which is licensed as follows:
1285
+ *
1286
+ * (The MIT License)
1287
+ *
1288
+ * Copyright (c) 2012-2014 Federico Romero
1289
+ * Copyright (c) 2012-2014 Isaac Z. Schlueter
1290
+ * Copyright (c) 2014-2015 Douglas Christopher Wilson
1291
+ *
1292
+ * Permission is hereby granted, free of charge, to any person obtaining
1293
+ * a copy of this software and associated documentation files (the
1294
+ * 'Software'), to deal in the Software without restriction, including
1295
+ * without limitation the rights to use, copy, modify, merge, publish,
1296
+ * distribute, sublicense, and/or sell copies of the Software, and to
1297
+ * permit persons to whom the Software is furnished to do so, subject to
1298
+ * the following conditions:
1299
+ *
1300
+ * The above copyright notice and this permission notice shall be
1301
+ * included in all copies or substantial portions of the Software.
1302
+ *
1303
+ * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
1304
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1305
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
1306
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
1307
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
1308
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
1309
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1310
+ */
1153
1311
  function compareSpecs(a, b) {
1154
1312
  return b.q - a.q || (b.s ?? 0) - (a.s ?? 0) || (a.o ?? 0) - (b.o ?? 0) || a.i - b.i || 0;
1155
1313
  }
@@ -2032,6 +2190,14 @@ async function _handleOrderedCollection(request, { name, values, context, tracer
2032
2190
  * @since 1.8.0
2033
2191
  */
2034
2192
  var CustomCollectionHandler = class {
2193
+ name;
2194
+ values;
2195
+ context;
2196
+ callbacks;
2197
+ tracerProvider;
2198
+ Collection;
2199
+ CollectionPage;
2200
+ filterPredicate;
2035
2201
  /**
2036
2202
  * The tracer for telemetry.
2037
2203
  * @type {Tracer}
@@ -3067,78 +3233,123 @@ var FederationImpl = class extends FederationBuilderImpl {
3067
3233
  processQueuedTask(contextData, message) {
3068
3234
  const tracer = this._getTracer();
3069
3235
  const extractedContext = propagation.extract(context.active(), message.traceContext);
3236
+ const meter = getFederationMetrics(this.meterProvider);
3070
3237
  return withContext({ messageId: message.id }, async () => {
3071
- if (message.type === "fanout") await tracer.startActiveSpan("activitypub.fanout", {
3072
- kind: SpanKind.CONSUMER,
3073
- attributes: { "activitypub.activity.type": message.activityType }
3074
- }, extractedContext, async (span) => {
3075
- const spanCtx = span.spanContext();
3076
- return await withContext({
3077
- traceId: spanCtx.traceId,
3078
- spanId: spanCtx.spanId
3079
- }, async () => {
3080
- if (message.activityId != null) span.setAttribute("activitypub.activity.id", message.activityId);
3081
- try {
3082
- await this.#listenFanoutMessage(contextData, message);
3083
- } catch (e) {
3084
- span.setStatus({
3085
- code: SpanStatusCode.ERROR,
3086
- message: String(e)
3087
- });
3088
- throw e;
3089
- } finally {
3090
- span.end();
3091
- }
3238
+ if (message.type === "fanout") {
3239
+ const common = {
3240
+ role: "fanout",
3241
+ queue: this.fanoutQueue,
3242
+ activityType: message.activityType
3243
+ };
3244
+ await tracer.startActiveSpan("activitypub.fanout", {
3245
+ kind: SpanKind.CONSUMER,
3246
+ attributes: { "activitypub.activity.type": message.activityType }
3247
+ }, extractedContext, async (span) => {
3248
+ const spanCtx = span.spanContext();
3249
+ return await withContext({
3250
+ traceId: spanCtx.traceId,
3251
+ spanId: spanCtx.spanId
3252
+ }, async () => {
3253
+ if (message.activityId != null) span.setAttribute("activitypub.activity.id", message.activityId);
3254
+ meter.recordQueueTaskStarted(common);
3255
+ meter.incrementQueueTaskInFlight(common);
3256
+ const startedAt = performance.now();
3257
+ let outcome = "completed";
3258
+ try {
3259
+ await this.#listenFanoutMessage(contextData, message);
3260
+ } catch (e) {
3261
+ const aborted = isAbortError(e);
3262
+ outcome = aborted ? "aborted" : "failed";
3263
+ if (!aborted) span.setStatus({
3264
+ code: SpanStatusCode.ERROR,
3265
+ message: String(e)
3266
+ });
3267
+ throw e;
3268
+ } finally {
3269
+ meter.recordQueueTaskOutcome(common, outcome, getDurationMs(startedAt));
3270
+ meter.decrementQueueTaskInFlight(common);
3271
+ span.end();
3272
+ }
3273
+ });
3092
3274
  });
3093
- });
3094
- else if (message.type === "outbox") await tracer.startActiveSpan("activitypub.outbox", {
3095
- kind: SpanKind.CONSUMER,
3096
- attributes: {
3097
- "activitypub.activity.type": message.activityType,
3098
- "activitypub.activity.retries": message.attempt
3099
- }
3100
- }, extractedContext, async (span) => {
3101
- const spanCtx = span.spanContext();
3102
- return await withContext({
3103
- traceId: spanCtx.traceId,
3104
- spanId: spanCtx.spanId
3105
- }, async () => {
3106
- if (message.activityId != null) span.setAttribute("activitypub.activity.id", message.activityId);
3107
- try {
3108
- await this.#listenOutboxMessage(contextData, message, span);
3109
- } catch (e) {
3110
- span.setStatus({
3111
- code: SpanStatusCode.ERROR,
3112
- message: String(e)
3113
- });
3114
- throw e;
3115
- } finally {
3116
- span.end();
3275
+ } else if (message.type === "outbox") {
3276
+ const common = {
3277
+ role: "outbox",
3278
+ queue: this.outboxQueue,
3279
+ activityType: message.activityType
3280
+ };
3281
+ await tracer.startActiveSpan("activitypub.outbox", {
3282
+ kind: SpanKind.CONSUMER,
3283
+ attributes: {
3284
+ "activitypub.activity.type": message.activityType,
3285
+ "activitypub.activity.retries": message.attempt
3117
3286
  }
3287
+ }, extractedContext, async (span) => {
3288
+ const spanCtx = span.spanContext();
3289
+ return await withContext({
3290
+ traceId: spanCtx.traceId,
3291
+ spanId: spanCtx.spanId
3292
+ }, async () => {
3293
+ if (message.activityId != null) span.setAttribute("activitypub.activity.id", message.activityId);
3294
+ meter.recordQueueTaskStarted(common);
3295
+ meter.incrementQueueTaskInFlight(common);
3296
+ const startedAt = performance.now();
3297
+ let outcome = "completed";
3298
+ try {
3299
+ await this.#listenOutboxMessage(contextData, message, span);
3300
+ } catch (e) {
3301
+ const aborted = isAbortError(e);
3302
+ outcome = aborted ? "aborted" : "failed";
3303
+ if (!aborted) span.setStatus({
3304
+ code: SpanStatusCode.ERROR,
3305
+ message: String(e)
3306
+ });
3307
+ throw e;
3308
+ } finally {
3309
+ meter.recordQueueTaskOutcome(common, outcome, getDurationMs(startedAt));
3310
+ meter.decrementQueueTaskInFlight(common);
3311
+ span.end();
3312
+ }
3313
+ });
3118
3314
  });
3119
- });
3120
- else if (message.type === "inbox") await tracer.startActiveSpan("activitypub.inbox", {
3121
- kind: SpanKind.CONSUMER,
3122
- attributes: { "activitypub.shared_inbox": message.identifier == null }
3123
- }, extractedContext, async (span) => {
3124
- const spanCtx = span.spanContext();
3125
- return await withContext({
3126
- traceId: spanCtx.traceId,
3127
- spanId: spanCtx.spanId
3128
- }, async () => {
3129
- try {
3130
- await this.#listenInboxMessage(contextData, message, span);
3131
- } catch (e) {
3132
- span.setStatus({
3133
- code: SpanStatusCode.ERROR,
3134
- message: String(e)
3135
- });
3136
- throw e;
3137
- } finally {
3138
- span.end();
3139
- }
3315
+ } else if (message.type === "inbox") {
3316
+ const common = {
3317
+ role: "inbox",
3318
+ queue: this.inboxQueue
3319
+ };
3320
+ await tracer.startActiveSpan("activitypub.inbox", {
3321
+ kind: SpanKind.CONSUMER,
3322
+ attributes: { "activitypub.shared_inbox": message.identifier == null }
3323
+ }, extractedContext, async (span) => {
3324
+ const spanCtx = span.spanContext();
3325
+ return await withContext({
3326
+ traceId: spanCtx.traceId,
3327
+ spanId: spanCtx.spanId
3328
+ }, async () => {
3329
+ meter.recordQueueTaskStarted(common);
3330
+ meter.incrementQueueTaskInFlight(common);
3331
+ const startedAt = performance.now();
3332
+ let outcome = "completed";
3333
+ try {
3334
+ await this.#listenInboxMessage(contextData, message, span, (activityType) => {
3335
+ common.activityType = activityType;
3336
+ });
3337
+ } catch (e) {
3338
+ const aborted = isAbortError(e);
3339
+ outcome = aborted ? "aborted" : "failed";
3340
+ if (!aborted) span.setStatus({
3341
+ code: SpanStatusCode.ERROR,
3342
+ message: String(e)
3343
+ });
3344
+ throw e;
3345
+ } finally {
3346
+ meter.recordQueueTaskOutcome(common, outcome, getDurationMs(startedAt));
3347
+ meter.decrementQueueTaskInFlight(common);
3348
+ span.end();
3349
+ }
3350
+ });
3140
3351
  });
3141
- });
3352
+ }
3142
3353
  });
3143
3354
  }
3144
3355
  async #listenFanoutMessage(data, message) {
@@ -3295,10 +3506,19 @@ var FederationImpl = class extends FederationBuilderImpl {
3295
3506
  ...logData,
3296
3507
  error
3297
3508
  });
3298
- await this.outboxQueue?.enqueue({
3509
+ const retryMessage = {
3299
3510
  ...message,
3300
3511
  attempt: message.attempt + 1
3301
- }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
3512
+ };
3513
+ const { outboxQueue } = this;
3514
+ if (outboxQueue != null) {
3515
+ await outboxQueue.enqueue(retryMessage, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
3516
+ getFederationMetrics(this.meterProvider).recordQueueTaskEnqueued({
3517
+ role: "outbox",
3518
+ queue: outboxQueue,
3519
+ activityType: retryMessage.activityType
3520
+ }, retryMessage.attempt);
3521
+ }
3302
3522
  } else logger.error("Failed to send activity {activityId} to {inbox} after {attempt} attempts; giving up:\n{error}", {
3303
3523
  ...logData,
3304
3524
  error
@@ -3307,7 +3527,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3307
3527
  }
3308
3528
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
3309
3529
  }
3310
- async #listenInboxMessage(ctxData, message, span) {
3530
+ async #listenInboxMessage(ctxData, message, span, onActivityType) {
3311
3531
  const logger = getLogger([
3312
3532
  "fedify",
3313
3533
  "federation",
@@ -3321,7 +3541,9 @@ var FederationImpl = class extends FederationBuilderImpl {
3321
3541
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3322
3542
  }
3323
3543
  const activity = await Activity.fromJsonLd(message.activity, context);
3324
- span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
3544
+ const activityType = getTypeId(activity).href;
3545
+ span.setAttribute("activitypub.activity.type", activityType);
3546
+ onActivityType?.(activityType);
3325
3547
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3326
3548
  const cacheKey = activity.id == null ? null : [
3327
3549
  ...this.kvPrefixes.activityIdempotence,
@@ -3349,7 +3571,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3349
3571
  });
3350
3572
  span.setStatus({
3351
3573
  code: SpanStatusCode.ERROR,
3352
- message: `Unsupported activity type: ${getTypeId(activity).href}`
3574
+ message: `Unsupported activity type: ${activityType}`
3353
3575
  });
3354
3576
  span.end();
3355
3577
  return;
@@ -3357,7 +3579,6 @@ var FederationImpl = class extends FederationBuilderImpl {
3357
3579
  const { class: cls, listener } = dispatched;
3358
3580
  span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3359
3581
  try {
3360
- const activityType = getTypeId(activity).href;
3361
3582
  const started = performance.now();
3362
3583
  try {
3363
3584
  await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
@@ -3402,10 +3623,19 @@ var FederationImpl = class extends FederationBuilderImpl {
3402
3623
  activity: message.activity,
3403
3624
  recipient: message.identifier
3404
3625
  });
3405
- await this.inboxQueue?.enqueue({
3626
+ const retryMessage = {
3406
3627
  ...message,
3407
3628
  attempt: message.attempt + 1
3408
- }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
3629
+ };
3630
+ const { inboxQueue } = this;
3631
+ if (inboxQueue != null) {
3632
+ await inboxQueue.enqueue(retryMessage, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
3633
+ getFederationMetrics(this.meterProvider).recordQueueTaskEnqueued({
3634
+ role: "inbox",
3635
+ queue: inboxQueue,
3636
+ activityType
3637
+ }, retryMessage.attempt);
3638
+ }
3409
3639
  } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
3410
3640
  error,
3411
3641
  activityId: activity.id?.href,
@@ -3594,9 +3824,11 @@ var FederationImpl = class extends FederationBuilderImpl {
3594
3824
  });
3595
3825
  }
3596
3826
  const { outboxQueue } = this;
3597
- if (outboxQueue.enqueueMany == null) {
3598
- const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
3599
- const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
3827
+ if (outboxQueue.enqueueMany == null || orderingKey != null) {
3828
+ const errors = (await Promise.allSettled(messages.map(async (m) => {
3829
+ await outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey });
3830
+ recordOutboxEnqueue(this.meterProvider, outboxQueue, m.message);
3831
+ }))).filter((r) => r.status === "rejected").map((r) => r.reason);
3600
3832
  if (errors.length > 0) {
3601
3833
  logger.error("Failed to enqueue activity {activityId} to send later: {errors}", {
3602
3834
  activityId: activity.id.href,
@@ -3605,25 +3837,17 @@ var FederationImpl = class extends FederationBuilderImpl {
3605
3837
  if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${activityId} to send later.`);
3606
3838
  throw errors[0];
3607
3839
  }
3608
- } else if (orderingKey != null) {
3609
- const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
3610
- const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
3611
- if (errors.length > 0) {
3612
- logger.error("Failed to enqueue activity {activityId} to send later: {errors}", {
3840
+ } else {
3841
+ try {
3842
+ await outboxQueue.enqueueMany(messages.map((m) => m.message));
3843
+ } catch (error) {
3844
+ logger.error("Failed to enqueue activity {activityId} to send later: {error}", {
3613
3845
  activityId: activity.id.href,
3614
- errors
3846
+ error
3615
3847
  });
3616
- if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${activityId} to send later.`);
3617
- throw errors[0];
3848
+ throw error;
3618
3849
  }
3619
- } else try {
3620
- await outboxQueue.enqueueMany(messages.map((m) => m.message));
3621
- } catch (error) {
3622
- logger.error("Failed to enqueue activity {activityId} to send later: {error}", {
3623
- activityId: activity.id.href,
3624
- error
3625
- });
3626
- throw error;
3850
+ for (const m of messages) recordOutboxEnqueue(this.meterProvider, outboxQueue, m.message);
3627
3851
  }
3628
3852
  }
3629
3853
  fetch(request, options) {
@@ -4439,6 +4663,11 @@ var ContextImpl = class ContextImpl {
4439
4663
  };
4440
4664
  if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
4441
4665
  await this.federation.fanoutQueue.enqueue(message, { orderingKey: options.orderingKey });
4666
+ getFederationMetrics(this.federation.meterProvider).recordQueueTaskEnqueued({
4667
+ role: "fanout",
4668
+ queue: this.federation.fanoutQueue,
4669
+ activityType: message.activityType
4670
+ }, 0);
4442
4671
  return true;
4443
4672
  }
4444
4673
  async *getFollowers(identifier) {
@@ -4569,6 +4798,7 @@ var ContextImpl = class ContextImpl {
4569
4798
  kvPrefixes: this.federation.kvPrefixes,
4570
4799
  queue: this.federation.inboxQueue,
4571
4800
  span,
4801
+ meterProvider: this.federation.meterProvider,
4572
4802
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
4573
4803
  idempotencyStrategy: this.federation.idempotencyStrategy
4574
4804
  });
@@ -4818,8 +5048,10 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
4818
5048
  }
4819
5049
  const { outboxQueue } = ctx.federation;
4820
5050
  if (outboxQueue.enqueueMany == null || orderingKey != null) {
4821
- const promises = messages.map((m) => outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey }));
4822
- const errors = (await Promise.allSettled(promises)).filter((r) => r.status === "rejected").map((r) => r.reason);
5051
+ const errors = (await Promise.allSettled(messages.map(async (m) => {
5052
+ await outboxQueue.enqueue(m.message, { orderingKey: m.orderingKey });
5053
+ recordOutboxEnqueue(ctx.federation.meterProvider, outboxQueue, m.message);
5054
+ }))).filter((r) => r.status === "rejected").map((r) => r.reason);
4823
5055
  if (errors.length > 0) {
4824
5056
  logger.error("Failed to enqueue activity {activityId} to forward later:\n{errors}", {
4825
5057
  activityId: ctx.activityId,
@@ -4828,14 +5060,17 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
4828
5060
  if (errors.length > 1) throw new AggregateError(errors, `Failed to enqueue activity ${ctx.activityId} to forward later.`);
4829
5061
  throw errors[0];
4830
5062
  }
4831
- } else try {
4832
- await outboxQueue.enqueueMany(messages.map((m) => m.message));
4833
- } catch (error) {
4834
- logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
4835
- activityId: ctx.activityId,
4836
- error
4837
- });
4838
- throw error;
5063
+ } else {
5064
+ try {
5065
+ await outboxQueue.enqueueMany(messages.map((m) => m.message));
5066
+ } catch (error) {
5067
+ logger.error("Failed to enqueue activity {activityId} to forward later:\n{error}", {
5068
+ activityId: ctx.activityId,
5069
+ error
5070
+ });
5071
+ throw error;
5072
+ }
5073
+ for (const m of messages) recordOutboxEnqueue(ctx.federation.meterProvider, outboxQueue, m.message);
4839
5074
  }
4840
5075
  return true;
4841
5076
  }
@@ -1,9 +1,8 @@
1
- import { Temporal } from "@js-temporal/polyfill";
2
- import { URLPattern } from "urlpattern-polyfill";
3
- import { n as HttpMessageSignaturesSpecDeterminer } from "./http-aQzN9Ayi.js";
4
- import { n as KvStore, t as KvKey } from "./kv-GFYnFoOl.js";
5
- import { TracerProvider } from "@opentelemetry/api";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { n as HttpMessageSignaturesSpecDeterminer } from "./http-C87EWkO0.cjs";
3
+ import { n as KvStore, t as KvKey } from "./kv-gJ8LYbxX.cjs";
6
4
  import { DocumentLoader, DocumentLoaderFactoryOptions } from "@fedify/vocab-runtime";
5
+ import { TracerProvider } from "@opentelemetry/api";
7
6
 
8
7
  //#region src/utils/docloader.d.ts
9
8
  /**
@@ -1,7 +1,8 @@
1
- import { n as HttpMessageSignaturesSpecDeterminer } from "./http-CrGuipxe.cjs";
2
- import { n as KvStore, t as KvKey } from "./kv-CbLNp3zQ.cjs";
3
- import { DocumentLoader, DocumentLoaderFactoryOptions } from "@fedify/vocab-runtime";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { n as HttpMessageSignaturesSpecDeterminer } from "./http-BDZeS5om.js";
3
+ import { n as KvStore, t as KvKey } from "./kv-D6hNiMTK.js";
4
4
  import { TracerProvider } from "@opentelemetry/api";
5
+ import { DocumentLoader, DocumentLoaderFactoryOptions } from "@fedify/vocab-runtime";
5
6
 
6
7
  //#region src/utils/docloader.d.ts
7
8
  /**
@@ -1,6 +1,5 @@
1
- import { Temporal } from "@js-temporal/polyfill";
2
- import { URLPattern } from "urlpattern-polyfill";
3
- import { Ct as WebFingerLinksDispatcher, et as ActorAliasMapper, l as RequestContext, nt as ActorHandleMapper, tt as ActorDispatcher } from "./context-DrNqYkPw.js";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { Ct as WebFingerLinksDispatcher, et as ActorAliasMapper, l as RequestContext, nt as ActorHandleMapper, tt as ActorDispatcher } from "./context-C0C_sRha.cjs";
4
3
  import { Span, Tracer } from "@opentelemetry/api";
5
4
 
6
5
  //#region src/federation/webfinger.d.ts
@@ -1,7 +1,8 @@
1
- import { S as KeyCache } from "./http-CrGuipxe.cjs";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { S as KeyCache } from "./http-BDZeS5om.js";
2
3
  import { CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1 } from "@fedify/vocab";
3
- import { DocumentLoader } from "@fedify/vocab-runtime";
4
4
  import { TracerProvider } from "@opentelemetry/api";
5
+ import { DocumentLoader } from "@fedify/vocab-runtime";
5
6
 
6
7
  //#region src/sig/ld.d.ts
7
8
  /**
@@ -1,9 +1,8 @@
1
- import { Temporal } from "@js-temporal/polyfill";
2
- import { URLPattern } from "urlpattern-polyfill";
3
- import { S as KeyCache } from "./http-aQzN9Ayi.js";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { S as KeyCache } from "./http-C87EWkO0.cjs";
4
3
  import { CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1 } from "@fedify/vocab";
5
- import { TracerProvider } from "@opentelemetry/api";
6
4
  import { DocumentLoader } from "@fedify/vocab-runtime";
5
+ import { TracerProvider } from "@opentelemetry/api";
7
6
 
8
7
  //#region src/sig/ld.d.ts
9
8
  /**