@fedify/fedify 2.5.0-dev.2448 → 2.5.0-dev.2494

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 (126) hide show
  1. package/README.md +3 -0
  2. package/dist/{builder-DbC8bn6k.mjs → builder-Y3pokaG2.mjs} +2 -2
  3. package/dist/{client-D-XP_Ccb.d.cts → client-3InDZbba.d.cts} +0 -1
  4. package/dist/{client-D-XP_Ccb.d.ts → client-3InDZbba.d.ts} +0 -1
  5. package/dist/{client-CEnNz8O7.mjs → client-5SIFzW3q.mjs} +113 -64
  6. package/dist/compat/mod.d.cts +1 -1
  7. package/dist/compat/mod.d.ts +1 -1
  8. package/dist/compat/transformers.test.mjs +1 -1
  9. package/dist/{compound-proof-heeXW2_A.mjs → compound-proof-BM7AINht.mjs} +2 -2
  10. package/dist/{context-GlHrzdSP.d.ts → context-DQJE9YfH.d.ts} +29 -3
  11. package/dist/{context-BazwQrHn.d.cts → context-DSuL3YWx.d.cts} +29 -3
  12. package/dist/{deno-Dpewt_jJ.mjs → deno-D7blYLbb.mjs} +1 -1
  13. package/dist/docloader-DbBbDaY-.mjs +136 -0
  14. package/dist/{exporter-DThFfe7P.mjs → exporter-DVG68ozH.mjs} +36 -30
  15. package/dist/federation/builder.test.mjs +1 -1
  16. package/dist/federation/compound-fanout.test.mjs +6 -3
  17. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  18. package/dist/federation/handler.test.mjs +5 -5
  19. package/dist/federation/hashlink-media.test.mjs +2 -2
  20. package/dist/federation/idempotency.test.mjs +2 -2
  21. package/dist/federation/inbox-actorless.test.mjs +5 -5
  22. package/dist/federation/inbox-report.test.mjs +5 -5
  23. package/dist/federation/inbox-view.test.mjs +1 -1
  24. package/dist/federation/inbox.test.mjs +1 -1
  25. package/dist/federation/metrics.test.mjs +1 -1
  26. package/dist/federation/middleware.test.mjs +468 -33
  27. package/dist/federation/mod.cjs +1 -1
  28. package/dist/federation/mod.d.cts +3 -3
  29. package/dist/federation/mod.d.ts +3 -3
  30. package/dist/federation/mod.js +1 -1
  31. package/dist/federation/mq.test.mjs +1 -1
  32. package/dist/federation/outgoing-proof.test.mjs +9 -6
  33. package/dist/federation/portable-actor.test.mjs +3 -3
  34. package/dist/federation/portable-chain.test.mjs +3 -3
  35. package/dist/federation/portable-collection.test.mjs +4 -4
  36. package/dist/federation/portable-delivery.test.mjs +2 -2
  37. package/dist/federation/portable-gateway-keys.test.mjs +11 -5
  38. package/dist/federation/portable-inbox.test.mjs +4 -4
  39. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  40. package/dist/federation/portable-route-activity.test.mjs +2 -2
  41. package/dist/federation/portable.test.mjs +3 -3
  42. package/dist/federation/send.test.mjs +502 -93
  43. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  44. package/dist/federation/tasks/tasks.test.mjs +2 -2
  45. package/dist/federation/temporal.test.mjs +1 -1
  46. package/dist/federation/webfinger.test.mjs +1 -1
  47. package/dist/{federation-D8YexkP1.cjs → federation-CFaTsSMN.cjs} +246 -108
  48. package/dist/{http-CAl7Num5.cjs → http-2SwIR4b-.cjs} +179 -110
  49. package/dist/{http-BBr-VLil.js → http-CrYxBKqG.js} +178 -109
  50. package/dist/{http-BZkyQIkb.mjs → http-CxKd10Lq.mjs} +204 -106
  51. package/dist/{inbox-wDuMKaFW.mjs → inbox-CM0UktG8.mjs} +2 -2
  52. package/dist/{inbox-view-NE9wXdLo.mjs → inbox-view-BJ5DBPFo.mjs} +1 -1
  53. package/dist/{key-nYGY4QB9.d.cts → key-DbLNT-vA.d.ts} +1 -2
  54. package/dist/{key-DhsBxxZN.d.ts → key-DlHT4R6k.d.cts} +1 -2
  55. package/dist/{kv-cache-CTQSfkmp.mjs → kv-cache-CIdGATck.mjs} +1 -1
  56. package/dist/{kv-cache-CjgCOz3i.cjs → kv-cache-Cd8xKv-x.cjs} +72 -27
  57. package/dist/{kv-cache-U7uz5nx4.js → kv-cache-DbgFe35y.js} +73 -28
  58. package/dist/{ld-Cel1RRZ5.d.cts → ld-CWVpo_NM.d.cts} +1 -1
  59. package/dist/{ld-CQFuiah9.mjs → ld-DUeBHFWH.mjs} +2 -2
  60. package/dist/{ld-CcuZ4NZa.d.ts → ld-Inagww4R.d.ts} +1 -1
  61. package/dist/{metrics-B-QxRSSR.mjs → metrics-B7TvoAP7.mjs} +1 -1
  62. package/dist/{middleware-BdlNa8BD.mjs → middleware-BE4JXx-U.mjs} +1 -1
  63. package/dist/{middleware-CuVa0UuS.mjs → middleware-BEaUojg7.mjs} +89 -49
  64. package/dist/{middleware-P-AZEQMn.js → middleware-CId6efsJ.js} +247 -109
  65. package/dist/{mod-DvWOv2Ho.d.cts → mod-6TQ4UQZb.d.cts} +1 -1
  66. package/dist/{mod-BHQE-7WQ.d.ts → mod-B4kBw1-8.d.ts} +1 -1
  67. package/dist/{mod-DkhfZC1u.d.ts → mod-B7j30IQf.d.ts} +2 -9
  68. package/dist/{mod-B2783_3v.d.cts → mod-C1nT74IG.d.cts} +1 -1
  69. package/dist/{mod-DjZfJj9R.d.ts → mod-CIeBgsOC.d.ts} +1 -1
  70. package/dist/{mod-B5-wdzmJ.d.cts → mod-YvA4Rk8J.d.cts} +2 -9
  71. package/dist/mod.cjs +5 -5
  72. package/dist/mod.d.cts +7 -7
  73. package/dist/mod.d.ts +7 -7
  74. package/dist/mod.js +5 -5
  75. package/dist/nodeinfo/client.test.mjs +337 -19
  76. package/dist/nodeinfo/handler.test.mjs +1 -1
  77. package/dist/nodeinfo/mod.cjs +1 -1
  78. package/dist/nodeinfo/mod.d.cts +1 -1
  79. package/dist/nodeinfo/mod.d.ts +1 -1
  80. package/dist/nodeinfo/mod.js +1 -1
  81. package/dist/otel/exporter.test.mjs +257 -1
  82. package/dist/otel/mod.cjs +36 -30
  83. package/dist/otel/mod.d.cts +13 -0
  84. package/dist/otel/mod.d.ts +13 -0
  85. package/dist/otel/mod.js +36 -30
  86. package/dist/{owner-ccKkQOAf.mjs → owner-CwijV0c5.mjs} +3 -3
  87. package/dist/{portable-collection-DUlQ3s2M.js → portable-collection-2jn_4V7Y.js} +1 -1
  88. package/dist/{portable-collection-CndnpXfo.mjs → portable-collection-B023xqhZ.mjs} +2 -2
  89. package/dist/{portable-collection-TC-kvkVI.cjs → portable-collection-BeS88JJp.cjs} +1 -1
  90. package/dist/{portable-key-D5tFOoV-.mjs → portable-key-BpBucRfb.mjs} +1 -1
  91. package/dist/{proof-DPoF06nz.mjs → proof-DQh2z9fx.mjs} +3 -3
  92. package/dist/{send-CF44O1Df.mjs → send-DN5HNBv-.mjs} +174 -76
  93. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  94. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  95. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  96. package/dist/sig/compound-proof.test.mjs +2 -2
  97. package/dist/sig/http.test.mjs +553 -2
  98. package/dist/sig/key.test.mjs +1 -1
  99. package/dist/sig/ld.test.mjs +1 -1
  100. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  101. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  102. package/dist/sig/mod.cjs +2 -2
  103. package/dist/sig/mod.d.cts +3 -3
  104. package/dist/sig/mod.d.ts +3 -3
  105. package/dist/sig/mod.js +2 -2
  106. package/dist/sig/owner.test.mjs +1 -1
  107. package/dist/sig/portable-collection.test.mjs +1 -1
  108. package/dist/sig/portable-interop.test.mjs +3 -3
  109. package/dist/sig/portable-key.test.mjs +5 -5
  110. package/dist/sig/proof.test.mjs +4 -4
  111. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  112. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  113. package/dist/sig/verification.test.mjs +3 -3
  114. package/dist/{temporal-XlZ4Wb94.mjs → temporal-yl7Z7A6y.mjs} +1 -1
  115. package/dist/testing/kv.test.mjs +1 -1
  116. package/dist/testing/mod.d.mts +26 -0
  117. package/dist/{types-_S2_MeLF.js → types-DUjZb4i6.js} +112 -63
  118. package/dist/{types-poIao40f.cjs → types-hKCCjii0.cjs} +111 -62
  119. package/dist/utils/docloader.test.mjs +344 -36
  120. package/dist/utils/kv-cache.test.mjs +1 -1
  121. package/dist/utils/mod.cjs +1 -1
  122. package/dist/utils/mod.d.cts +1 -1
  123. package/dist/utils/mod.d.ts +1 -1
  124. package/dist/utils/mod.js +1 -1
  125. package/package.json +8 -8
  126. package/dist/docloader-CpsDyqVN.mjs +0 -91
