@fedify/fedify 2.0.35-dev.2470 → 2.0.35-dev.2485
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-CusH9K0G.mjs → builder-CKbQrOH1.mjs} +3 -3
- package/dist/{client-CAM_bQXx.d.cts → client-BIBzqEIp.d.cts} +0 -1
- package/dist/{client-BUqbea1d.mjs → client-DGNacEx_.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-r8sjhltN.d.cts → context-CnoWxuoB.d.cts} +2 -2
- package/dist/{context-B2iX3heK.d.ts → context-lxjaUTAI.d.ts} +2 -2
- package/dist/{deno-BtRoR9in.mjs → deno-DL_ifPJk.mjs} +1 -1
- package/dist/docloader-B0_0Fjqo.mjs +124 -0
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +6 -6
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +42 -23
- 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-Bs6thuCC.d.cts → http-Cd8UD1ym.d.ts} +1 -2
- package/dist/{http-C7d5tddX.cjs → http-CjkqnlR2.cjs} +114 -56
- package/dist/{http-CDupyfOc.d.ts → http-DFqAb11b.d.cts} +1 -2
- package/dist/{http-BTQ5wllw.js → http-DVEBwq6e.js} +113 -55
- package/dist/{http-CMduEDST.mjs → http-Dvsq4uCu.mjs} +136 -49
- package/dist/{inbox-BkvNt_Sb.mjs → inbox-D6YbZFrZ.mjs} +1 -1
- package/dist/{key-CDAqlyxF.mjs → key-CgcWGw6W.mjs} +1 -1
- package/dist/{kv-cache-Cm0DSKFo.js → kv-cache-DC_F2WSH.js} +60 -18
- package/dist/{kv-cache-D-xcxGwU.cjs → kv-cache-nN9RaiYp.cjs} +59 -17
- package/dist/{ld-Ceiqwon2.mjs → ld-CfOIwHup.mjs} +2 -2
- package/dist/{middleware-NVjGfqSC.js → middleware-B0eqDUmD.js} +118 -33
- package/dist/{middleware-Bj0R0_D7.mjs → middleware-CA7egMDR.mjs} +12 -12
- package/dist/{middleware-Bx0vwC0P.cjs → middleware-Cu_yKroA.cjs} +1 -1
- package/dist/{middleware-bv6duFIF.mjs → middleware-DEwj7jGj.mjs} +1 -1
- package/dist/{middleware-BWtJCObq.cjs → middleware-DZvWX5af.cjs} +118 -33
- package/dist/{mod-BkPB4hPe.d.cts → mod-BPDfu4Ey.d.cts} +2 -7
- package/dist/{mod-E7YM72JZ.d.ts → mod-BS-jg3Wx.d.ts} +1 -1
- package/dist/{mod-BKJyD7nk.d.ts → mod-DGP89q0u.d.ts} +2 -7
- package/dist/{mod-IOWUF5Yr.d.cts → mod-DVPS4yam.d.cts} +1 -1
- package/dist/{mod-CM_eR8hQ.d.ts → mod-I-34OMN3.d.ts} +1 -1
- package/dist/{mod-DDTLbfce.d.cts → mod-w1oyHseQ.d.cts} +1 -1
- 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-Cmn2WKBi.mjs → owner-BvVwCoj2.mjs} +2 -2
- package/dist/{proof-DllVQdfR.cjs → proof-BssSZpMi.cjs} +1 -1
- package/dist/{proof-VZ9Ryp3F.mjs → proof-C9TBSmUR.mjs} +2 -2
- package/dist/{proof-ClDA_fq4.js → proof-Cp62GFJ3.js} +1 -1
- package/dist/{send-BiC5gxMS.mjs → send-ZWmjk2Zf.mjs} +117 -32
- package/dist/sig/http.test.mjs +411 -2
- package/dist/sig/key.test.mjs +2 -2
- package/dist/sig/ld.test.mjs +2 -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 +1 -1
- package/dist/sig/proof.test.mjs +1 -1
- package/dist/{temporal-DXm8zGR8.mjs → temporal-BNtFi_ut.mjs} +1 -1
- package/dist/testing/mod.d.mts +0 -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 +316 -32
- 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-BF0FJHJ1.mjs +0 -82
|
@@ -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-DflMZha5.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";
|
|
@@ -187,33 +188,6 @@ var FedifySpanExporter = class {
|
|
|
187
188
|
record.spanId
|
|
188
189
|
];
|
|
189
190
|
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
191
|
}
|
|
218
192
|
/**
|
|
219
193
|
* Gets all activity records for a specific trace ID.
|
|
@@ -230,13 +204,46 @@ var FedifySpanExporter = class {
|
|
|
230
204
|
/**
|
|
231
205
|
* Gets recent traces with summary information.
|
|
232
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
|
+
*
|
|
233
220
|
* @param options Options for the query.
|
|
234
221
|
* @returns An array of trace summaries.
|
|
235
222
|
*/
|
|
236
223
|
async getRecentTraces(options) {
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
for await (const entry of this.#kv.list(
|
|
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
|
+
}));
|
|
240
247
|
summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
241
248
|
if (options?.limit != null) return summaries.slice(0, options.limit);
|
|
242
249
|
return summaries;
|
|
@@ -497,6 +504,261 @@ test("FedifySpanExporter", async (t) => {
|
|
|
497
504
|
}
|
|
498
505
|
assertEquals((await exporter.getRecentTraces()).length >= 3, true);
|
|
499
506
|
});
|
|
507
|
+
function createOutboundSpan(traceId, spanId) {
|
|
508
|
+
return createMockSpan({
|
|
509
|
+
traceId,
|
|
510
|
+
spanId,
|
|
511
|
+
name: "activitypub.send_activity",
|
|
512
|
+
events: [createActivitySentEvent({
|
|
513
|
+
activityJson: JSON.stringify({
|
|
514
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
515
|
+
type: "Create",
|
|
516
|
+
id: `https://local.example/activities/${spanId}`
|
|
517
|
+
}),
|
|
518
|
+
inboxUrl: "https://follower.example/inbox"
|
|
519
|
+
})]
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
async function exportSpans(exporter, spans) {
|
|
523
|
+
assertEquals((await new Promise((resolve) => {
|
|
524
|
+
exporter.export(spans, resolve);
|
|
525
|
+
})).code, 0);
|
|
526
|
+
}
|
|
527
|
+
for (const withCas of [true, false]) {
|
|
528
|
+
const createStore = () => {
|
|
529
|
+
const memory = new MemoryKvStore();
|
|
530
|
+
return withCas ? memory : {
|
|
531
|
+
get: memory.get.bind(memory),
|
|
532
|
+
set: memory.set.bind(memory),
|
|
533
|
+
delete: memory.delete.bind(memory),
|
|
534
|
+
list: memory.list.bind(memory)
|
|
535
|
+
};
|
|
536
|
+
};
|
|
537
|
+
await t.step(`re-export counts each retained span once (CAS=${withCas})`, async () => {
|
|
538
|
+
const kv = createStore();
|
|
539
|
+
const exporters = [new FedifySpanExporter(kv), new FedifySpanExporter(kv)];
|
|
540
|
+
const span = createOutboundSpan("reexport-trace", "span-a");
|
|
541
|
+
await exportSpans(exporters[0], [span]);
|
|
542
|
+
await exportSpans(exporters[0], [span]);
|
|
543
|
+
assertEquals((await exporters[1].getRecentTraces())[0].activityCount, 1);
|
|
544
|
+
await Promise.all(exporters.map((exporter) => exportSpans(exporter, [span])));
|
|
545
|
+
assertEquals((await exporters[0].getActivitiesByTraceId("reexport-trace")).length, 1);
|
|
546
|
+
assertEquals((await exporters[1].getRecentTraces())[0].activityCount, 1);
|
|
547
|
+
});
|
|
548
|
+
await t.step(`concurrent distinct spans are all counted (CAS=${withCas})`, async () => {
|
|
549
|
+
const kv = createStore();
|
|
550
|
+
const exporters = [new FedifySpanExporter(kv), new FedifySpanExporter(kv)];
|
|
551
|
+
await Promise.all(exporters.map((exporter, i) => exportSpans(exporter, [createOutboundSpan("concurrent-count-trace", `span-${i}`)])));
|
|
552
|
+
const reader = new FedifySpanExporter(kv);
|
|
553
|
+
assertEquals((await reader.getActivitiesByTraceId("concurrent-count-trace")).length, 2);
|
|
554
|
+
assertEquals((await reader.getRecentTraces())[0].activityCount, 2);
|
|
555
|
+
});
|
|
556
|
+
await t.step(`legacy summaries cannot hide or inflate retained records (CAS=${withCas})`, async () => {
|
|
557
|
+
const kv = createStore();
|
|
558
|
+
const exporter = new FedifySpanExporter(kv);
|
|
559
|
+
await exportSpans(exporter, [createOutboundSpan("legacy-trace", "span-a")]);
|
|
560
|
+
await kv.delete([
|
|
561
|
+
"fedify",
|
|
562
|
+
"traces",
|
|
563
|
+
"_summaries",
|
|
564
|
+
"legacy-trace"
|
|
565
|
+
]);
|
|
566
|
+
const originalSet = kv.set.bind(kv);
|
|
567
|
+
let writes = 0;
|
|
568
|
+
kv.set = (...args) => {
|
|
569
|
+
writes++;
|
|
570
|
+
return originalSet(...args);
|
|
571
|
+
};
|
|
572
|
+
assertEquals((await exporter.getRecentTraces())[0].activityCount, 1);
|
|
573
|
+
assertEquals(writes, 0);
|
|
574
|
+
const stale = {
|
|
575
|
+
traceId: "legacy-trace",
|
|
576
|
+
timestamp: "2000-01-01T00:00:00.000Z",
|
|
577
|
+
activityCount: 99,
|
|
578
|
+
activityTypes: ["Bogus"]
|
|
579
|
+
};
|
|
580
|
+
await kv.set([
|
|
581
|
+
"fedify",
|
|
582
|
+
"traces",
|
|
583
|
+
"_summaries",
|
|
584
|
+
"legacy-trace"
|
|
585
|
+
], stale);
|
|
586
|
+
await kv.set([
|
|
587
|
+
"fedify",
|
|
588
|
+
"traces",
|
|
589
|
+
"_summaries",
|
|
590
|
+
"ghost-trace"
|
|
591
|
+
], {
|
|
592
|
+
...stale,
|
|
593
|
+
traceId: "ghost-trace"
|
|
594
|
+
});
|
|
595
|
+
const records = await exporter.getActivitiesByTraceId("legacy-trace");
|
|
596
|
+
assertEquals(await exporter.getRecentTraces(), [{
|
|
597
|
+
traceId: "legacy-trace",
|
|
598
|
+
timestamp: records[0].timestamp,
|
|
599
|
+
activityCount: 1,
|
|
600
|
+
activityTypes: ["Create"]
|
|
601
|
+
}]);
|
|
602
|
+
await kv.delete([
|
|
603
|
+
"fedify",
|
|
604
|
+
"traces",
|
|
605
|
+
"legacy-trace",
|
|
606
|
+
"span-a"
|
|
607
|
+
]);
|
|
608
|
+
assertEquals(await exporter.getRecentTraces(), []);
|
|
609
|
+
});
|
|
610
|
+
await t.step(`summary metadata follows retained records (CAS=${withCas})`, async () => {
|
|
611
|
+
const kv = createStore();
|
|
612
|
+
const exporter = new FedifySpanExporter(kv, { keyPrefix: ["custom"] });
|
|
613
|
+
function span(traceId, spanId, type, seconds) {
|
|
614
|
+
return createMockSpan({
|
|
615
|
+
traceId,
|
|
616
|
+
spanId,
|
|
617
|
+
events: [{
|
|
618
|
+
...createActivitySentEvent({
|
|
619
|
+
activityJson: JSON.stringify({ type }),
|
|
620
|
+
inboxUrl: "https://example.com/inbox"
|
|
621
|
+
}),
|
|
622
|
+
time: [seconds, 0]
|
|
623
|
+
}]
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
await exportSpans(exporter, [
|
|
627
|
+
span("older-trace", "late", "Like", 1700000002),
|
|
628
|
+
span("older-trace", "early", "Create", 17e8),
|
|
629
|
+
span("older-trace", "duplicate-type", "Like", 1700000003),
|
|
630
|
+
span("newer-trace", "only", "Follow", 1700000001)
|
|
631
|
+
]);
|
|
632
|
+
const recent = await exporter.getRecentTraces();
|
|
633
|
+
assertEquals(recent.map((summary) => summary.traceId), ["newer-trace", "older-trace"]);
|
|
634
|
+
assertEquals(recent[1], {
|
|
635
|
+
traceId: "older-trace",
|
|
636
|
+
timestamp: (/* @__PURE__ */ new Date(17e11)).toISOString(),
|
|
637
|
+
activityCount: 3,
|
|
638
|
+
activityTypes: ["Like", "Create"]
|
|
639
|
+
});
|
|
640
|
+
assertEquals(await exporter.getRecentTraces({ limit: 1 }), [recent[0]]);
|
|
641
|
+
await kv.delete([
|
|
642
|
+
"custom",
|
|
643
|
+
"older-trace",
|
|
644
|
+
"early"
|
|
645
|
+
]);
|
|
646
|
+
assertEquals((await exporter.getRecentTraces())[0], {
|
|
647
|
+
traceId: "older-trace",
|
|
648
|
+
timestamp: (/* @__PURE__ */ new Date(1700000002e3)).toISOString(),
|
|
649
|
+
activityCount: 2,
|
|
650
|
+
activityTypes: ["Like"]
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
await t.step("a trace with only one record is discoverable via getRecentTraces(), with no further write", async () => {
|
|
655
|
+
const exporter = new FedifySpanExporter(new MemoryKvStore());
|
|
656
|
+
const traceId = "first-write-discoverable-trace";
|
|
657
|
+
await new Promise((resolve) => {
|
|
658
|
+
exporter.export([createOutboundSpan(traceId, "span-a")], () => resolve());
|
|
659
|
+
});
|
|
660
|
+
assertEquals((await exporter.getRecentTraces()).find((s) => s.traceId === traceId)?.activityCount, 1);
|
|
661
|
+
});
|
|
662
|
+
await t.step("getRecentTraces() needs no additional writes to discover a trace or correct its activityCount", async () => {
|
|
663
|
+
const kv = new MemoryKvStore();
|
|
664
|
+
const exporter = new FedifySpanExporter(kv);
|
|
665
|
+
const traceId = "query-time-correction-trace";
|
|
666
|
+
await new Promise((resolve) => {
|
|
667
|
+
exporter.export([createOutboundSpan(traceId, "span-a")], () => resolve());
|
|
668
|
+
});
|
|
669
|
+
await new Promise((resolve) => {
|
|
670
|
+
exporter.export([createOutboundSpan(traceId, "span-b")], () => resolve());
|
|
671
|
+
});
|
|
672
|
+
const originalSet = kv.set.bind(kv);
|
|
673
|
+
let writeCalls = 0;
|
|
674
|
+
kv.set = (key, value, options) => {
|
|
675
|
+
writeCalls++;
|
|
676
|
+
return originalSet(key, value, options);
|
|
677
|
+
};
|
|
678
|
+
assertEquals((await exporter.getRecentTraces()).find((s) => s.traceId === traceId)?.activityCount, 2);
|
|
679
|
+
assertEquals(writeCalls, 0);
|
|
680
|
+
});
|
|
681
|
+
await t.step("getRecentTraces() reflects only currently-retained records, e.g., after some have expired", async () => {
|
|
682
|
+
const kv = new MemoryKvStore();
|
|
683
|
+
const exporter = new FedifySpanExporter(kv);
|
|
684
|
+
const traceId = "expiry-reflects-retained-trace";
|
|
685
|
+
await new Promise((resolve) => {
|
|
686
|
+
exporter.export([createOutboundSpan(traceId, "span-a"), createOutboundSpan(traceId, "span-b")], () => resolve());
|
|
687
|
+
});
|
|
688
|
+
let recent = await exporter.getRecentTraces();
|
|
689
|
+
assertEquals(recent.find((s) => s.traceId === traceId)?.activityCount, 2);
|
|
690
|
+
await kv.delete([
|
|
691
|
+
"fedify",
|
|
692
|
+
"traces",
|
|
693
|
+
traceId,
|
|
694
|
+
"span-a"
|
|
695
|
+
]);
|
|
696
|
+
recent = await exporter.getRecentTraces();
|
|
697
|
+
assertEquals(recent.find((s) => s.traceId === traceId)?.activityCount, 1);
|
|
698
|
+
});
|
|
699
|
+
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 () => {
|
|
700
|
+
const kv = new MemoryKvStore();
|
|
701
|
+
const exporter = new FedifySpanExporter(kv);
|
|
702
|
+
const traceCount = 20;
|
|
703
|
+
const recordsPerTrace = 5;
|
|
704
|
+
for (let i = 0; i < traceCount; i++) {
|
|
705
|
+
const traceId = `poll-cost-trace-${i}`;
|
|
706
|
+
const spans = Array.from({ length: recordsPerTrace }, (_, j) => createOutboundSpan(traceId, `span-${j}`));
|
|
707
|
+
await new Promise((resolve) => {
|
|
708
|
+
exporter.export(spans, () => resolve());
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
const originalList = kv.list.bind(kv);
|
|
712
|
+
let entriesRead = 0;
|
|
713
|
+
let bytesRead = 0;
|
|
714
|
+
kv.list = async function* (prefix) {
|
|
715
|
+
for await (const entry of originalList(prefix)) {
|
|
716
|
+
entriesRead++;
|
|
717
|
+
bytesRead += new TextEncoder().encode(JSON.stringify(entry.value)).byteLength;
|
|
718
|
+
yield entry;
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
assertEquals((await exporter.getRecentTraces()).length, traceCount);
|
|
722
|
+
assertEquals(entriesRead, traceCount * recordsPerTrace);
|
|
723
|
+
assert(bytesRead > 0);
|
|
724
|
+
const entriesReadForFullScan = entriesRead;
|
|
725
|
+
const bytesReadForFullScan = bytesRead;
|
|
726
|
+
entriesRead = 0;
|
|
727
|
+
bytesRead = 0;
|
|
728
|
+
assertEquals((await exporter.getRecentTraces({ limit: 3 })).length, 3);
|
|
729
|
+
assertEquals(entriesRead, entriesReadForFullScan);
|
|
730
|
+
assertEquals(bytesRead, bytesReadForFullScan);
|
|
731
|
+
});
|
|
732
|
+
await t.step("recent-list scans include full inbound payloads even with limit", async () => {
|
|
733
|
+
const kv = new MemoryKvStore();
|
|
734
|
+
const exporter = new FedifySpanExporter(kv);
|
|
735
|
+
const activityJson = JSON.stringify({
|
|
736
|
+
type: "Create",
|
|
737
|
+
object: {
|
|
738
|
+
type: "Note",
|
|
739
|
+
content: "한".repeat(64 * 1024)
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
await exportSpans(exporter, Array.from({ length: 10 }, (_, i) => createMockSpan({
|
|
743
|
+
traceId: `large-inbound-${i}`,
|
|
744
|
+
spanId: "inbound",
|
|
745
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
746
|
+
})));
|
|
747
|
+
const originalList = kv.list.bind(kv);
|
|
748
|
+
let entriesRead = 0;
|
|
749
|
+
let bytesRead = 0;
|
|
750
|
+
kv.list = async function* (prefix) {
|
|
751
|
+
for await (const entry of originalList(prefix)) {
|
|
752
|
+
entriesRead++;
|
|
753
|
+
bytesRead += new TextEncoder().encode(JSON.stringify(entry.value)).byteLength;
|
|
754
|
+
yield entry;
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
assertEquals((await exporter.getRecentTraces({ limit: 1 })).length, 1);
|
|
758
|
+
assertEquals(entriesRead, 10);
|
|
759
|
+
assert(bytesRead >= new TextEncoder().encode(activityJson).byteLength * 10);
|
|
760
|
+
assert(bytesRead > 1.8 * 1024 * 1024);
|
|
761
|
+
});
|
|
500
762
|
await t.step("forceFlush() returns resolved promise", async () => {
|
|
501
763
|
await new FedifySpanExporter(new MemoryKvStore()).forceFlush();
|
|
502
764
|
});
|
package/dist/otel/mod.cjs
CHANGED
|
@@ -183,33 +183,6 @@ var FedifySpanExporter = class {
|
|
|
183
183
|
record.spanId
|
|
184
184
|
];
|
|
185
185
|
await this.#kv.set(key, record, options);
|
|
186
|
-
await this.#updateTraceSummary(record, options);
|
|
187
|
-
}
|
|
188
|
-
async #setWithCasRetry(key, transform, options) {
|
|
189
|
-
if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
|
|
190
|
-
const existing = await this.#kv.get(key);
|
|
191
|
-
const newValue = transform(existing);
|
|
192
|
-
if (await this.#kv.cas(key, existing, newValue, options)) return;
|
|
193
|
-
}
|
|
194
|
-
const newValue = transform(await this.#kv.get(key));
|
|
195
|
-
await this.#kv.set(key, newValue, options);
|
|
196
|
-
}
|
|
197
|
-
async #updateTraceSummary(record, options) {
|
|
198
|
-
const summaryKey = [
|
|
199
|
-
...this.#keyPrefix,
|
|
200
|
-
"_summaries",
|
|
201
|
-
record.traceId
|
|
202
|
-
];
|
|
203
|
-
await this.#setWithCasRetry(summaryKey, (existing) => {
|
|
204
|
-
const activityCount = existing != null ? existing.activityCount + 1 : 1;
|
|
205
|
-
const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
|
|
206
|
-
return {
|
|
207
|
-
traceId: existing?.traceId ?? record.traceId,
|
|
208
|
-
timestamp: existing?.timestamp ?? record.timestamp,
|
|
209
|
-
activityCount,
|
|
210
|
-
activityTypes
|
|
211
|
-
};
|
|
212
|
-
}, options);
|
|
213
186
|
}
|
|
214
187
|
/**
|
|
215
188
|
* Gets all activity records for a specific trace ID.
|
|
@@ -226,13 +199,46 @@ var FedifySpanExporter = class {
|
|
|
226
199
|
/**
|
|
227
200
|
* Gets recent traces with summary information.
|
|
228
201
|
*
|
|
202
|
+
* Each summary is computed directly from the activity records currently
|
|
203
|
+
* stored for each trace, rather than from a separately maintained
|
|
204
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
205
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
206
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
207
|
+
* exporter) never completed, and that the count always reflects
|
|
208
|
+
* records that have not expired. It costs a full scan of every stored
|
|
209
|
+
* record on every call, including full inbound activity JSON. The
|
|
210
|
+
* `limit` option only truncates the returned summaries and does not
|
|
211
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
212
|
+
* account for the total retained data when choosing a TTL and polling
|
|
213
|
+
* interval.
|
|
214
|
+
*
|
|
229
215
|
* @param options Options for the query.
|
|
230
216
|
* @returns An array of trace summaries.
|
|
231
217
|
*/
|
|
232
218
|
async getRecentTraces(options) {
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
for await (const entry of this.#kv.list(
|
|
219
|
+
const aggregates = /* @__PURE__ */ new Map();
|
|
220
|
+
const traceIdIndex = this.#keyPrefix.length;
|
|
221
|
+
for await (const entry of this.#kv.list(this.#keyPrefix)) {
|
|
222
|
+
const traceId = entry.key[traceIdIndex];
|
|
223
|
+
if (traceId == null || traceId === "_summaries") continue;
|
|
224
|
+
const record = entry.value;
|
|
225
|
+
if (record == null || typeof record.timestamp !== "string") continue;
|
|
226
|
+
const aggregate = aggregates.get(traceId);
|
|
227
|
+
if (aggregate == null) aggregates.set(traceId, {
|
|
228
|
+
timestamp: record.timestamp,
|
|
229
|
+
activityCount: 1,
|
|
230
|
+
activityTypes: [record.activityType]
|
|
231
|
+
});
|
|
232
|
+
else {
|
|
233
|
+
aggregate.activityCount++;
|
|
234
|
+
if (record.timestamp < aggregate.timestamp) aggregate.timestamp = record.timestamp;
|
|
235
|
+
if (!aggregate.activityTypes.includes(record.activityType)) aggregate.activityTypes.push(record.activityType);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const summaries = [...aggregates].map(([traceId, aggregate]) => ({
|
|
239
|
+
traceId,
|
|
240
|
+
...aggregate
|
|
241
|
+
}));
|
|
236
242
|
summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
237
243
|
if (options?.limit != null) return summaries.slice(0, options.limit);
|
|
238
244
|
return summaries;
|
package/dist/otel/mod.d.cts
CHANGED
|
@@ -213,6 +213,19 @@ declare class FedifySpanExporter implements SpanExporter {
|
|
|
213
213
|
/**
|
|
214
214
|
* Gets recent traces with summary information.
|
|
215
215
|
*
|
|
216
|
+
* Each summary is computed directly from the activity records currently
|
|
217
|
+
* stored for each trace, rather than from a separately maintained
|
|
218
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
219
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
220
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
221
|
+
* exporter) never completed, and that the count always reflects
|
|
222
|
+
* records that have not expired. It costs a full scan of every stored
|
|
223
|
+
* record on every call, including full inbound activity JSON. The
|
|
224
|
+
* `limit` option only truncates the returned summaries and does not
|
|
225
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
226
|
+
* account for the total retained data when choosing a TTL and polling
|
|
227
|
+
* interval.
|
|
228
|
+
*
|
|
216
229
|
* @param options Options for the query.
|
|
217
230
|
* @returns An array of trace summaries.
|
|
218
231
|
*/
|
package/dist/otel/mod.d.ts
CHANGED
|
@@ -213,6 +213,19 @@ declare class FedifySpanExporter implements SpanExporter {
|
|
|
213
213
|
/**
|
|
214
214
|
* Gets recent traces with summary information.
|
|
215
215
|
*
|
|
216
|
+
* Each summary is computed directly from the activity records currently
|
|
217
|
+
* stored for each trace, rather than from a separately maintained
|
|
218
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
219
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
220
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
221
|
+
* exporter) never completed, and that the count always reflects
|
|
222
|
+
* records that have not expired. It costs a full scan of every stored
|
|
223
|
+
* record on every call, including full inbound activity JSON. The
|
|
224
|
+
* `limit` option only truncates the returned summaries and does not
|
|
225
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
226
|
+
* account for the total retained data when choosing a TTL and polling
|
|
227
|
+
* interval.
|
|
228
|
+
*
|
|
216
229
|
* @param options Options for the query.
|
|
217
230
|
* @returns An array of trace summaries.
|
|
218
231
|
*/
|
package/dist/otel/mod.js
CHANGED
|
@@ -181,33 +181,6 @@ var FedifySpanExporter = class {
|
|
|
181
181
|
record.spanId
|
|
182
182
|
];
|
|
183
183
|
await this.#kv.set(key, record, options);
|
|
184
|
-
await this.#updateTraceSummary(record, options);
|
|
185
|
-
}
|
|
186
|
-
async #setWithCasRetry(key, transform, options) {
|
|
187
|
-
if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
|
|
188
|
-
const existing = await this.#kv.get(key);
|
|
189
|
-
const newValue = transform(existing);
|
|
190
|
-
if (await this.#kv.cas(key, existing, newValue, options)) return;
|
|
191
|
-
}
|
|
192
|
-
const newValue = transform(await this.#kv.get(key));
|
|
193
|
-
await this.#kv.set(key, newValue, options);
|
|
194
|
-
}
|
|
195
|
-
async #updateTraceSummary(record, options) {
|
|
196
|
-
const summaryKey = [
|
|
197
|
-
...this.#keyPrefix,
|
|
198
|
-
"_summaries",
|
|
199
|
-
record.traceId
|
|
200
|
-
];
|
|
201
|
-
await this.#setWithCasRetry(summaryKey, (existing) => {
|
|
202
|
-
const activityCount = existing != null ? existing.activityCount + 1 : 1;
|
|
203
|
-
const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
|
|
204
|
-
return {
|
|
205
|
-
traceId: existing?.traceId ?? record.traceId,
|
|
206
|
-
timestamp: existing?.timestamp ?? record.timestamp,
|
|
207
|
-
activityCount,
|
|
208
|
-
activityTypes
|
|
209
|
-
};
|
|
210
|
-
}, options);
|
|
211
184
|
}
|
|
212
185
|
/**
|
|
213
186
|
* Gets all activity records for a specific trace ID.
|
|
@@ -224,13 +197,46 @@ var FedifySpanExporter = class {
|
|
|
224
197
|
/**
|
|
225
198
|
* Gets recent traces with summary information.
|
|
226
199
|
*
|
|
200
|
+
* Each summary is computed directly from the activity records currently
|
|
201
|
+
* stored for each trace, rather than from a separately maintained
|
|
202
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
203
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
204
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
205
|
+
* exporter) never completed, and that the count always reflects
|
|
206
|
+
* records that have not expired. It costs a full scan of every stored
|
|
207
|
+
* record on every call, including full inbound activity JSON. The
|
|
208
|
+
* `limit` option only truncates the returned summaries and does not
|
|
209
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
210
|
+
* account for the total retained data when choosing a TTL and polling
|
|
211
|
+
* interval.
|
|
212
|
+
*
|
|
227
213
|
* @param options Options for the query.
|
|
228
214
|
* @returns An array of trace summaries.
|
|
229
215
|
*/
|
|
230
216
|
async getRecentTraces(options) {
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
for await (const entry of this.#kv.list(
|
|
217
|
+
const aggregates = /* @__PURE__ */ new Map();
|
|
218
|
+
const traceIdIndex = this.#keyPrefix.length;
|
|
219
|
+
for await (const entry of this.#kv.list(this.#keyPrefix)) {
|
|
220
|
+
const traceId = entry.key[traceIdIndex];
|
|
221
|
+
if (traceId == null || traceId === "_summaries") continue;
|
|
222
|
+
const record = entry.value;
|
|
223
|
+
if (record == null || typeof record.timestamp !== "string") continue;
|
|
224
|
+
const aggregate = aggregates.get(traceId);
|
|
225
|
+
if (aggregate == null) aggregates.set(traceId, {
|
|
226
|
+
timestamp: record.timestamp,
|
|
227
|
+
activityCount: 1,
|
|
228
|
+
activityTypes: [record.activityType]
|
|
229
|
+
});
|
|
230
|
+
else {
|
|
231
|
+
aggregate.activityCount++;
|
|
232
|
+
if (record.timestamp < aggregate.timestamp) aggregate.timestamp = record.timestamp;
|
|
233
|
+
if (!aggregate.activityTypes.includes(record.activityType)) aggregate.activityTypes.push(record.activityType);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const summaries = [...aggregates].map(([traceId, aggregate]) => ({
|
|
237
|
+
traceId,
|
|
238
|
+
...aggregate
|
|
239
|
+
}));
|
|
234
240
|
summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
235
241
|
if (options?.limit != null) return summaries.slice(0, options.limit);
|
|
236
242
|
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 { n as fetchActorDocument, s as verifyKeyOwnership, t as exportJwk } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-DL_ifPJk.mjs";
|
|
5
|
+
import { n as fetchActorDocument, s as verifyKeyOwnership, t as exportJwk } from "./key-CgcWGw6W.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,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-CjkqnlR2.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");
|
|
@@ -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 { o as validateCryptoKey, r as fetchKey } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-DL_ifPJk.mjs";
|
|
5
|
+
import { o as validateCryptoKey, r as fetchKey } from "./key-CgcWGw6W.mjs";
|
|
6
6
|
import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
|
|
7
7
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
8
8
|
import { getLogger } from "@logtape/logtape";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import { a as fetchActorDocument, d as name, f as version, i as exportJwk, l as validateCryptoKey, o as fetchKey, u as verifyKeyOwnership } from "./http-
|
|
3
|
+
import { a as fetchActorDocument, d as name, f as version, i as exportJwk, l as validateCryptoKey, o as fetchKey, u as verifyKeyOwnership } from "./http-DVEBwq6e.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { Activity, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1, getTypeId, isActor } from "@fedify/vocab";
|
|
6
6
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|