@fedify/fedify 2.3.0-dev.1114 → 2.3.0-dev.1131

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 (69) hide show
  1. package/dist/{builder-YlEusQth.mjs → builder-DckAhD27.mjs} +2 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{context-cSUMk2da.d.ts → context-Cq18Gplu.d.ts} +3 -208
  6. package/dist/{context-Ch-ZLyTQ.d.cts → context-tc6VOOOL.d.cts} +3 -208
  7. package/dist/{deno-CF3jMgip.mjs → deno--CS-SBS9.mjs} +1 -1
  8. package/dist/{docloader-BENj6vQ4.mjs → docloader-k6huZLQL.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +2 -2
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.d.mts +2 -0
  13. package/dist/federation/metrics.test.mjs +335 -0
  14. package/dist/federation/middleware.test.mjs +444 -6
  15. package/dist/federation/mod.cjs +1 -1
  16. package/dist/federation/mod.d.cts +3 -2
  17. package/dist/federation/mod.d.ts +3 -2
  18. package/dist/federation/mod.js +1 -1
  19. package/dist/federation/send.test.mjs +3 -3
  20. package/dist/federation/webfinger.test.mjs +1 -1
  21. package/dist/{http-CKCgOPkX.cjs → http-CJfvRL7D.cjs} +352 -22
  22. package/dist/{http-BmOZYc-8.mjs → http-IywnQdiX.mjs} +7 -5
  23. package/dist/{http-D6LP89UO.d.ts → http-VyDTd4G3.d.cts} +8 -1
  24. package/dist/{http-CpzZ9zsb.js → http-cqujdCRz.js} +323 -23
  25. package/dist/{http-D6aw3j2U.d.cts → http-lf8Hsd91.d.ts} +8 -1
  26. package/dist/{key-B4I8H5Lc.mjs → key-Df3tMleh.mjs} +42 -17
  27. package/dist/{kv-cache-DY-XWOqM.cjs → kv-cache-L0SMQkcd.cjs} +19 -2
  28. package/dist/{kv-cache-Wc5ezcVW.js → kv-cache-pEejzYq4.js} +19 -2
  29. package/dist/{kv-cache-DihufyAQ.mjs → kv-cache-q9Ec2ryS.mjs} +19 -1
  30. package/dist/{ld-B5D5THhl.mjs → ld-BGwiJpl3.mjs} +3 -3
  31. package/dist/{metrics-ek3ilf6c.mjs → metrics-BTOMkW8C.mjs} +280 -5
  32. package/dist/{middleware-EqTYPG4F.cjs → middleware-B2rtdpFV.cjs} +75 -28
  33. package/dist/{middleware-DlcecZMq.mjs → middleware-BB0IbDow.mjs} +84 -37
  34. package/dist/{middleware-EI7OU6BR.mjs → middleware-Dnql59Y8.mjs} +1 -1
  35. package/dist/{middleware-CuZbBw-N.js → middleware-DtOddSVg.js} +75 -28
  36. package/dist/{mod-BDhgfjP7.d.cts → mod-B0hW12_O.d.cts} +1 -1
  37. package/dist/{mod-B-Lin9Sy.d.ts → mod-COIAjwRS.d.ts} +1 -1
  38. package/dist/{mod-C6E8rkcz.d.ts → mod-CajNYYkt.d.ts} +1 -1
  39. package/dist/{mod-DLrRb0dx.d.ts → mod-DFvNJcNb.d.ts} +54 -3
  40. package/dist/{mod-P9tE2WmM.d.cts → mod-DnzgcPcy.d.cts} +1 -1
  41. package/dist/{mod-BR_BB0bh.d.cts → mod-yvIXFAEi.d.cts} +54 -3
  42. package/dist/mod.cjs +4 -4
  43. package/dist/mod.d.cts +6 -5
  44. package/dist/mod.d.ts +6 -5
  45. package/dist/mod.js +4 -4
  46. package/dist/mq-D-nlpY04.d.ts +208 -0
  47. package/dist/mq-D8uSFzxe.d.cts +208 -0
  48. package/dist/nodeinfo/handler.test.mjs +1 -1
  49. package/dist/{owner-DO810N24.mjs → owner-CIt4hvmM.mjs} +2 -2
  50. package/dist/{proof-DIoqrKnX.cjs → proof-B1_u25UV.cjs} +1 -1
  51. package/dist/{proof-BgfyWv7b.mjs → proof-BYlrRSmZ.mjs} +3 -3
  52. package/dist/{proof-Vd8-1EWh.js → proof-DMGIjHYH.js} +1 -1
  53. package/dist/{send-CAYXdUTk.mjs → send-DJFpze7B.mjs} +3 -3
  54. package/dist/sig/http.test.mjs +6 -2
  55. package/dist/sig/key.test.mjs +99 -2
  56. package/dist/sig/ld.test.mjs +2 -2
  57. package/dist/sig/mod.cjs +2 -2
  58. package/dist/sig/mod.d.cts +2 -2
  59. package/dist/sig/mod.d.ts +2 -2
  60. package/dist/sig/mod.js +2 -2
  61. package/dist/sig/owner.test.mjs +1 -1
  62. package/dist/sig/proof.test.mjs +1 -1
  63. package/dist/utils/docloader.test.mjs +2 -2
  64. package/dist/utils/kv-cache.test.mjs +67 -2
  65. package/dist/utils/mod.cjs +1 -1
  66. package/dist/utils/mod.d.cts +1 -1
  67. package/dist/utils/mod.d.ts +1 -1
  68. package/dist/utils/mod.js +1 -1
  69. package/package.json +6 -6
@@ -1,8 +1,9 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CF3jMgip.mjs";
4
+ import { n as version, t as name } from "./deno--CS-SBS9.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
+ import { FetchError } from "@fedify/vocab-runtime";
6
7
  //#region src/federation/metrics.ts
7
8
  var FederationMetrics = class {
8
9
  deliverySent;
@@ -20,6 +21,14 @@ var FederationMetrics = class {
20
21
  queueTaskFailed;
21
22
  queueTaskDuration;
22
23
  queueTaskInFlight;
24
+ fanoutRecipients;
25
+ inboxActivity;
26
+ outboxActivity;
27
+ keyLookup;
28
+ keyLookupDuration;
29
+ documentFetch;
30
+ documentFetchDuration;
31
+ documentCache;
23
32
  constructor(meterProvider) {
24
33
  const meter = meterProvider.getMeter(name, version);
25
34
  this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
@@ -114,6 +123,70 @@ var FederationMetrics = class {
114
123
  description: "Queue tasks currently being processed in this Fedify process.",
115
124
  unit: "{task}"
116
125
  });
126
+ this.fanoutRecipients = meter.createHistogram("activitypub.fanout.recipients", {
127
+ description: "Number of recipient inboxes produced by an ActivityPub fanout task.",
128
+ unit: "{recipient}"
129
+ });
130
+ this.inboxActivity = meter.createCounter("activitypub.inbox.activity", {
131
+ description: "ActivityPub activities observed at the inbox lifecycle level: queued, processed, retried, rejected, or abandoned.",
132
+ unit: "{activity}"
133
+ });
134
+ this.outboxActivity = meter.createCounter("activitypub.outbox.activity", {
135
+ description: "ActivityPub activities observed at the outbox lifecycle level: queued, retried, or abandoned. Per-recipient delivery counters live on `activitypub.delivery.*`.",
136
+ unit: "{activity}"
137
+ });
138
+ this.keyLookup = meter.createCounter("activitypub.key.lookup", {
139
+ description: "Public-key lookup attempts performed by Fedify, including both cache hits and remote fetches.",
140
+ unit: "{lookup}"
141
+ });
142
+ this.keyLookupDuration = meter.createHistogram("activitypub.key.lookup.duration", {
143
+ description: "Duration of public-key lookups performed by Fedify, including any remote fetch.",
144
+ unit: "ms",
145
+ advice: { explicitBucketBoundaries: [
146
+ 5,
147
+ 10,
148
+ 25,
149
+ 50,
150
+ 75,
151
+ 100,
152
+ 250,
153
+ 500,
154
+ 750,
155
+ 1e3,
156
+ 2500,
157
+ 5e3,
158
+ 7500,
159
+ 1e4
160
+ ] }
161
+ });
162
+ this.documentFetch = meter.createCounter("activitypub.document.fetch", {
163
+ description: "Remote JSON-LD document loader invocations made by Fedify-wrapped loaders.",
164
+ unit: "{fetch}"
165
+ });
166
+ this.documentFetchDuration = meter.createHistogram("activitypub.document.fetch.duration", {
167
+ description: "Duration of remote JSON-LD document loader invocations made by Fedify-wrapped loaders.",
168
+ unit: "ms",
169
+ advice: { explicitBucketBoundaries: [
170
+ 5,
171
+ 10,
172
+ 25,
173
+ 50,
174
+ 75,
175
+ 100,
176
+ 250,
177
+ 500,
178
+ 750,
179
+ 1e3,
180
+ 2500,
181
+ 5e3,
182
+ 7500,
183
+ 1e4
184
+ ] }
185
+ });
186
+ this.documentCache = meter.createCounter("activitypub.document.cache", {
187
+ description: "KV-backed document loader cache lookups, with `hit` or `miss` classification.",
188
+ unit: "{lookup}"
189
+ });
117
190
  }
118
191
  recordDelivery(inbox, durationMs, success, activityType) {
119
192
  const deliveryAttributes = {
@@ -186,7 +259,53 @@ var FederationMetrics = class {
186
259
  else if (result === "failed") this.queueTaskFailed.add(1, attributes);
187
260
  this.queueTaskDuration.record(durationMs, attributes);
188
261
  }
262
+ recordFanoutRecipients(recipientCount, activityType) {
263
+ const attributes = {};
264
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
265
+ this.fanoutRecipients.record(recipientCount, attributes);
266
+ }
267
+ recordInboxActivity(result, activityType) {
268
+ this.inboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
269
+ }
270
+ recordOutboxActivity(result, activityType) {
271
+ this.outboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
272
+ }
273
+ recordKeyLookup(attrs) {
274
+ const attributes = {
275
+ "activitypub.lookup.kind": "public_key",
276
+ "activitypub.lookup.result": attrs.result,
277
+ "activitypub.cache.enabled": attrs.cacheEnabled
278
+ };
279
+ if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
280
+ if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
281
+ this.keyLookup.add(1, attributes);
282
+ this.keyLookupDuration.record(attrs.durationMs, attributes);
283
+ }
284
+ recordDocumentFetch(attrs) {
285
+ const attributes = {
286
+ "activitypub.lookup.kind": attrs.kind,
287
+ "activitypub.lookup.result": attrs.result
288
+ };
289
+ if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
290
+ if (attrs.cacheEnabled != null) attributes["activitypub.cache.enabled"] = attrs.cacheEnabled;
291
+ if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
292
+ this.documentFetch.add(1, attributes);
293
+ this.documentFetchDuration.record(attrs.durationMs, attributes);
294
+ }
295
+ recordDocumentCache(attrs) {
296
+ const attributes = {
297
+ "activitypub.lookup.kind": attrs.kind,
298
+ "activitypub.lookup.result": attrs.result
299
+ };
300
+ if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
301
+ this.documentCache.add(1, attributes);
302
+ }
189
303
  };
304
+ function buildActivityLifecycleAttributes(result, activityType) {
305
+ const attributes = { "activitypub.processing.result": result };
306
+ if (activityType != null) attributes["activitypub.activity.type"] = activityType;
307
+ return attributes;
308
+ }
190
309
  function buildQueueTaskAttributes(common) {
191
310
  const attributes = { "fedify.queue.role": common.role };
192
311
  const backend = getQueueBackend(common.queue);
@@ -216,20 +335,176 @@ function getQueueBackend(queue) {
216
335
  return name;
217
336
  }
218
337
  /**
219
- * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue.
338
+ * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue and,
339
+ * for the initial attempt, also records
340
+ * `activitypub.outbox.activity{queued}`.
220
341
  *
221
342
  * Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
222
343
  * outbox messages with the same metric attributes (role, queue, activity
223
344
  * type, attempt), so they share this helper rather than each defining a local
224
- * closure.
345
+ * closure. Retry enqueues (attempt > 0) intentionally do not record a
346
+ * second `activitypub.outbox.activity{queued}`; retries are reported as
347
+ * `result=retried` from the retry-scheduling site, which has the failure
348
+ * context.
225
349
  * @since 2.3.0
226
350
  */
227
351
  function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
228
- getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
352
+ const metrics = getFederationMetrics(meterProvider);
353
+ metrics.recordQueueTaskEnqueued({
229
354
  role: "outbox",
230
355
  queue: outboxQueue,
231
356
  activityType: message.activityType
232
357
  }, message.attempt);
358
+ if (message.attempt === 0) metrics.recordOutboxActivity("queued", message.activityType);
359
+ }
360
+ /**
361
+ * Records `activitypub.fanout.recipients` with the number of recipient
362
+ * inboxes a single fanout produced. The histogram is unitless count
363
+ * (one measurement per fanout enqueue). Recipient URLs are deliberately
364
+ * not recorded; only the activity type, when known.
365
+ * @since 2.3.0
366
+ */
367
+ function recordFanoutRecipients(meterProvider, recipientCount, activityType) {
368
+ getFederationMetrics(meterProvider).recordFanoutRecipients(recipientCount, activityType);
369
+ }
370
+ /**
371
+ * Records one `activitypub.inbox.activity` measurement. The
372
+ * `activitypub.processing.result` attribute is always present;
373
+ * `activitypub.activity.type` is recorded only when Fedify already knows
374
+ * the activity type.
375
+ * @since 2.3.0
376
+ */
377
+ function recordInboxActivity(meterProvider, result, activityType) {
378
+ getFederationMetrics(meterProvider).recordInboxActivity(result, activityType);
379
+ }
380
+ /**
381
+ * Records one `activitypub.outbox.activity` measurement. The
382
+ * `activitypub.processing.result` attribute is always present;
383
+ * `activitypub.activity.type` is recorded only when Fedify already knows
384
+ * the activity type (it is always known for outbox lifecycle events).
385
+ * @since 2.3.0
386
+ */
387
+ function recordOutboxActivity(meterProvider, result, activityType) {
388
+ getFederationMetrics(meterProvider).recordOutboxActivity(result, activityType);
389
+ }
390
+ /**
391
+ * Records one measurement on `activitypub.key.lookup` (counter) and
392
+ * `activitypub.key.lookup.duration` (histogram) for a public-key lookup.
393
+ *
394
+ * `activitypub.lookup.kind` is always recorded as `public_key`; the result
395
+ * classification, remote host, HTTP status code (when an HTTP response was
396
+ * received), and `activitypub.cache.enabled` are recorded as attributes on
397
+ * both measurements. Full key URLs and key IDs are deliberately omitted to
398
+ * keep cardinality bounded.
399
+ * @since 2.3.0
400
+ */
401
+ function recordKeyLookup(meterProvider, attrs) {
402
+ getFederationMetrics(meterProvider).recordKeyLookup(attrs);
403
+ }
404
+ /**
405
+ * Records one measurement each on `activitypub.document.fetch` (counter)
406
+ * and `activitypub.document.fetch.duration` (histogram) for one remote
407
+ * JSON-LD document loader invocation, with bounded
408
+ * `activitypub.lookup.kind` and `activitypub.lookup.result` attributes
409
+ * plus the optional remote-host, cache-enabled, and HTTP status-code
410
+ * attributes. Counter and histogram are always recorded together so
411
+ * aggregate rate and latency views stay in sync.
412
+ * @since 2.3.0
413
+ */
414
+ function recordDocumentFetch(meterProvider, attrs) {
415
+ getFederationMetrics(meterProvider).recordDocumentFetch(attrs);
416
+ }
417
+ /**
418
+ * Records one `activitypub.document.cache` measurement, classifying the
419
+ * lookup as `hit` (the cache returned an entry) or `miss` (the cache was
420
+ * consulted and returned nothing, prompting a delegate fetch).
421
+ * @since 2.3.0
422
+ */
423
+ function recordDocumentCache(meterProvider, attrs) {
424
+ getFederationMetrics(meterProvider).recordDocumentCache(attrs);
425
+ }
426
+ /**
427
+ * Classifies a thrown value from a key or document fetch into the bounded
428
+ * {@link LookupResult} taxonomy and, when an HTTP response was received,
429
+ * surfaces its status code.
430
+ *
431
+ * - `FetchError` with a `Response` whose status is `404` or `410`:
432
+ * `result=not_found` and the response status code.
433
+ * - `FetchError` with any other `Response`: `result=error` and the
434
+ * response status code.
435
+ * - `FetchError` without a `Response`: `result=network_error`.
436
+ * - An `AbortError` (typically from a cancelled fetch): `result=network_error`.
437
+ * - A bare `TypeError` (the shape native `fetch()` raises on DNS, connect,
438
+ * and TLS failures before any response is observed):
439
+ * `result=network_error`.
440
+ * - Any other value: `result=error`.
441
+ * @since 2.3.0
442
+ */
443
+ function classifyFetchError(error) {
444
+ if (error instanceof FetchError) {
445
+ if (error.response != null) {
446
+ const status = error.response.status;
447
+ return {
448
+ result: status === 404 || status === 410 ? "not_found" : "error",
449
+ statusCode: status
450
+ };
451
+ }
452
+ return { result: "network_error" };
453
+ }
454
+ if (isAbortError(error)) return { result: "network_error" };
455
+ if (error instanceof TypeError) return { result: "network_error" };
456
+ return { result: "error" };
457
+ }
458
+ /**
459
+ * Wraps a {@link DocumentLoader} so each invocation records one
460
+ * measurement on `activitypub.document.fetch` (counter) and one on
461
+ * `activitypub.document.fetch.duration` (histogram), classifying the
462
+ * outcome via {@link classifyFetchError} when the wrapped loader throws
463
+ * and as `fetched` on success. The wrapper rethrows whatever the
464
+ * wrapped loader throws so caller behavior is unchanged.
465
+ *
466
+ * The wrapper records the hostname of the requested URL on
467
+ * `activitypub.remote.host` when the URL parses; full URLs, paths, and
468
+ * query strings are deliberately excluded to keep cardinality bounded.
469
+ * HTTP status codes are recorded only when the failure carries a
470
+ * `Response` (currently, when the wrapped loader throws a
471
+ * {@link FetchError} with a non-`null` `response`).
472
+ * @since 2.3.0
473
+ */
474
+ function instrumentDocumentLoader(loader, options) {
475
+ const meterProvider = options.meterProvider;
476
+ if (meterProvider == null) return loader;
477
+ return async (url, opts) => {
478
+ const start = performance.now();
479
+ let remoteUrl;
480
+ try {
481
+ remoteUrl = new URL(url);
482
+ } catch {
483
+ remoteUrl = void 0;
484
+ }
485
+ try {
486
+ const result = await loader(url, opts);
487
+ recordDocumentFetch(meterProvider, {
488
+ durationMs: getDurationMs(start),
489
+ kind: options.kind,
490
+ result: "fetched",
491
+ remoteUrl,
492
+ cacheEnabled: options.cacheEnabled
493
+ });
494
+ return result;
495
+ } catch (error) {
496
+ const classified = classifyFetchError(error);
497
+ recordDocumentFetch(meterProvider, {
498
+ durationMs: getDurationMs(start),
499
+ kind: options.kind,
500
+ result: classified.result,
501
+ remoteUrl,
502
+ cacheEnabled: options.cacheEnabled,
503
+ statusCode: classified.statusCode
504
+ });
505
+ throw error;
506
+ }
507
+ };
233
508
  }
234
509
  /**
235
510
  * Times an awaited public key fetch and records exactly one
@@ -311,4 +586,4 @@ function getDurationMs(start) {
311
586
  return Math.max(0, performance.now() - start);
312
587
  }
313
588
  //#endregion
314
- export { measureSignatureKeyFetch as a, isAbortError as i, getFederationMetrics as n, recordOutboxEnqueue as o, getRemoteHost as r, getDurationMs as t };
589
+ export { instrumentDocumentLoader as a, recordDocumentCache as c, recordInboxActivity as d, recordKeyLookup as f, getRemoteHost as i, recordDocumentFetch as l, recordOutboxEnqueue as m, getDurationMs as n, isAbortError as o, recordOutboxActivity as p, getFederationMetrics as r, measureSignatureKeyFetch as s, classifyFetchError as t, recordFanoutRecipients as u };
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-CKCgOPkX.cjs");
6
- const require_proof = require("./proof-DIoqrKnX.cjs");
5
+ const require_http = require("./http-CJfvRL7D.cjs");
6
+ const require_proof = require("./proof-B1_u25UV.cjs");
7
7
  const require_types = require("./types-KC4QAoxe.cjs");
8
- const require_kv_cache = require("./kv-cache-DY-XWOqM.cjs");
8
+ const require_kv_cache = require("./kv-cache-L0SMQkcd.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -817,6 +817,7 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
817
817
  code: _opentelemetry_api.SpanStatusCode.UNSET,
818
818
  message: `Activity ${activity.id?.href} has already been processed.`
819
819
  });
820
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
820
821
  return "alreadyProcessed";
821
822
  }
822
823
  }
@@ -826,6 +827,7 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
826
827
  code: _opentelemetry_api.SpanStatusCode.ERROR,
827
828
  message: "Missing actor."
828
829
  });
830
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
829
831
  return "missingActor";
830
832
  }
831
833
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
@@ -861,6 +863,7 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
861
863
  queue,
862
864
  activityType: (0, _fedify_vocab.getTypeId)(activity).href
863
865
  }, 0);
866
+ require_http.recordInboxActivity(meterProvider, "queued", (0, _fedify_vocab.getTypeId)(activity).href);
864
867
  logger.info("Activity {activityId} is enqueued.", {
865
868
  activityId: activity.id?.href,
866
869
  activity: json,
@@ -880,6 +883,7 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
880
883
  code: _opentelemetry_api.SpanStatusCode.UNSET,
881
884
  message: `Unsupported activity type: ${(0, _fedify_vocab.getTypeId)(activity).href}`
882
885
  });
886
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
883
887
  span.end();
884
888
  return "unsupportedActivity";
885
889
  }
@@ -889,7 +893,7 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
889
893
  const activityType = (0, _fedify_vocab.getTypeId)(activity).href;
890
894
  const started = performance.now();
891
895
  try {
892
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, activityType), activity);
896
+ await listener(inboxContextFactory(recipient, json, activity.id?.href, activityType), activity);
893
897
  } finally {
894
898
  require_http.getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
895
899
  }
@@ -914,9 +918,11 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
914
918
  code: _opentelemetry_api.SpanStatusCode.ERROR,
915
919
  message: String(error)
916
920
  });
921
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
917
922
  span.end();
918
923
  return "error";
919
924
  }
925
+ require_http.recordInboxActivity(meterProvider, "processed", (0, _fedify_vocab.getTypeId)(activity).href);
920
926
  if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
921
927
  logger.info("Activity {activityId} has been processed.", {
922
928
  activityId: activity.id?.href,
@@ -2907,23 +2913,50 @@ var FederationImpl = class extends FederationBuilderImpl {
2907
2913
  }
2908
2914
  const { allowPrivateAddress, userAgent } = options;
2909
2915
  this.allowPrivateAddress = allowPrivateAddress ?? false;
2910
- this.documentLoaderFactory = options.documentLoaderFactory ?? ((opts) => {
2911
- return require_kv_cache.kvCache({
2912
- loader: (0, _fedify_vocab_runtime.getDocumentLoader)({
2913
- allowPrivateAddress: opts?.allowPrivateAddress ?? allowPrivateAddress,
2914
- userAgent: opts?.userAgent ?? userAgent
2915
- }),
2916
- kv: options.kv,
2917
- prefix: this.kvPrefixes.remoteDocument
2918
- });
2916
+ const userDocumentLoaderFactory = options.documentLoaderFactory;
2917
+ const userContextLoaderFactory = options.contextLoaderFactory;
2918
+ const userAuthFactory = options.authenticatedDocumentLoaderFactory;
2919
+ const builtinDocumentLoaderFactory = (opts) => require_kv_cache.kvCache({
2920
+ loader: (0, _fedify_vocab_runtime.getDocumentLoader)({
2921
+ allowPrivateAddress: opts?.allowPrivateAddress ?? allowPrivateAddress,
2922
+ userAgent: opts?.userAgent ?? userAgent
2923
+ }),
2924
+ kv: options.kv,
2925
+ prefix: this.kvPrefixes.remoteDocument,
2926
+ meterProvider: this._meterProvider,
2927
+ kind: "object"
2928
+ });
2929
+ const builtinContextLoaderFactory = (opts) => require_kv_cache.kvCache({
2930
+ loader: (0, _fedify_vocab_runtime.getDocumentLoader)({
2931
+ allowPrivateAddress: opts?.allowPrivateAddress ?? allowPrivateAddress,
2932
+ userAgent: opts?.userAgent ?? userAgent
2933
+ }),
2934
+ kv: options.kv,
2935
+ prefix: this.kvPrefixes.remoteDocument,
2936
+ meterProvider: this._meterProvider,
2937
+ kind: "context"
2938
+ });
2939
+ this.documentLoaderFactory = (opts) => require_http.instrumentDocumentLoader((userDocumentLoaderFactory ?? builtinDocumentLoaderFactory)(opts), {
2940
+ meterProvider: this._meterProvider,
2941
+ kind: "object",
2942
+ cacheEnabled: userDocumentLoaderFactory == null ? true : void 0
2943
+ });
2944
+ const resolvedContextLoaderFactory = userContextLoaderFactory ?? userDocumentLoaderFactory ?? builtinContextLoaderFactory;
2945
+ this.contextLoaderFactory = (opts) => require_http.instrumentDocumentLoader(resolvedContextLoaderFactory(opts), {
2946
+ meterProvider: this._meterProvider,
2947
+ kind: "context",
2948
+ cacheEnabled: userContextLoaderFactory == null && userDocumentLoaderFactory == null ? true : void 0
2919
2949
  });
2920
- this.contextLoaderFactory = options.contextLoaderFactory ?? this.documentLoaderFactory;
2921
- this.authenticatedDocumentLoaderFactory = options.authenticatedDocumentLoaderFactory ?? ((identity) => require_kv_cache.getAuthenticatedDocumentLoader(identity, {
2922
- allowPrivateAddress,
2923
- userAgent,
2950
+ this.authenticatedDocumentLoaderFactory = (identity, factoryOpts) => require_http.instrumentDocumentLoader(userAuthFactory != null ? userAuthFactory(identity, factoryOpts) : require_kv_cache.getAuthenticatedDocumentLoader(identity, {
2951
+ allowPrivateAddress: factoryOpts?.allowPrivateAddress ?? allowPrivateAddress,
2952
+ userAgent: factoryOpts?.userAgent ?? userAgent,
2924
2953
  specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, options.firstKnock),
2925
2954
  tracerProvider: this.tracerProvider
2926
- }));
2955
+ }), {
2956
+ meterProvider: this._meterProvider,
2957
+ kind: "object",
2958
+ cacheEnabled: userAuthFactory == null ? false : void 0
2959
+ });
2927
2960
  this.userAgent = userAgent;
2928
2961
  this.onOutboxError = options.onOutboxError;
2929
2962
  this.permanentFailureStatusCodes = options.permanentFailureStatusCodes ?? [404, 410];
@@ -3233,6 +3266,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3233
3266
  });
3234
3267
  }
3235
3268
  }
3269
+ require_http.recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
3236
3270
  return;
3237
3271
  }
3238
3272
  if (this.outboxQueue?.nativeRetrial) {
@@ -3263,11 +3297,15 @@ var FederationImpl = class extends FederationBuilderImpl {
3263
3297
  queue: outboxQueue,
3264
3298
  activityType: retryMessage.activityType
3265
3299
  }, retryMessage.attempt);
3300
+ require_http.recordOutboxActivity(this.meterProvider, "retried", retryMessage.activityType);
3266
3301
  }
3267
- } else logger.error("Failed to send activity {activityId} to {inbox} after {attempt} attempts; giving up:\n{error}", {
3268
- ...logData,
3269
- error
3270
- });
3302
+ } else {
3303
+ logger.error("Failed to send activity {activityId} to {inbox} after {attempt} attempts; giving up:\n{error}", {
3304
+ ...logData,
3305
+ error
3306
+ });
3307
+ require_http.recordOutboxActivity(this.meterProvider, "abandoned", message.activityType);
3308
+ }
3271
3309
  return;
3272
3310
  }
3273
3311
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
@@ -3302,6 +3340,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3302
3340
  activity: message.activity,
3303
3341
  recipient: message.identifier
3304
3342
  });
3343
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
3305
3344
  return;
3306
3345
  }
3307
3346
  }
@@ -3318,6 +3357,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3318
3357
  code: _opentelemetry_api.SpanStatusCode.ERROR,
3319
3358
  message: `Unsupported activity type: ${activityType}`
3320
3359
  });
3360
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
3321
3361
  span.end();
3322
3362
  return;
3323
3363
  }
@@ -3330,6 +3370,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3330
3370
  } finally {
3331
3371
  require_http.getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
3332
3372
  }
3373
+ require_http.recordInboxActivity(this.meterProvider, "processed", activityType);
3333
3374
  } catch (error) {
3334
3375
  try {
3335
3376
  await this.inboxErrorHandler?.(context, error);
@@ -3380,13 +3421,17 @@ var FederationImpl = class extends FederationBuilderImpl {
3380
3421
  queue: inboxQueue,
3381
3422
  activityType
3382
3423
  }, retryMessage.attempt);
3424
+ require_http.recordInboxActivity(this.meterProvider, "retried", activityType);
3383
3425
  }
3384
- } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
3385
- error,
3386
- activityId: activity.id?.href,
3387
- activity: message.activity,
3388
- recipient: message.identifier
3389
- });
3426
+ } else {
3427
+ logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
3428
+ error,
3429
+ activityId: activity.id?.href,
3430
+ activity: message.activity,
3431
+ recipient: message.identifier
3432
+ });
3433
+ require_http.recordInboxActivity(this.meterProvider, "abandoned", activityType);
3434
+ }
3390
3435
  span.setStatus({
3391
3436
  code: _opentelemetry_api.SpanStatusCode.ERROR,
3392
3437
  message: String(error)
@@ -4226,6 +4271,7 @@ var ContextImpl = class ContextImpl {
4226
4271
  contextLoader: options.contextLoader ?? this.contextLoader,
4227
4272
  userAgent: options.userAgent ?? this.federation.userAgent,
4228
4273
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
4274
+ meterProvider: options.meterProvider ?? this.meterProvider,
4229
4275
  allowPrivateAddress: this.federation.allowPrivateAddress
4230
4276
  });
4231
4277
  }
@@ -4413,6 +4459,7 @@ var ContextImpl = class ContextImpl {
4413
4459
  queue: this.federation.fanoutQueue,
4414
4460
  activityType: message.activityType
4415
4461
  }, 0);
4462
+ require_http.recordFanoutRecipients(this.federation.meterProvider, globalThis.Object.keys(message.inboxes).length, message.activityType);
4416
4463
  return true;
4417
4464
  }
4418
4465
  async *getFollowers(identifier) {