@fedify/fedify 2.2.18 → 2.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-DAbQyxmb.mjs → builder-BolQ56ab.mjs} +2 -2
- package/dist/{client-CAM_bQXx.d.cts → client-BIBzqEIp.d.cts} +0 -1
- package/dist/{client-9F_u23dc.mjs → client-DhxaVgOv.mjs} +113 -64
- package/dist/{client-CSddvgWN.d.ts → client-jE7oGvRh.d.ts} +0 -1
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{context-tJbL_Hkw.d.cts → context-CFQGaq5x.d.cts} +28 -2
- package/dist/{context-OkwEpc3-.d.ts → context-DdbLVV_k.d.ts} +28 -2
- package/dist/{deno-JUTr6LYC.mjs → deno-BUj4roJc.mjs} +1 -1
- package/dist/docloader-CbtF_0cD.mjs +125 -0
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/keycache.test.mjs +18 -7
- package/dist/federation/middleware.test.mjs +389 -28
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +445 -70
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{http-Dl00HXr4.d.ts → http-9IFUxdkp.d.cts} +1 -2
- package/dist/{http-OSYYhKM8.js → http-C5g_VrA_.js} +188 -105
- package/dist/{http-lCmKCZBI.mjs → http-CQMVisM0.mjs} +195 -97
- package/dist/{http-DEH_eppX.cjs → http-D8I7LBfx.cjs} +189 -106
- package/dist/{http-DqZhyEso.d.cts → http-SbfezERn.d.ts} +1 -2
- package/dist/{inbox-DMATfdEI.mjs → inbox-CD8IDILN.mjs} +1 -1
- package/dist/{key-D0ahOVjf.mjs → key-C3o3DGap.mjs} +17 -3
- package/dist/{keycache-Dkc_sflV.mjs → keycache-BimEQucx.mjs} +1 -1
- package/dist/{kv-cache-B7X4p1y7.js → kv-cache-CSKXXleR.js} +60 -16
- package/dist/{kv-cache-CPEV_4nZ.cjs → kv-cache-DC9LcfoC.cjs} +59 -15
- package/dist/{ld-CJOXIehz.mjs → ld-ijSr-0AB.mjs} +2 -2
- package/dist/{middleware-CHoUjmJm.mjs → middleware-CYAaUTi5.mjs} +79 -39
- package/dist/{middleware-QcuVRA-q.cjs → middleware-DNueR0GD.cjs} +185 -60
- package/dist/{middleware-D3hx1aiI.cjs → middleware-DmcY2TWS.cjs} +1 -1
- package/dist/{middleware-aNUajYPY.js → middleware-sjhz30sm.js} +185 -60
- package/dist/{middleware-BgGJEcdp.mjs → middleware-yqqfI7oJ.mjs} +1 -1
- package/dist/{mod-B6LmugJ4.d.ts → mod-BRnwh2oy.d.ts} +2 -8
- package/dist/{mod-D2JyWtW3.d.cts → mod-BYWp77-z.d.cts} +1 -1
- package/dist/{mod-DQGjRJUG.d.ts → mod-BuRRdsfD.d.ts} +1 -1
- package/dist/{mod-ej_rLx8h.d.ts → mod-CM5_J_cx.d.ts} +1 -1
- package/dist/{mod-a6kmB8xD.d.cts → mod-CVSKw-5H.d.cts} +1 -1
- package/dist/{mod-V2vDnYNu.d.cts → mod-Co7LEK9X.d.cts} +2 -8
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +337 -19
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/otel/exporter.test.mjs +292 -30
- package/dist/otel/mod.cjs +36 -30
- package/dist/otel/mod.d.cts +13 -0
- package/dist/otel/mod.d.ts +13 -0
- package/dist/otel/mod.js +36 -30
- package/dist/{owner-BUTMEkbn.mjs → owner-B8kXzD0q.mjs} +2 -2
- package/dist/{proof-DS1Zl6BX.mjs → proof-CBQOjOgA.mjs} +2 -2
- package/dist/{proof-Bm3xyn3D.js → proof-DKX6nASL.js} +1 -1
- package/dist/{proof-BeY7TsiS.cjs → proof-qiuGJCD7.cjs} +1 -1
- package/dist/{send-fnPrh9Zm.mjs → send-DRpisX_f.mjs} +117 -32
- package/dist/sig/http.test.mjs +529 -3
- package/dist/sig/key.test.mjs +197 -3
- package/dist/sig/ld.test.mjs +47 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +46 -1
- package/dist/sig/proof.test.mjs +52 -1
- package/dist/{temporal-Biz6zK6e.mjs → temporal-BQGEINad.mjs} +1 -1
- package/dist/testing/mod.d.mts +26 -1
- package/dist/{types-miAYfiGO.js → types-B1_pRFE_.js} +112 -63
- package/dist/{types-DRPjmHVV.cjs → types-DOvyymM4.cjs} +111 -62
- package/dist/utils/docloader.test.mjs +382 -34
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
- package/dist/docloader-BGjq1F5U.mjs +0 -81
|
@@ -3,6 +3,7 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
5
5
|
import "../std__assert-DNZ4Liil.mjs";
|
|
6
|
+
import { t as assert } from "../assert-BhQd8QHP.mjs";
|
|
6
7
|
import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
|
|
7
8
|
import { test } from "@fedify/fixture";
|
|
8
9
|
import { SpanKind, SpanStatusCode, TraceFlags } from "@opentelemetry/api";
|
|
@@ -193,33 +194,6 @@ var FedifySpanExporter = class {
|
|
|
193
194
|
record.spanId
|
|
194
195
|
];
|
|
195
196
|
await this.#kv.set(key, record, options);
|
|
196
|
-
await this.#updateTraceSummary(record, options);
|
|
197
|
-
}
|
|
198
|
-
async #setWithCasRetry(key, transform, options) {
|
|
199
|
-
if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
|
|
200
|
-
const existing = await this.#kv.get(key);
|
|
201
|
-
const newValue = transform(existing);
|
|
202
|
-
if (await this.#kv.cas(key, existing, newValue, options)) return;
|
|
203
|
-
}
|
|
204
|
-
const newValue = transform(await this.#kv.get(key));
|
|
205
|
-
await this.#kv.set(key, newValue, options);
|
|
206
|
-
}
|
|
207
|
-
async #updateTraceSummary(record, options) {
|
|
208
|
-
const summaryKey = [
|
|
209
|
-
...this.#keyPrefix,
|
|
210
|
-
"_summaries",
|
|
211
|
-
record.traceId
|
|
212
|
-
];
|
|
213
|
-
await this.#setWithCasRetry(summaryKey, (existing) => {
|
|
214
|
-
const activityCount = existing != null ? existing.activityCount + 1 : 1;
|
|
215
|
-
const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
|
|
216
|
-
return {
|
|
217
|
-
traceId: existing?.traceId ?? record.traceId,
|
|
218
|
-
timestamp: existing?.timestamp ?? record.timestamp,
|
|
219
|
-
activityCount,
|
|
220
|
-
activityTypes
|
|
221
|
-
};
|
|
222
|
-
}, options);
|
|
223
197
|
}
|
|
224
198
|
/**
|
|
225
199
|
* Gets all activity records for a specific trace ID.
|
|
@@ -236,13 +210,46 @@ var FedifySpanExporter = class {
|
|
|
236
210
|
/**
|
|
237
211
|
* Gets recent traces with summary information.
|
|
238
212
|
*
|
|
213
|
+
* Each summary is computed directly from the activity records currently
|
|
214
|
+
* stored for each trace, rather than from a separately maintained
|
|
215
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
216
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
217
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
218
|
+
* exporter) never completed, and that the count always reflects
|
|
219
|
+
* records that have not expired. It costs a full scan of every stored
|
|
220
|
+
* record on every call, including full inbound activity JSON. The
|
|
221
|
+
* `limit` option only truncates the returned summaries and does not
|
|
222
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
223
|
+
* account for the total retained data when choosing a TTL and polling
|
|
224
|
+
* interval.
|
|
225
|
+
*
|
|
239
226
|
* @param options Options for the query.
|
|
240
227
|
* @returns An array of trace summaries.
|
|
241
228
|
*/
|
|
242
229
|
async getRecentTraces(options) {
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
for await (const entry of this.#kv.list(
|
|
230
|
+
const aggregates = /* @__PURE__ */ new Map();
|
|
231
|
+
const traceIdIndex = this.#keyPrefix.length;
|
|
232
|
+
for await (const entry of this.#kv.list(this.#keyPrefix)) {
|
|
233
|
+
const traceId = entry.key[traceIdIndex];
|
|
234
|
+
if (traceId == null || traceId === "_summaries") continue;
|
|
235
|
+
const record = entry.value;
|
|
236
|
+
if (record == null || typeof record.timestamp !== "string") continue;
|
|
237
|
+
const aggregate = aggregates.get(traceId);
|
|
238
|
+
if (aggregate == null) aggregates.set(traceId, {
|
|
239
|
+
timestamp: record.timestamp,
|
|
240
|
+
activityCount: 1,
|
|
241
|
+
activityTypes: [record.activityType]
|
|
242
|
+
});
|
|
243
|
+
else {
|
|
244
|
+
aggregate.activityCount++;
|
|
245
|
+
if (record.timestamp < aggregate.timestamp) aggregate.timestamp = record.timestamp;
|
|
246
|
+
if (!aggregate.activityTypes.includes(record.activityType)) aggregate.activityTypes.push(record.activityType);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const summaries = [...aggregates].map(([traceId, aggregate]) => ({
|
|
250
|
+
traceId,
|
|
251
|
+
...aggregate
|
|
252
|
+
}));
|
|
246
253
|
summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
247
254
|
if (options?.limit != null) return summaries.slice(0, options.limit);
|
|
248
255
|
return summaries;
|
|
@@ -506,6 +513,261 @@ test("FedifySpanExporter", async (t) => {
|
|
|
506
513
|
}
|
|
507
514
|
assertEquals((await exporter.getRecentTraces()).length >= 3, true);
|
|
508
515
|
});
|
|
516
|
+
function createOutboundSpan(traceId, spanId) {
|
|
517
|
+
return createMockSpan({
|
|
518
|
+
traceId,
|
|
519
|
+
spanId,
|
|
520
|
+
name: "activitypub.send_activity",
|
|
521
|
+
events: [createActivitySentEvent({
|
|
522
|
+
activityJson: JSON.stringify({
|
|
523
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
524
|
+
type: "Create",
|
|
525
|
+
id: `https://local.example/activities/${spanId}`
|
|
526
|
+
}),
|
|
527
|
+
inboxUrl: "https://follower.example/inbox"
|
|
528
|
+
})]
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
async function exportSpans(exporter, spans) {
|
|
532
|
+
assertEquals((await new Promise((resolve) => {
|
|
533
|
+
exporter.export(spans, resolve);
|
|
534
|
+
})).code, 0);
|
|
535
|
+
}
|
|
536
|
+
for (const withCas of [true, false]) {
|
|
537
|
+
const createStore = () => {
|
|
538
|
+
const memory = new MemoryKvStore();
|
|
539
|
+
return withCas ? memory : {
|
|
540
|
+
get: memory.get.bind(memory),
|
|
541
|
+
set: memory.set.bind(memory),
|
|
542
|
+
delete: memory.delete.bind(memory),
|
|
543
|
+
list: memory.list.bind(memory)
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
await t.step(`re-export counts each retained span once (CAS=${withCas})`, async () => {
|
|
547
|
+
const kv = createStore();
|
|
548
|
+
const exporters = [new FedifySpanExporter(kv), new FedifySpanExporter(kv)];
|
|
549
|
+
const span = createOutboundSpan("reexport-trace", "span-a");
|
|
550
|
+
await exportSpans(exporters[0], [span]);
|
|
551
|
+
await exportSpans(exporters[0], [span]);
|
|
552
|
+
assertEquals((await exporters[1].getRecentTraces())[0].activityCount, 1);
|
|
553
|
+
await Promise.all(exporters.map((exporter) => exportSpans(exporter, [span])));
|
|
554
|
+
assertEquals((await exporters[0].getActivitiesByTraceId("reexport-trace")).length, 1);
|
|
555
|
+
assertEquals((await exporters[1].getRecentTraces())[0].activityCount, 1);
|
|
556
|
+
});
|
|
557
|
+
await t.step(`concurrent distinct spans are all counted (CAS=${withCas})`, async () => {
|
|
558
|
+
const kv = createStore();
|
|
559
|
+
const exporters = [new FedifySpanExporter(kv), new FedifySpanExporter(kv)];
|
|
560
|
+
await Promise.all(exporters.map((exporter, i) => exportSpans(exporter, [createOutboundSpan("concurrent-count-trace", `span-${i}`)])));
|
|
561
|
+
const reader = new FedifySpanExporter(kv);
|
|
562
|
+
assertEquals((await reader.getActivitiesByTraceId("concurrent-count-trace")).length, 2);
|
|
563
|
+
assertEquals((await reader.getRecentTraces())[0].activityCount, 2);
|
|
564
|
+
});
|
|
565
|
+
await t.step(`legacy summaries cannot hide or inflate retained records (CAS=${withCas})`, async () => {
|
|
566
|
+
const kv = createStore();
|
|
567
|
+
const exporter = new FedifySpanExporter(kv);
|
|
568
|
+
await exportSpans(exporter, [createOutboundSpan("legacy-trace", "span-a")]);
|
|
569
|
+
await kv.delete([
|
|
570
|
+
"fedify",
|
|
571
|
+
"traces",
|
|
572
|
+
"_summaries",
|
|
573
|
+
"legacy-trace"
|
|
574
|
+
]);
|
|
575
|
+
const originalSet = kv.set.bind(kv);
|
|
576
|
+
let writes = 0;
|
|
577
|
+
kv.set = (...args) => {
|
|
578
|
+
writes++;
|
|
579
|
+
return originalSet(...args);
|
|
580
|
+
};
|
|
581
|
+
assertEquals((await exporter.getRecentTraces())[0].activityCount, 1);
|
|
582
|
+
assertEquals(writes, 0);
|
|
583
|
+
const stale = {
|
|
584
|
+
traceId: "legacy-trace",
|
|
585
|
+
timestamp: "2000-01-01T00:00:00.000Z",
|
|
586
|
+
activityCount: 99,
|
|
587
|
+
activityTypes: ["Bogus"]
|
|
588
|
+
};
|
|
589
|
+
await kv.set([
|
|
590
|
+
"fedify",
|
|
591
|
+
"traces",
|
|
592
|
+
"_summaries",
|
|
593
|
+
"legacy-trace"
|
|
594
|
+
], stale);
|
|
595
|
+
await kv.set([
|
|
596
|
+
"fedify",
|
|
597
|
+
"traces",
|
|
598
|
+
"_summaries",
|
|
599
|
+
"ghost-trace"
|
|
600
|
+
], {
|
|
601
|
+
...stale,
|
|
602
|
+
traceId: "ghost-trace"
|
|
603
|
+
});
|
|
604
|
+
const records = await exporter.getActivitiesByTraceId("legacy-trace");
|
|
605
|
+
assertEquals(await exporter.getRecentTraces(), [{
|
|
606
|
+
traceId: "legacy-trace",
|
|
607
|
+
timestamp: records[0].timestamp,
|
|
608
|
+
activityCount: 1,
|
|
609
|
+
activityTypes: ["Create"]
|
|
610
|
+
}]);
|
|
611
|
+
await kv.delete([
|
|
612
|
+
"fedify",
|
|
613
|
+
"traces",
|
|
614
|
+
"legacy-trace",
|
|
615
|
+
"span-a"
|
|
616
|
+
]);
|
|
617
|
+
assertEquals(await exporter.getRecentTraces(), []);
|
|
618
|
+
});
|
|
619
|
+
await t.step(`summary metadata follows retained records (CAS=${withCas})`, async () => {
|
|
620
|
+
const kv = createStore();
|
|
621
|
+
const exporter = new FedifySpanExporter(kv, { keyPrefix: ["custom"] });
|
|
622
|
+
function span(traceId, spanId, type, seconds) {
|
|
623
|
+
return createMockSpan({
|
|
624
|
+
traceId,
|
|
625
|
+
spanId,
|
|
626
|
+
events: [{
|
|
627
|
+
...createActivitySentEvent({
|
|
628
|
+
activityJson: JSON.stringify({ type }),
|
|
629
|
+
inboxUrl: "https://example.com/inbox"
|
|
630
|
+
}),
|
|
631
|
+
time: [seconds, 0]
|
|
632
|
+
}]
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
await exportSpans(exporter, [
|
|
636
|
+
span("older-trace", "late", "Like", 1700000002),
|
|
637
|
+
span("older-trace", "early", "Create", 17e8),
|
|
638
|
+
span("older-trace", "duplicate-type", "Like", 1700000003),
|
|
639
|
+
span("newer-trace", "only", "Follow", 1700000001)
|
|
640
|
+
]);
|
|
641
|
+
const recent = await exporter.getRecentTraces();
|
|
642
|
+
assertEquals(recent.map((summary) => summary.traceId), ["newer-trace", "older-trace"]);
|
|
643
|
+
assertEquals(recent[1], {
|
|
644
|
+
traceId: "older-trace",
|
|
645
|
+
timestamp: (/* @__PURE__ */ new Date(17e11)).toISOString(),
|
|
646
|
+
activityCount: 3,
|
|
647
|
+
activityTypes: ["Like", "Create"]
|
|
648
|
+
});
|
|
649
|
+
assertEquals(await exporter.getRecentTraces({ limit: 1 }), [recent[0]]);
|
|
650
|
+
await kv.delete([
|
|
651
|
+
"custom",
|
|
652
|
+
"older-trace",
|
|
653
|
+
"early"
|
|
654
|
+
]);
|
|
655
|
+
assertEquals((await exporter.getRecentTraces())[0], {
|
|
656
|
+
traceId: "older-trace",
|
|
657
|
+
timestamp: (/* @__PURE__ */ new Date(1700000002e3)).toISOString(),
|
|
658
|
+
activityCount: 2,
|
|
659
|
+
activityTypes: ["Like"]
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
await t.step("a trace with only one record is discoverable via getRecentTraces(), with no further write", async () => {
|
|
664
|
+
const exporter = new FedifySpanExporter(new MemoryKvStore());
|
|
665
|
+
const traceId = "first-write-discoverable-trace";
|
|
666
|
+
await new Promise((resolve) => {
|
|
667
|
+
exporter.export([createOutboundSpan(traceId, "span-a")], () => resolve());
|
|
668
|
+
});
|
|
669
|
+
assertEquals((await exporter.getRecentTraces()).find((s) => s.traceId === traceId)?.activityCount, 1);
|
|
670
|
+
});
|
|
671
|
+
await t.step("getRecentTraces() needs no additional writes to discover a trace or correct its activityCount", async () => {
|
|
672
|
+
const kv = new MemoryKvStore();
|
|
673
|
+
const exporter = new FedifySpanExporter(kv);
|
|
674
|
+
const traceId = "query-time-correction-trace";
|
|
675
|
+
await new Promise((resolve) => {
|
|
676
|
+
exporter.export([createOutboundSpan(traceId, "span-a")], () => resolve());
|
|
677
|
+
});
|
|
678
|
+
await new Promise((resolve) => {
|
|
679
|
+
exporter.export([createOutboundSpan(traceId, "span-b")], () => resolve());
|
|
680
|
+
});
|
|
681
|
+
const originalSet = kv.set.bind(kv);
|
|
682
|
+
let writeCalls = 0;
|
|
683
|
+
kv.set = (key, value, options) => {
|
|
684
|
+
writeCalls++;
|
|
685
|
+
return originalSet(key, value, options);
|
|
686
|
+
};
|
|
687
|
+
assertEquals((await exporter.getRecentTraces()).find((s) => s.traceId === traceId)?.activityCount, 2);
|
|
688
|
+
assertEquals(writeCalls, 0);
|
|
689
|
+
});
|
|
690
|
+
await t.step("getRecentTraces() reflects only currently-retained records, e.g., after some have expired", async () => {
|
|
691
|
+
const kv = new MemoryKvStore();
|
|
692
|
+
const exporter = new FedifySpanExporter(kv);
|
|
693
|
+
const traceId = "expiry-reflects-retained-trace";
|
|
694
|
+
await new Promise((resolve) => {
|
|
695
|
+
exporter.export([createOutboundSpan(traceId, "span-a"), createOutboundSpan(traceId, "span-b")], () => resolve());
|
|
696
|
+
});
|
|
697
|
+
let recent = await exporter.getRecentTraces();
|
|
698
|
+
assertEquals(recent.find((s) => s.traceId === traceId)?.activityCount, 2);
|
|
699
|
+
await kv.delete([
|
|
700
|
+
"fedify",
|
|
701
|
+
"traces",
|
|
702
|
+
traceId,
|
|
703
|
+
"span-a"
|
|
704
|
+
]);
|
|
705
|
+
recent = await exporter.getRecentTraces();
|
|
706
|
+
assertEquals(recent.find((s) => s.traceId === traceId)?.activityCount, 1);
|
|
707
|
+
});
|
|
708
|
+
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 () => {
|
|
709
|
+
const kv = new MemoryKvStore();
|
|
710
|
+
const exporter = new FedifySpanExporter(kv);
|
|
711
|
+
const traceCount = 20;
|
|
712
|
+
const recordsPerTrace = 5;
|
|
713
|
+
for (let i = 0; i < traceCount; i++) {
|
|
714
|
+
const traceId = `poll-cost-trace-${i}`;
|
|
715
|
+
const spans = Array.from({ length: recordsPerTrace }, (_, j) => createOutboundSpan(traceId, `span-${j}`));
|
|
716
|
+
await new Promise((resolve) => {
|
|
717
|
+
exporter.export(spans, () => resolve());
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
const originalList = kv.list.bind(kv);
|
|
721
|
+
let entriesRead = 0;
|
|
722
|
+
let bytesRead = 0;
|
|
723
|
+
kv.list = async function* (prefix) {
|
|
724
|
+
for await (const entry of originalList(prefix)) {
|
|
725
|
+
entriesRead++;
|
|
726
|
+
bytesRead += new TextEncoder().encode(JSON.stringify(entry.value)).byteLength;
|
|
727
|
+
yield entry;
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
assertEquals((await exporter.getRecentTraces()).length, traceCount);
|
|
731
|
+
assertEquals(entriesRead, traceCount * recordsPerTrace);
|
|
732
|
+
assert(bytesRead > 0);
|
|
733
|
+
const entriesReadForFullScan = entriesRead;
|
|
734
|
+
const bytesReadForFullScan = bytesRead;
|
|
735
|
+
entriesRead = 0;
|
|
736
|
+
bytesRead = 0;
|
|
737
|
+
assertEquals((await exporter.getRecentTraces({ limit: 3 })).length, 3);
|
|
738
|
+
assertEquals(entriesRead, entriesReadForFullScan);
|
|
739
|
+
assertEquals(bytesRead, bytesReadForFullScan);
|
|
740
|
+
});
|
|
741
|
+
await t.step("recent-list scans include full inbound payloads even with limit", async () => {
|
|
742
|
+
const kv = new MemoryKvStore();
|
|
743
|
+
const exporter = new FedifySpanExporter(kv);
|
|
744
|
+
const activityJson = JSON.stringify({
|
|
745
|
+
type: "Create",
|
|
746
|
+
object: {
|
|
747
|
+
type: "Note",
|
|
748
|
+
content: "한".repeat(64 * 1024)
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
await exportSpans(exporter, Array.from({ length: 10 }, (_, i) => createMockSpan({
|
|
752
|
+
traceId: `large-inbound-${i}`,
|
|
753
|
+
spanId: "inbound",
|
|
754
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
755
|
+
})));
|
|
756
|
+
const originalList = kv.list.bind(kv);
|
|
757
|
+
let entriesRead = 0;
|
|
758
|
+
let bytesRead = 0;
|
|
759
|
+
kv.list = async function* (prefix) {
|
|
760
|
+
for await (const entry of originalList(prefix)) {
|
|
761
|
+
entriesRead++;
|
|
762
|
+
bytesRead += new TextEncoder().encode(JSON.stringify(entry.value)).byteLength;
|
|
763
|
+
yield entry;
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
assertEquals((await exporter.getRecentTraces({ limit: 1 })).length, 1);
|
|
767
|
+
assertEquals(entriesRead, 10);
|
|
768
|
+
assert(bytesRead >= new TextEncoder().encode(activityJson).byteLength * 10);
|
|
769
|
+
assert(bytesRead > 1.8 * 1024 * 1024);
|
|
770
|
+
});
|
|
509
771
|
await t.step("forceFlush() returns resolved promise", async () => {
|
|
510
772
|
await new FedifySpanExporter(new MemoryKvStore()).forceFlush();
|
|
511
773
|
});
|
package/dist/otel/mod.cjs
CHANGED
|
@@ -189,33 +189,6 @@ var FedifySpanExporter = class {
|
|
|
189
189
|
record.spanId
|
|
190
190
|
];
|
|
191
191
|
await this.#kv.set(key, record, options);
|
|
192
|
-
await this.#updateTraceSummary(record, options);
|
|
193
|
-
}
|
|
194
|
-
async #setWithCasRetry(key, transform, options) {
|
|
195
|
-
if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
|
|
196
|
-
const existing = await this.#kv.get(key);
|
|
197
|
-
const newValue = transform(existing);
|
|
198
|
-
if (await this.#kv.cas(key, existing, newValue, options)) return;
|
|
199
|
-
}
|
|
200
|
-
const newValue = transform(await this.#kv.get(key));
|
|
201
|
-
await this.#kv.set(key, newValue, options);
|
|
202
|
-
}
|
|
203
|
-
async #updateTraceSummary(record, options) {
|
|
204
|
-
const summaryKey = [
|
|
205
|
-
...this.#keyPrefix,
|
|
206
|
-
"_summaries",
|
|
207
|
-
record.traceId
|
|
208
|
-
];
|
|
209
|
-
await this.#setWithCasRetry(summaryKey, (existing) => {
|
|
210
|
-
const activityCount = existing != null ? existing.activityCount + 1 : 1;
|
|
211
|
-
const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
|
|
212
|
-
return {
|
|
213
|
-
traceId: existing?.traceId ?? record.traceId,
|
|
214
|
-
timestamp: existing?.timestamp ?? record.timestamp,
|
|
215
|
-
activityCount,
|
|
216
|
-
activityTypes
|
|
217
|
-
};
|
|
218
|
-
}, options);
|
|
219
192
|
}
|
|
220
193
|
/**
|
|
221
194
|
* Gets all activity records for a specific trace ID.
|
|
@@ -232,13 +205,46 @@ var FedifySpanExporter = class {
|
|
|
232
205
|
/**
|
|
233
206
|
* Gets recent traces with summary information.
|
|
234
207
|
*
|
|
208
|
+
* Each summary is computed directly from the activity records currently
|
|
209
|
+
* stored for each trace, rather than from a separately maintained
|
|
210
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
211
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
212
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
213
|
+
* exporter) never completed, and that the count always reflects
|
|
214
|
+
* records that have not expired. It costs a full scan of every stored
|
|
215
|
+
* record on every call, including full inbound activity JSON. The
|
|
216
|
+
* `limit` option only truncates the returned summaries and does not
|
|
217
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
218
|
+
* account for the total retained data when choosing a TTL and polling
|
|
219
|
+
* interval.
|
|
220
|
+
*
|
|
235
221
|
* @param options Options for the query.
|
|
236
222
|
* @returns An array of trace summaries.
|
|
237
223
|
*/
|
|
238
224
|
async getRecentTraces(options) {
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
for await (const entry of this.#kv.list(
|
|
225
|
+
const aggregates = /* @__PURE__ */ new Map();
|
|
226
|
+
const traceIdIndex = this.#keyPrefix.length;
|
|
227
|
+
for await (const entry of this.#kv.list(this.#keyPrefix)) {
|
|
228
|
+
const traceId = entry.key[traceIdIndex];
|
|
229
|
+
if (traceId == null || traceId === "_summaries") continue;
|
|
230
|
+
const record = entry.value;
|
|
231
|
+
if (record == null || typeof record.timestamp !== "string") continue;
|
|
232
|
+
const aggregate = aggregates.get(traceId);
|
|
233
|
+
if (aggregate == null) aggregates.set(traceId, {
|
|
234
|
+
timestamp: record.timestamp,
|
|
235
|
+
activityCount: 1,
|
|
236
|
+
activityTypes: [record.activityType]
|
|
237
|
+
});
|
|
238
|
+
else {
|
|
239
|
+
aggregate.activityCount++;
|
|
240
|
+
if (record.timestamp < aggregate.timestamp) aggregate.timestamp = record.timestamp;
|
|
241
|
+
if (!aggregate.activityTypes.includes(record.activityType)) aggregate.activityTypes.push(record.activityType);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const summaries = [...aggregates].map(([traceId, aggregate]) => ({
|
|
245
|
+
traceId,
|
|
246
|
+
...aggregate
|
|
247
|
+
}));
|
|
242
248
|
summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
243
249
|
if (options?.limit != null) return summaries.slice(0, options.limit);
|
|
244
250
|
return summaries;
|
package/dist/otel/mod.d.cts
CHANGED
|
@@ -225,6 +225,19 @@ declare class FedifySpanExporter implements SpanExporter {
|
|
|
225
225
|
/**
|
|
226
226
|
* Gets recent traces with summary information.
|
|
227
227
|
*
|
|
228
|
+
* Each summary is computed directly from the activity records currently
|
|
229
|
+
* stored for each trace, rather than from a separately maintained
|
|
230
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
231
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
232
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
233
|
+
* exporter) never completed, and that the count always reflects
|
|
234
|
+
* records that have not expired. It costs a full scan of every stored
|
|
235
|
+
* record on every call, including full inbound activity JSON. The
|
|
236
|
+
* `limit` option only truncates the returned summaries and does not
|
|
237
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
238
|
+
* account for the total retained data when choosing a TTL and polling
|
|
239
|
+
* interval.
|
|
240
|
+
*
|
|
228
241
|
* @param options Options for the query.
|
|
229
242
|
* @returns An array of trace summaries.
|
|
230
243
|
*/
|
package/dist/otel/mod.d.ts
CHANGED
|
@@ -225,6 +225,19 @@ declare class FedifySpanExporter implements SpanExporter {
|
|
|
225
225
|
/**
|
|
226
226
|
* Gets recent traces with summary information.
|
|
227
227
|
*
|
|
228
|
+
* Each summary is computed directly from the activity records currently
|
|
229
|
+
* stored for each trace, rather than from a separately maintained
|
|
230
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
231
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
232
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
233
|
+
* exporter) never completed, and that the count always reflects
|
|
234
|
+
* records that have not expired. It costs a full scan of every stored
|
|
235
|
+
* record on every call, including full inbound activity JSON. The
|
|
236
|
+
* `limit` option only truncates the returned summaries and does not
|
|
237
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
238
|
+
* account for the total retained data when choosing a TTL and polling
|
|
239
|
+
* interval.
|
|
240
|
+
*
|
|
228
241
|
* @param options Options for the query.
|
|
229
242
|
* @returns An array of trace summaries.
|
|
230
243
|
*/
|
package/dist/otel/mod.js
CHANGED
|
@@ -187,33 +187,6 @@ var FedifySpanExporter = class {
|
|
|
187
187
|
record.spanId
|
|
188
188
|
];
|
|
189
189
|
await this.#kv.set(key, record, options);
|
|
190
|
-
await this.#updateTraceSummary(record, options);
|
|
191
|
-
}
|
|
192
|
-
async #setWithCasRetry(key, transform, options) {
|
|
193
|
-
if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
|
|
194
|
-
const existing = await this.#kv.get(key);
|
|
195
|
-
const newValue = transform(existing);
|
|
196
|
-
if (await this.#kv.cas(key, existing, newValue, options)) return;
|
|
197
|
-
}
|
|
198
|
-
const newValue = transform(await this.#kv.get(key));
|
|
199
|
-
await this.#kv.set(key, newValue, options);
|
|
200
|
-
}
|
|
201
|
-
async #updateTraceSummary(record, options) {
|
|
202
|
-
const summaryKey = [
|
|
203
|
-
...this.#keyPrefix,
|
|
204
|
-
"_summaries",
|
|
205
|
-
record.traceId
|
|
206
|
-
];
|
|
207
|
-
await this.#setWithCasRetry(summaryKey, (existing) => {
|
|
208
|
-
const activityCount = existing != null ? existing.activityCount + 1 : 1;
|
|
209
|
-
const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
|
|
210
|
-
return {
|
|
211
|
-
traceId: existing?.traceId ?? record.traceId,
|
|
212
|
-
timestamp: existing?.timestamp ?? record.timestamp,
|
|
213
|
-
activityCount,
|
|
214
|
-
activityTypes
|
|
215
|
-
};
|
|
216
|
-
}, options);
|
|
217
190
|
}
|
|
218
191
|
/**
|
|
219
192
|
* Gets all activity records for a specific trace ID.
|
|
@@ -230,13 +203,46 @@ var FedifySpanExporter = class {
|
|
|
230
203
|
/**
|
|
231
204
|
* Gets recent traces with summary information.
|
|
232
205
|
*
|
|
206
|
+
* Each summary is computed directly from the activity records currently
|
|
207
|
+
* stored for each trace, rather than from a separately maintained
|
|
208
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
209
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
210
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
211
|
+
* exporter) never completed, and that the count always reflects
|
|
212
|
+
* records that have not expired. It costs a full scan of every stored
|
|
213
|
+
* record on every call, including full inbound activity JSON. The
|
|
214
|
+
* `limit` option only truncates the returned summaries and does not
|
|
215
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
216
|
+
* account for the total retained data when choosing a TTL and polling
|
|
217
|
+
* interval.
|
|
218
|
+
*
|
|
233
219
|
* @param options Options for the query.
|
|
234
220
|
* @returns An array of trace summaries.
|
|
235
221
|
*/
|
|
236
222
|
async getRecentTraces(options) {
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
for await (const entry of this.#kv.list(
|
|
223
|
+
const aggregates = /* @__PURE__ */ new Map();
|
|
224
|
+
const traceIdIndex = this.#keyPrefix.length;
|
|
225
|
+
for await (const entry of this.#kv.list(this.#keyPrefix)) {
|
|
226
|
+
const traceId = entry.key[traceIdIndex];
|
|
227
|
+
if (traceId == null || traceId === "_summaries") continue;
|
|
228
|
+
const record = entry.value;
|
|
229
|
+
if (record == null || typeof record.timestamp !== "string") continue;
|
|
230
|
+
const aggregate = aggregates.get(traceId);
|
|
231
|
+
if (aggregate == null) aggregates.set(traceId, {
|
|
232
|
+
timestamp: record.timestamp,
|
|
233
|
+
activityCount: 1,
|
|
234
|
+
activityTypes: [record.activityType]
|
|
235
|
+
});
|
|
236
|
+
else {
|
|
237
|
+
aggregate.activityCount++;
|
|
238
|
+
if (record.timestamp < aggregate.timestamp) aggregate.timestamp = record.timestamp;
|
|
239
|
+
if (!aggregate.activityTypes.includes(record.activityType)) aggregate.activityTypes.push(record.activityType);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const summaries = [...aggregates].map(([traceId, aggregate]) => ({
|
|
243
|
+
traceId,
|
|
244
|
+
...aggregate
|
|
245
|
+
}));
|
|
240
246
|
summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
241
247
|
if (options?.limit != null) return summaries.slice(0, options.limit);
|
|
242
248
|
return summaries;
|
|
@@ -1,8 +1,8 @@
|
|
|
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-
|
|
5
|
-
import { c as verifyKeyOwnership, n as fetchActorDocument, t as exportJwk } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-BUj4roJc.mjs";
|
|
5
|
+
import { c as verifyKeyOwnership, n as fetchActorDocument, t as exportJwk } from "./key-C3o3DGap.mjs";
|
|
6
6
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
7
7
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
8
8
|
import { getDocumentLoader } from "@fedify/vocab-runtime";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { r as fetchKey, s as validateCryptoKey } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-BUj4roJc.mjs";
|
|
5
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-C3o3DGap.mjs";
|
|
6
6
|
import { n as preloadedOnlyDocumentLoader } from "./public-audience-DpJqzPuh.mjs";
|
|
7
7
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-amccRMrV.mjs";
|
|
8
8
|
import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import { c as fetchKey, f as validateCryptoKey, o as exportJwk, p as verifyKeyOwnership, s as fetchActorDocument, v as name, y as version } from "./http-
|
|
3
|
+
import { c as fetchKey, f as validateCryptoKey, o as exportJwk, p as verifyKeyOwnership, s as fetchActorDocument, v as name, y as version } from "./http-C5g_VrA_.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { Activity, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1, PUBLIC_COLLECTION, getTypeId, isActor } from "@fedify/vocab";
|
|
6
6
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { Temporal } = require("@js-temporal/polyfill");
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
const require_chunk = require("./chunk-DDcVe30Y.cjs");
|
|
4
|
-
const require_http = require("./http-
|
|
4
|
+
const require_http = require("./http-D8I7LBfx.cjs");
|
|
5
5
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
6
|
let _fedify_vocab = require("@fedify/vocab");
|
|
7
7
|
let _opentelemetry_api = require("@opentelemetry/api");
|