@@ -4,7 +4,8 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
- import { t as FedifySpanExporter } from "../exporter-DThFfe7P.mjs";
7
+ import { t as assert } from "../assert-D1Eafmd_.mjs";
8
+ import { t as FedifySpanExporter } from "../exporter-DVG68ozH.mjs";
8
9
  import { SpanKind, SpanStatusCode, TraceFlags } from "@opentelemetry/api";
9
10
  import { test } from "@fedify/fixture";
10
11
  //#region src/otel/exporter.test.ts
@@ -257,6 +258,261 @@ test("FedifySpanExporter", async (t) => {
257
258
  }
258
259
  assertEquals((await exporter.getRecentTraces()).length >= 3, true);
259
260
  });
261
+ function createOutboundSpan(traceId, spanId) {
262
+ return createMockSpan({
263
+ traceId,
264
+ spanId,
265
+ name: "activitypub.send_activity",
266
+ events: [createActivitySentEvent({
267
+ activityJson: JSON.stringify({
268
+ "@context": "https://www.w3.org/ns/activitystreams",
269
+ type: "Create",
270
+ id: `https://local.example/activities/${spanId}`
271
+ }),
272
+ inboxUrl: "https://follower.example/inbox"
273
+ })]
274
+ });
275
+ }
276
+ async function exportSpans(exporter, spans) {
277
+ assertEquals((await new Promise((resolve) => {
278
+ exporter.export(spans, resolve);
279
+ })).code, 0);
280
+ }
281
+ for (const withCas of [true, false]) {
282
+ const createStore = () => {
283
+ const memory = new MemoryKvStore();
284
+ return withCas ? memory : {
285
+ get: memory.get.bind(memory),
286
+ set: memory.set.bind(memory),
287
+ delete: memory.delete.bind(memory),
288
+ list: memory.list.bind(memory)
289
+ };
290
+ };
291
+ await t.step(`re-export counts each retained span once (CAS=${withCas})`, async () => {
292
+ const kv = createStore();
293
+ const exporters = [new FedifySpanExporter(kv), new FedifySpanExporter(kv)];
294
+ const span = createOutboundSpan("reexport-trace", "span-a");
295
+ await exportSpans(exporters[0], [span]);
296
+ await exportSpans(exporters[0], [span]);
297
+ assertEquals((await exporters[1].getRecentTraces())[0].activityCount, 1);
298
+ await Promise.all(exporters.map((exporter) => exportSpans(exporter, [span])));
299
+ assertEquals((await exporters[0].getActivitiesByTraceId("reexport-trace")).length, 1);
300
+ assertEquals((await exporters[1].getRecentTraces())[0].activityCount, 1);
301
+ });
302
+ await t.step(`concurrent distinct spans are all counted (CAS=${withCas})`, async () => {
303
+ const kv = createStore();
304
+ const exporters = [new FedifySpanExporter(kv), new FedifySpanExporter(kv)];
305
+ await Promise.all(exporters.map((exporter, i) => exportSpans(exporter, [createOutboundSpan("concurrent-count-trace", `span-${i}`)])));
306
+ const reader = new FedifySpanExporter(kv);
307
+ assertEquals((await reader.getActivitiesByTraceId("concurrent-count-trace")).length, 2);
308
+ assertEquals((await reader.getRecentTraces())[0].activityCount, 2);
309
+ });
310
+ await t.step(`legacy summaries cannot hide or inflate retained records (CAS=${withCas})`, async () => {
311
+ const kv = createStore();
312
+ const exporter = new FedifySpanExporter(kv);
313
+ await exportSpans(exporter, [createOutboundSpan("legacy-trace", "span-a")]);
314
+ await kv.delete([
315
+ "fedify",
316
+ "traces",
317
+ "_summaries",
318
+ "legacy-trace"
319
+ ]);
320
+ const originalSet = kv.set.bind(kv);
321
+ let writes = 0;
322
+ kv.set = (...args) => {
323
+ writes++;
324
+ return originalSet(...args);
325
+ };
326
+ assertEquals((await exporter.getRecentTraces())[0].activityCount, 1);
327
+ assertEquals(writes, 0);
328
+ const stale = {
329
+ traceId: "legacy-trace",
330
+ timestamp: "2000-01-01T00:00:00.000Z",
331
+ activityCount: 99,
332
+ activityTypes: ["Bogus"]
333
+ };
334
+ await kv.set([
335
+ "fedify",
336
+ "traces",
337
+ "_summaries",
338
+ "legacy-trace"
339
+ ], stale);
340
+ await kv.set([
341
+ "fedify",
342
+ "traces",
343
+ "_summaries",
344
+ "ghost-trace"
345
+ ], {
346
+ ...stale,
347
+ traceId: "ghost-trace"
348
+ });
349
+ const records = await exporter.getActivitiesByTraceId("legacy-trace");
350
+ assertEquals(await exporter.getRecentTraces(), [{
351
+ traceId: "legacy-trace",
352
+ timestamp: records[0].timestamp,
353
+ activityCount: 1,
354
+ activityTypes: ["Create"]
355
+ }]);
356
+ await kv.delete([
357
+ "fedify",
358
+ "traces",
359
+ "legacy-trace",
360
+ "span-a"
361
+ ]);
362
+ assertEquals(await exporter.getRecentTraces(), []);
363
+ });
364
+ await t.step(`summary metadata follows retained records (CAS=${withCas})`, async () => {
365
+ const kv = createStore();
366
+ const exporter = new FedifySpanExporter(kv, { keyPrefix: ["custom"] });
367
+ function span(traceId, spanId, type, seconds) {
368
+ return createMockSpan({
369
+ traceId,
370
+ spanId,
371
+ events: [{
372
+ ...createActivitySentEvent({
373
+ activityJson: JSON.stringify({ type }),
374
+ inboxUrl: "https://example.com/inbox"
375
+ }),
376
+ time: [seconds, 0]
377
+ }]
378
+ });
379
+ }
380
+ await exportSpans(exporter, [
381
+ span("older-trace", "late", "Like", 1700000002),
382
+ span("older-trace", "early", "Create", 17e8),
383
+ span("older-trace", "duplicate-type", "Like", 1700000003),
384
+ span("newer-trace", "only", "Follow", 1700000001)
385
+ ]);
386
+ const recent = await exporter.getRecentTraces();
387
+ assertEquals(recent.map((summary) => summary.traceId), ["newer-trace", "older-trace"]);
388
+ assertEquals(recent[1], {
389
+ traceId: "older-trace",
390
+ timestamp: (/* @__PURE__ */ new Date(17e11)).toISOString(),
391
+ activityCount: 3,
392
+ activityTypes: ["Like", "Create"]
393
+ });
394
+ assertEquals(await exporter.getRecentTraces({ limit: 1 }), [recent[0]]);
395
+ await kv.delete([
396
+ "custom",
397
+ "older-trace",
398
+ "early"
399
+ ]);
400
+ assertEquals((await exporter.getRecentTraces())[0], {
401
+ traceId: "older-trace",
402
+ timestamp: (/* @__PURE__ */ new Date(1700000002e3)).toISOString(),
403
+ activityCount: 2,
404
+ activityTypes: ["Like"]
405
+ });
406
+ });
407
+ }
408
+ await t.step("a trace with only one record is discoverable via getRecentTraces(), with no further write", async () => {
409
+ const exporter = new FedifySpanExporter(new MemoryKvStore());
410
+ const traceId = "first-write-discoverable-trace";
411
+ await new Promise((resolve) => {
412
+ exporter.export([createOutboundSpan(traceId, "span-a")], () => resolve());
413
+ });
414
+ assertEquals((await exporter.getRecentTraces()).find((s) => s.traceId === traceId)?.activityCount, 1);
415
+ });
416
+ await t.step("getRecentTraces() needs no additional writes to discover a trace or correct its activityCount", async () => {
417
+ const kv = new MemoryKvStore();
418
+ const exporter = new FedifySpanExporter(kv);
419
+ const traceId = "query-time-correction-trace";
420
+ await new Promise((resolve) => {
421
+ exporter.export([createOutboundSpan(traceId, "span-a")], () => resolve());
422
+ });
423
+ await new Promise((resolve) => {
424
+ exporter.export([createOutboundSpan(traceId, "span-b")], () => resolve());
425
+ });
426
+ const originalSet = kv.set.bind(kv);
427
+ let writeCalls = 0;
428
+ kv.set = (key, value, options) => {
429
+ writeCalls++;
430
+ return originalSet(key, value, options);
431
+ };
432
+ assertEquals((await exporter.getRecentTraces()).find((s) => s.traceId === traceId)?.activityCount, 2);
433
+ assertEquals(writeCalls, 0);
434
+ });
435
+ await t.step("getRecentTraces() reflects only currently-retained records, e.g., after some have expired", async () => {
436
+ const kv = new MemoryKvStore();
437
+ const exporter = new FedifySpanExporter(kv);
438
+ const traceId = "expiry-reflects-retained-trace";
439
+ await new Promise((resolve) => {
440
+ exporter.export([createOutboundSpan(traceId, "span-a"), createOutboundSpan(traceId, "span-b")], () => resolve());
441
+ });
442
+ let recent = await exporter.getRecentTraces();
443
+ assertEquals(recent.find((s) => s.traceId === traceId)?.activityCount, 2);
444
+ await kv.delete([
445
+ "fedify",
446
+ "traces",
447
+ traceId,
448
+ "span-a"
449
+ ]);
450
+ recent = await exporter.getRecentTraces();
451
+ assertEquals(recent.find((s) => s.traceId === traceId)?.activityCount, 1);
452
+ });
453
+ await t.step("getRecentTraces() polling cost scales with every retained record, not just records changed since the last poll, and limit does not reduce it", async () => {
454
+ const kv = new MemoryKvStore();
455
+ const exporter = new FedifySpanExporter(kv);
456
+ const traceCount = 20;
457
+ const recordsPerTrace = 5;
458
+ for (let i = 0; i < traceCount; i++) {
459
+ const traceId = `poll-cost-trace-${i}`;
460
+ const spans = Array.from({ length: recordsPerTrace }, (_, j) => createOutboundSpan(traceId, `span-${j}`));
461
+ await new Promise((resolve) => {
462
+ exporter.export(spans, () => resolve());
463
+ });
464
+ }
465
+ const originalList = kv.list.bind(kv);
466
+ let entriesRead = 0;
467
+ let bytesRead = 0;
468
+ kv.list = async function* (prefix) {
469
+ for await (const entry of originalList(prefix)) {
470
+ entriesRead++;
471
+ bytesRead += new TextEncoder().encode(JSON.stringify(entry.value)).byteLength;
472
+ yield entry;
473
+ }
474
+ };
475
+ assertEquals((await exporter.getRecentTraces()).length, traceCount);
476
+ assertEquals(entriesRead, traceCount * recordsPerTrace);
477
+ assert(bytesRead > 0);
478
+ const entriesReadForFullScan = entriesRead;
479
+ const bytesReadForFullScan = bytesRead;
480
+ entriesRead = 0;
481
+ bytesRead = 0;
482
+ assertEquals((await exporter.getRecentTraces({ limit: 3 })).length, 3);
483
+ assertEquals(entriesRead, entriesReadForFullScan);
484
+ assertEquals(bytesRead, bytesReadForFullScan);
485
+ });
486
+ await t.step("recent-list scans include full inbound payloads even with limit", async () => {
487
+ const kv = new MemoryKvStore();
488
+ const exporter = new FedifySpanExporter(kv);
489
+ const activityJson = JSON.stringify({
490
+ type: "Create",
491
+ object: {
492
+ type: "Note",
493
+ content: "한".repeat(64 * 1024)
494
+ }
495
+ });
496
+ await exportSpans(exporter, Array.from({ length: 10 }, (_, i) => createMockSpan({
497
+ traceId: `large-inbound-${i}`,
498
+ spanId: "inbound",
499
+ events: [createActivityReceivedEvent({ activityJson })]
500
+ })));
501
+ const originalList = kv.list.bind(kv);
502
+ let entriesRead = 0;
503
+ let bytesRead = 0;
504
+ kv.list = async function* (prefix) {
505
+ for await (const entry of originalList(prefix)) {
506
+ entriesRead++;
507
+ bytesRead += new TextEncoder().encode(JSON.stringify(entry.value)).byteLength;
508
+ yield entry;
509
+ }
510
+ };
511
+ assertEquals((await exporter.getRecentTraces({ limit: 1 })).length, 1);
512
+ assertEquals(entriesRead, 10);
513
+ assert(bytesRead >= new TextEncoder().encode(activityJson).byteLength * 10);
514
+ assert(bytesRead > 1.8 * 1024 * 1024);
515
+ });
260
516
  await t.step("forceFlush() returns resolved promise", async () => {
261
517
  await new FedifySpanExporter(new MemoryKvStore()).forceFlush();
262
518
  });
package/dist/otel/mod.cjs CHANGED
@@ -188,33 +188,6 @@ var FedifySpanExporter = class {
188
188
  record.spanId
189
189
  ];
190
190
  await this.#kv.set(key, record, options);
191
- await this.#updateTraceSummary(record, options);
192
- }
193
- async #setWithCasRetry(key, transform, options) {
194
- if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
195
- const existing = await this.#kv.get(key);
196
- const newValue = transform(existing);
197
- if (await this.#kv.cas(key, existing, newValue, options)) return;
198
- }
199
- const newValue = transform(await this.#kv.get(key));
200
- await this.#kv.set(key, newValue, options);
201
- }
202
- async #updateTraceSummary(record, options) {
203
- const summaryKey = [
204
- ...this.#keyPrefix,
205
- "_summaries",
206
- record.traceId
207
- ];
208
- await this.#setWithCasRetry(summaryKey, (existing) => {
209
- const activityCount = existing != null ? existing.activityCount + 1 : 1;
210
- const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
211
- return {
212
- traceId: existing?.traceId ?? record.traceId,
213
- timestamp: existing?.timestamp ?? record.timestamp,
214
- activityCount,
215
- activityTypes
216
- };
217
- }, options);
218
191
  }
219
192
  /**
220
193
  * Gets all activity records for a specific trace ID.
@@ -231,13 +204,46 @@ var FedifySpanExporter = class {
231
204
  /**
232
205
  * Gets recent traces with summary information.
233
206
  *
207
+ * Each summary is computed directly from the activity records currently
208
+ * stored for each trace, rather than from a separately maintained
209
+ * counter. This guarantees that a trace is discoverable, and its
210
+ * `activityCount` accurate, as soon as its records are stored, even if
211
+ * an earlier attempt at summarizing it (in a previous version of this
212
+ * exporter) never completed, and that the count always reflects
213
+ * records that have not expired. It costs a full scan of every stored
214
+ * record on every call, including full inbound activity JSON. The
215
+ * `limit` option only truncates the returned summaries and does not
216
+ * reduce the scan. Callers that poll this method frequently should
217
+ * account for the total retained data when choosing a TTL and polling
218
+ * interval.
219
+ *
234
220
  * @param options Options for the query.
235
221
  * @returns An array of trace summaries.
236
222
  */
237
223
  async getRecentTraces(options) {
238
- const summaryPrefix = [...this.#keyPrefix, "_summaries"];
239
- const summaries = [];
240
- for await (const entry of this.#kv.list(summaryPrefix)) summaries.push(entry.value);
224
+ const aggregates = /* @__PURE__ */ new Map();
225
+ const traceIdIndex = this.#keyPrefix.length;
226
+ for await (const entry of this.#kv.list(this.#keyPrefix)) {
227
+ const traceId = entry.key[traceIdIndex];
228
+ if (traceId == null || traceId === "_summaries") continue;
229
+ const record = entry.value;
230
+ if (record == null || typeof record.timestamp !== "string") continue;
231
+ const aggregate = aggregates.get(traceId);
232
+ if (aggregate == null) aggregates.set(traceId, {
233
+ timestamp: record.timestamp,
234
+ activityCount: 1,
235
+ activityTypes: [record.activityType]
236
+ });
237
+ else {
238
+ aggregate.activityCount++;
239
+ if (record.timestamp < aggregate.timestamp) aggregate.timestamp = record.timestamp;
240
+ if (!aggregate.activityTypes.includes(record.activityType)) aggregate.activityTypes.push(record.activityType);
241
+ }
242
+ }
243
+ const summaries = [...aggregates].map(([traceId, aggregate]) => ({
244
+ traceId,
245
+ ...aggregate
246
+ }));
241
247
  summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
242
248
  if (options?.limit != null) return summaries.slice(0, options.limit);
243
249
  return summaries;
@@ -226,6 +226,19 @@ declare class FedifySpanExporter implements SpanExporter {
226
226
  /**
227
227
  * Gets recent traces with summary information.
228
228
  *
229
+ * Each summary is computed directly from the activity records currently
230
+ * stored for each trace, rather than from a separately maintained
231
+ * counter. This guarantees that a trace is discoverable, and its
232
+ * `activityCount` accurate, as soon as its records are stored, even if
233
+ * an earlier attempt at summarizing it (in a previous version of this
234
+ * exporter) never completed, and that the count always reflects
235
+ * records that have not expired. It costs a full scan of every stored
236
+ * record on every call, including full inbound activity JSON. The
237
+ * `limit` option only truncates the returned summaries and does not
238
+ * reduce the scan. Callers that poll this method frequently should
239
+ * account for the total retained data when choosing a TTL and polling
240
+ * interval.
241
+ *
229
242
  * @param options Options for the query.
230
243
  * @returns An array of trace summaries.
231
244
  */
@@ -226,6 +226,19 @@ declare class FedifySpanExporter implements SpanExporter {
226
226
  /**
227
227
  * Gets recent traces with summary information.
228
228
  *
229
+ * Each summary is computed directly from the activity records currently
230
+ * stored for each trace, rather than from a separately maintained
231
+ * counter. This guarantees that a trace is discoverable, and its
232
+ * `activityCount` accurate, as soon as its records are stored, even if
233
+ * an earlier attempt at summarizing it (in a previous version of this
234
+ * exporter) never completed, and that the count always reflects
235
+ * records that have not expired. It costs a full scan of every stored
236
+ * record on every call, including full inbound activity JSON. The
237
+ * `limit` option only truncates the returned summaries and does not
238
+ * reduce the scan. Callers that poll this method frequently should
239
+ * account for the total retained data when choosing a TTL and polling
240
+ * interval.
241
+ *
229
242
  * @param options Options for the query.
230
243
  * @returns An array of trace summaries.
231
244
  */
package/dist/otel/mod.js CHANGED
@@ -188,33 +188,6 @@ var FedifySpanExporter = class {
188
188
  record.spanId
189
189
  ];
190
190
  await this.#kv.set(key, record, options);
191
- await this.#updateTraceSummary(record, options);
192
- }
193
- async #setWithCasRetry(key, transform, options) {
194
- if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
195
- const existing = await this.#kv.get(key);
196
- const newValue = transform(existing);
197
- if (await this.#kv.cas(key, existing, newValue, options)) return;
198
- }
199
- const newValue = transform(await this.#kv.get(key));
200
- await this.#kv.set(key, newValue, options);
201
- }
202
- async #updateTraceSummary(record, options) {
203
- const summaryKey = [
204
- ...this.#keyPrefix,
205
- "_summaries",
206
- record.traceId
207
- ];
208
- await this.#setWithCasRetry(summaryKey, (existing) => {
209
- const activityCount = existing != null ? existing.activityCount + 1 : 1;
210
- const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
211
- return {
212
- traceId: existing?.traceId ?? record.traceId,
213
- timestamp: existing?.timestamp ?? record.timestamp,
214
- activityCount,
215
- activityTypes
216
- };
217
- }, options);
218
191
  }
219
192
  /**
220
193
  * Gets all activity records for a specific trace ID.
@@ -231,13 +204,46 @@ var FedifySpanExporter = class {
231
204
  /**
232
205
  * Gets recent traces with summary information.
233
206
  *
207
+ * Each summary is computed directly from the activity records currently
208
+ * stored for each trace, rather than from a separately maintained
209
+ * counter. This guarantees that a trace is discoverable, and its
210
+ * `activityCount` accurate, as soon as its records are stored, even if
211
+ * an earlier attempt at summarizing it (in a previous version of this
212
+ * exporter) never completed, and that the count always reflects
213
+ * records that have not expired. It costs a full scan of every stored
214
+ * record on every call, including full inbound activity JSON. The
215
+ * `limit` option only truncates the returned summaries and does not
216
+ * reduce the scan. Callers that poll this method frequently should
217
+ * account for the total retained data when choosing a TTL and polling
218
+ * interval.
219
+ *
234
220
  * @param options Options for the query.
235
221
  * @returns An array of trace summaries.
236
222
  */
237
223
  async getRecentTraces(options) {
238
- const summaryPrefix = [...this.#keyPrefix, "_summaries"];
239
- const summaries = [];
240
- for await (const entry of this.#kv.list(summaryPrefix)) summaries.push(entry.value);
224
+ const aggregates = /* @__PURE__ */ new Map();
225
+ const traceIdIndex = this.#keyPrefix.length;
226
+ for await (const entry of this.#kv.list(this.#keyPrefix)) {
227
+ const traceId = entry.key[traceIdIndex];
228
+ if (traceId == null || traceId === "_summaries") continue;
229
+ const record = entry.value;
230
+ if (record == null || typeof record.timestamp !== "string") continue;
231
+ const aggregate = aggregates.get(traceId);
232
+ if (aggregate == null) aggregates.set(traceId, {
233
+ timestamp: record.timestamp,
234
+ activityCount: 1,
235
+ activityTypes: [record.activityType]
236
+ });
237
+ else {
238
+ aggregate.activityCount++;
239
+ if (record.timestamp < aggregate.timestamp) aggregate.timestamp = record.timestamp;
240
+ if (!aggregate.activityTypes.includes(record.activityType)) aggregate.activityTypes.push(record.activityType);
241
+ }
242
+ }
243
+ const summaries = [...aggregates].map(([traceId, aggregate]) => ({
244
+ traceId,
245
+ ...aggregate
246
+ }));
241
247
  summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
242
248
  if (options?.limit != null) return summaries.slice(0, options.limit);
243
249
  return summaries;
@@ -2,10 +2,10 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { d as isPortableUri, l as isPortableId, n as getCanonicalPortableId, p as isSamePublicKey$1, s as isCompatibleKeyId } from "./portable-key-id-Ca30-hOa.mjs";
5
- import { n as version, t as name } from "./deno-Dpewt_jJ.mjs";
5
+ import { n as version, t as name } from "./deno-D7blYLbb.mjs";
6
6
  import { i as parseKeyOwnerEvidence, r as getVerifiedKeyOwnerEvidence } from "./key-owner-evidence-APB6NpSk.mjs";
7
- import { O as verifyKeyOwnership, S as fetchActorDocument, x as exportJwk } from "./ld-CQFuiah9.mjs";
8
- import { n as resolvePortableActorKey, t as fetchPortableGatewayKey } from "./portable-key-D5tFOoV-.mjs";
7
+ import { O as verifyKeyOwnership, S as fetchActorDocument, x as exportJwk } from "./ld-DUeBHFWH.mjs";
8
+ import { n as resolvePortableActorKey, t as fetchPortableGatewayKey } from "./portable-key-BpBucRfb.mjs";
9
9
  import { getLogger } from "@logtape/logtape";
10
10
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { $ as getVerifiedKeyOwnerEvidence, C as verifyPortableObjectProofWithRoot, Dt as getCanonicalPortableId, Ft as isPortableUri, G as fetchActorDocument, Lt as isSamePublicKey$1, Mt as isCompatibleKeyId, Nt as isPortableId, Rt as name, W as exportJwk, Z as verifyKeyOwnership, et as parseKeyOwnerEvidence, l as fetchPortableGatewayKey, u as resolvePortableActorKey, x as verifyPortableObjectProof, zt as version } from "./http-BBr-VLil.js";
3
+ import { $ as getVerifiedKeyOwnerEvidence, C as verifyPortableObjectProofWithRoot, Dt as getCanonicalPortableId, Ft as isPortableUri, G as fetchActorDocument, Lt as isSamePublicKey$1, Mt as isCompatibleKeyId, Nt as isPortableId, Rt as name, W as exportJwk, Z as verifyKeyOwnership, et as parseKeyOwnerEvidence, l as fetchPortableGatewayKey, u as resolvePortableActorKey, x as verifyPortableObjectProof, zt as version } from "./http-CrYxBKqG.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { CollectionPage, CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Dpewt_jJ.mjs";
5
- import { d as verifyPortableObjectProof, p as verifyPortableObjectProofWithRoot } from "./proof-DPoF06nz.mjs";
4
+ import { n as version, t as name } from "./deno-D7blYLbb.mjs";
5
+ import { d as verifyPortableObjectProof, p as verifyPortableObjectProofWithRoot } from "./proof-DQh2z9fx.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { CollectionPage, Object as Object$1, isActor } from "@fedify/vocab";
8
8
  import { trace } from "@opentelemetry/api";
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-CAl7Num5.cjs");
2
+ const require_http = require("./http-2SwIR4b-.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let _fedify_vocab = require("@fedify/vocab");
5
5
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -2,7 +2,7 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { a as getRawCanonicalPortableId, c as isPortableActorDocument, d as isPortableUri, n as getCanonicalPortableId, p as isSamePublicKey } from "./portable-key-id-Ca30-hOa.mjs";
5
- import { p as verifyPortableObjectProofWithRoot } from "./proof-DPoF06nz.mjs";
5
+ import { p as verifyPortableObjectProofWithRoot } from "./proof-DQh2z9fx.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { CryptographicKey, Multikey, Object as Object$1 } from "@fedify/vocab";
8
8
  import { FetchError, formatIri, getDocumentLoader, isGatewayUrl, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
@@ -3,9 +3,9 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { i as getPortableDid, l as isPortableId } from "./portable-key-id-Ca30-hOa.mjs";
5
5
  import { a as verificationObservation, i as triedKey, n as observeCheck, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
6
- import { n as version, t as name } from "./deno-Dpewt_jJ.mjs";
7
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-B-QxRSSR.mjs";
8
- import { C as fetchKey, D as validateCryptoKey, b as bypassKeyCacheReads, c as getNormalizationContextLoader } from "./ld-CQFuiah9.mjs";
6
+ import { n as version, t as name } from "./deno-D7blYLbb.mjs";
7
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-B7TvoAP7.mjs";
8
+ import { C as fetchKey, D as validateCryptoKey, b as bypassKeyCacheReads, c as getNormalizationContextLoader } from "./ld-DUeBHFWH.mjs";
9
9
  import { n as preloadedOnlyDocumentLoader } from "./public-audience--klBIOk1.mjs";
10
10
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
11
11
  import { getLogger } from "@logtape/logtape";