@jeffjassky/telemetry 0.4.0 → 0.6.0
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/index.cjs +319 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +317 -46
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/ui/_assets/index-kvwB9_3A.js +41 -0
- package/dist/ui/_assets/{index-Deqhu-hT.js.map → index-kvwB9_3A.js.map} +1 -1
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/types/index.d.ts +162 -0
- package/types/test-d.ts +38 -0
- package/dist/ui/_assets/index-Deqhu-hT.js +0 -41
package/dist/index.js
CHANGED
|
@@ -61,7 +61,13 @@ var newCounters = () => ({
|
|
|
61
61
|
deduped: 0,
|
|
62
62
|
truncated: 0,
|
|
63
63
|
rollupSkippedBy: {},
|
|
64
|
-
undeclaredAttrs: {}
|
|
64
|
+
undeclaredAttrs: {},
|
|
65
|
+
subjectsLinked: 0,
|
|
66
|
+
subjectLinkMisses: 0,
|
|
67
|
+
subjectLinkErrors: 0,
|
|
68
|
+
subjectLinkTimeouts: 0,
|
|
69
|
+
subjectLinkUndeclared: 0,
|
|
70
|
+
subjectLinkCapped: 0
|
|
65
71
|
});
|
|
66
72
|
var COUNTER_MAP_MAX = 1e3;
|
|
67
73
|
var COUNTER_OVERFLOW_KEY = "(other)|(other)";
|
|
@@ -545,10 +551,11 @@ var truncate = (d, b) => {
|
|
|
545
551
|
if (b === "day") return day;
|
|
546
552
|
return new Date(day.getTime() - (day.getUTCDay() + 6) % 7 * 864e5);
|
|
547
553
|
};
|
|
548
|
-
async function recordRollup(RollupModel, doc, name, spec, counters) {
|
|
554
|
+
async function recordRollup(RollupModel, doc, name, spec, counters, opts) {
|
|
555
|
+
const dry = opts?.dryRun === true;
|
|
549
556
|
if (spec.actors && doc.actor) {
|
|
550
557
|
const actorType = String(doc.actor).split(":")[0];
|
|
551
|
-
if (!spec.actors.includes(actorType)) return;
|
|
558
|
+
if (!spec.actors.includes(actorType)) return 0;
|
|
552
559
|
}
|
|
553
560
|
const as = spec.as ?? name;
|
|
554
561
|
const at = doc.occurredAt;
|
|
@@ -560,9 +567,11 @@ async function recordRollup(RollupModel, doc, name, spec, counters) {
|
|
|
560
567
|
let v = resolveDim(src, doc);
|
|
561
568
|
if (v == null || v === "") {
|
|
562
569
|
if (spec.dimDefault === void 0) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
570
|
+
if (!dry) {
|
|
571
|
+
counters.rollupSkipped++;
|
|
572
|
+
bumpCounterMap(counters.rollupSkippedBy, `${as}|${label(src)}`);
|
|
573
|
+
}
|
|
574
|
+
return 0;
|
|
566
575
|
}
|
|
567
576
|
v = spec.dimDefault;
|
|
568
577
|
}
|
|
@@ -572,48 +581,48 @@ async function recordRollup(RollupModel, doc, name, spec, counters) {
|
|
|
572
581
|
const refs = fansOut ? (doc.subjectKeys ?? []).filter(
|
|
573
582
|
(r) => !spec.subjects || spec.subjects.includes(r.split(":")[0])
|
|
574
583
|
) : [null];
|
|
575
|
-
if (!refs.length) return;
|
|
584
|
+
if (!refs.length) return 0;
|
|
576
585
|
const firstCapture = Object.fromEntries(
|
|
577
586
|
(spec.capture ?? []).map((src) => [label(src), resolveDim(src, doc)]).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
|
|
578
587
|
);
|
|
579
588
|
const expiresAt = spec.retentionDays != null ? new Date(at.getTime() + spec.retentionDays * 864e5) : void 0;
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
{
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
firstCapture: { $cond: [isNewFirst, { $literal: firstCapture }, "$firstCapture"] }
|
|
608
|
-
}
|
|
589
|
+
const ops = refs.map((ref) => {
|
|
590
|
+
const dims = spec.by.map((src) => src === "subject" ? ref : fixed.get(src));
|
|
591
|
+
const isNewFirst = {
|
|
592
|
+
$or: [{ $eq: [{ $type: "$firstAt" }, "missing"] }, { $lt: [at, "$firstAt"] }]
|
|
593
|
+
};
|
|
594
|
+
const sums = Object.fromEntries(
|
|
595
|
+
(spec.sum ?? []).map((k) => [k, doc.metrics?.get(k)]).filter(([, v]) => typeof v === "number").map(([k, v]) => [`sums.${k}`, { $add: [{ $ifNull: [`$sums.${k}`, 0] }, v] }])
|
|
596
|
+
);
|
|
597
|
+
return {
|
|
598
|
+
updateOne: {
|
|
599
|
+
filter: { _id: `${doc.tenantId}|${as}|${dims.join("|")}|${bucketKey}` },
|
|
600
|
+
update: [
|
|
601
|
+
{
|
|
602
|
+
$set: {
|
|
603
|
+
tenantId: doc.tenantId,
|
|
604
|
+
as,
|
|
605
|
+
dims,
|
|
606
|
+
...ref ? { subjectType: ref.split(":")[0] } : {},
|
|
607
|
+
...bucketAt ? { bucketAt } : {},
|
|
608
|
+
...expiresAt ? { expiresAt } : {},
|
|
609
|
+
// aggregation $min/$max ignore missing, so correct on insert too
|
|
610
|
+
firstAt: { $min: ["$firstAt", at] },
|
|
611
|
+
lastAt: { $max: ["$lastAt", at] },
|
|
612
|
+
count: { $add: [{ $ifNull: ["$count", 0] }, 1] },
|
|
613
|
+
...sums,
|
|
614
|
+
firstTraceId: { $cond: [isNewFirst, doc.traceId ?? null, "$firstTraceId"] },
|
|
615
|
+
firstCapture: { $cond: [isNewFirst, { $literal: firstCapture }, "$firstCapture"] }
|
|
609
616
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
);
|
|
617
|
+
}
|
|
618
|
+
],
|
|
619
|
+
upsert: true
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
});
|
|
623
|
+
if (dry) return ops.length;
|
|
624
|
+
await RollupModel.bulkWrite(ops, { ordered: false });
|
|
625
|
+
return ops.length;
|
|
617
626
|
}
|
|
618
627
|
function buildCheckpointModel(connection, modelName, collection) {
|
|
619
628
|
const existing = connection.models?.[modelName];
|
|
@@ -663,6 +672,110 @@ function noteUndeclaredAttrs(counters, name, spec, attrs) {
|
|
|
663
672
|
bumpCounterMap(counters.undeclaredAttrs, `${name}|${key}`);
|
|
664
673
|
}
|
|
665
674
|
}
|
|
675
|
+
var SUBJECT_MAX = 8;
|
|
676
|
+
var SUBJECT_LINK_TIMEOUT_MS = 50;
|
|
677
|
+
var LINK_TIMEOUT = /* @__PURE__ */ Symbol("telemetry.subjectLink.timeout");
|
|
678
|
+
function createSubjectLinking(opts) {
|
|
679
|
+
const { linker, counters, logger } = opts;
|
|
680
|
+
if (!linker) return null;
|
|
681
|
+
const timeoutMs = opts.timeoutMs ?? SUBJECT_LINK_TIMEOUT_MS;
|
|
682
|
+
const warned = /* @__PURE__ */ new Set();
|
|
683
|
+
const warnOnce = (key, msg) => {
|
|
684
|
+
if (warned.has(key) || warned.size >= COUNTER_MAP_MAX) return;
|
|
685
|
+
warned.add(key);
|
|
686
|
+
logger.warn(msg);
|
|
687
|
+
};
|
|
688
|
+
return async function linkSubjects(name, spec, tenantId, declared) {
|
|
689
|
+
const have = Array.isArray(declared) ? declared : [];
|
|
690
|
+
const seen = /* @__PURE__ */ new Set();
|
|
691
|
+
const view = [];
|
|
692
|
+
for (const s of have) {
|
|
693
|
+
const ref = wellFormed(s);
|
|
694
|
+
if (!ref) continue;
|
|
695
|
+
seen.add(`${ref.type}:${ref.id}`);
|
|
696
|
+
view.push(ref);
|
|
697
|
+
}
|
|
698
|
+
let out;
|
|
699
|
+
let timer;
|
|
700
|
+
try {
|
|
701
|
+
out = await Promise.race([
|
|
702
|
+
// the async wrapper turns a SYNCHRONOUS throw into a rejection, so a
|
|
703
|
+
// linker that dies on its first line lands in the same catch as one
|
|
704
|
+
// whose promise rejects
|
|
705
|
+
(async () => linker.link(view, { name, tenantId }))(),
|
|
706
|
+
new Promise((_, reject) => {
|
|
707
|
+
timer = setTimeout(() => reject(LINK_TIMEOUT), timeoutMs);
|
|
708
|
+
})
|
|
709
|
+
]);
|
|
710
|
+
} catch (e) {
|
|
711
|
+
if (e === LINK_TIMEOUT) {
|
|
712
|
+
counters.subjectLinkTimeouts++;
|
|
713
|
+
warnOnce(
|
|
714
|
+
"timeout",
|
|
715
|
+
`[telemetry] subjectLinker.link() exceeded ${timeoutMs}ms \u2014 records are being written UNLINKED rather than waiting. The hook is expected to answer from a cache; a resolver that queries per record cannot keep up with ingest. Warned once \u2014 the count is counters.subjectLinkTimeouts.`
|
|
716
|
+
);
|
|
717
|
+
} else {
|
|
718
|
+
counters.subjectLinkErrors++;
|
|
719
|
+
warnOnce(
|
|
720
|
+
"threw",
|
|
721
|
+
`[telemetry] subjectLinker.link() threw \u2014 records are being written unlinked: ${e}. Warned once \u2014 the count is counters.subjectLinkErrors.`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return null;
|
|
725
|
+
} finally {
|
|
726
|
+
clearTimeout(timer);
|
|
727
|
+
}
|
|
728
|
+
if (!Array.isArray(out)) {
|
|
729
|
+
counters.subjectLinkErrors++;
|
|
730
|
+
warnOnce(
|
|
731
|
+
"shape",
|
|
732
|
+
`[telemetry] subjectLinker.link() resolved to ${typeof out}, not an array \u2014 records are being written unlinked. Return [] when nothing links. Warned once \u2014 the count is counters.subjectLinkErrors.`
|
|
733
|
+
);
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
if (!out.length) {
|
|
737
|
+
counters.subjectLinkMisses++;
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
let room = Math.max(0, SUBJECT_MAX - have.length);
|
|
741
|
+
let capped2 = 0;
|
|
742
|
+
const add = [];
|
|
743
|
+
for (const s of out) {
|
|
744
|
+
const ref = wellFormed(s);
|
|
745
|
+
if (!ref) {
|
|
746
|
+
counters.subjectLinkErrors++;
|
|
747
|
+
warnOnce(
|
|
748
|
+
"entry",
|
|
749
|
+
"[telemetry] subjectLinker returned an entry that is not { type, id } \u2014 dropped. Warned once \u2014 the count is counters.subjectLinkErrors."
|
|
750
|
+
);
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
const key = `${ref.type}:${ref.id}`;
|
|
754
|
+
if (seen.has(key)) continue;
|
|
755
|
+
if (!spec.subjects.includes(ref.type)) {
|
|
756
|
+
counters.subjectLinkUndeclared++;
|
|
757
|
+
}
|
|
758
|
+
if (room <= 0) {
|
|
759
|
+
capped2++;
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
seen.add(key);
|
|
763
|
+
room--;
|
|
764
|
+
add.push(ref);
|
|
765
|
+
}
|
|
766
|
+
counters.subjectLinkCapped += capped2;
|
|
767
|
+
if (!add.length) return null;
|
|
768
|
+
counters.subjectsLinked += add.length;
|
|
769
|
+
return [...have, ...add];
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
function wellFormed(s) {
|
|
773
|
+
if (!s || typeof s !== "object") return null;
|
|
774
|
+
const { type, id, role } = s;
|
|
775
|
+
if (typeof type !== "string" || !type) return null;
|
|
776
|
+
if (typeof id !== "string" || !id) return null;
|
|
777
|
+
return typeof role === "string" && role ? { type, id, role } : { type, id };
|
|
778
|
+
}
|
|
666
779
|
function createEmitter(ctx) {
|
|
667
780
|
const { registry, byKind, RollupModel, rejects, counters } = ctx;
|
|
668
781
|
const burstBuckets = /* @__PURE__ */ new Map();
|
|
@@ -699,11 +812,15 @@ function createEmitter(ctx) {
|
|
|
699
812
|
const durable = kind === TelemetryKind.Usage || (doc.durable ?? spec.durable ?? false);
|
|
700
813
|
const Model = byKind[kind];
|
|
701
814
|
const { forceKeep: _drop, durable: _durable, ...rest } = doc;
|
|
815
|
+
const linked = ctx.linkSubjects ? await ctx.linkSubjects(name, spec, doc.tenantId, doc.subjects) : null;
|
|
702
816
|
const safe = (o) => new Map(Object.entries(o ?? {}).map(([k, v]) => [k.replace(/\./g, "_"), v]));
|
|
703
817
|
const payload = {
|
|
704
818
|
...rest,
|
|
705
819
|
_id: id,
|
|
706
820
|
name,
|
|
821
|
+
// computed like everything below it, and absent when nothing linked, so a
|
|
822
|
+
// host with no linker hands the model the exact object 0.4.0 did
|
|
823
|
+
...linked ? { subjects: linked } : {},
|
|
707
824
|
sampleRate: forced ? 1 : baseRate,
|
|
708
825
|
forced,
|
|
709
826
|
attrs: safe(doc.attrs),
|
|
@@ -869,6 +986,109 @@ function createForget(ctx) {
|
|
|
869
986
|
};
|
|
870
987
|
}
|
|
871
988
|
|
|
989
|
+
// src/server/relink.ts
|
|
990
|
+
var RELINK_BATCH_SIZE = 500;
|
|
991
|
+
var refKey = (s) => `${s.type}:${s.id}`;
|
|
992
|
+
function createRelink(ctx) {
|
|
993
|
+
const { registry, TelemetryModel, RollupModel, counters, logger, linkSubjects } = ctx;
|
|
994
|
+
return async function relink(opts = {}) {
|
|
995
|
+
const dryRun = opts.dryRun !== false;
|
|
996
|
+
const batchSize = opts.batchSize ?? RELINK_BATCH_SIZE;
|
|
997
|
+
const result = {
|
|
998
|
+
examined: 0,
|
|
999
|
+
linked: 0,
|
|
1000
|
+
subjects: 0,
|
|
1001
|
+
rollups: 0,
|
|
1002
|
+
misses: 0,
|
|
1003
|
+
errors: 0,
|
|
1004
|
+
skipped: 0
|
|
1005
|
+
};
|
|
1006
|
+
if (opts.names) {
|
|
1007
|
+
const unknown = opts.names.filter((n) => !registry[n]);
|
|
1008
|
+
if (unknown.length) {
|
|
1009
|
+
throw new Error(
|
|
1010
|
+
`telemetry: relink() was given names this registry does not declare: ${unknown.join(", ")}. A name with no spec has no rollup families to replay, so relinking it could only desynchronize its rows from its aggregates. Omit \`names\` to sweep everything.`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (!linkSubjects) {
|
|
1015
|
+
logger.warn(
|
|
1016
|
+
"[telemetry] relink() has no subjectLinker to ask \u2014 nothing was read and nothing was written. Configure createTelemetry({ subjectLinker }) first; relink() backfills what that hook would have done, it does not replace it."
|
|
1017
|
+
);
|
|
1018
|
+
result.skipped = 1;
|
|
1019
|
+
return result;
|
|
1020
|
+
}
|
|
1021
|
+
if (opts.limit != null && opts.limit <= 0) return result;
|
|
1022
|
+
const filter = {};
|
|
1023
|
+
if (opts.names) filter.name = { $in: opts.names };
|
|
1024
|
+
if (opts.since) filter.occurredAt = { $gte: opts.since };
|
|
1025
|
+
const query = TelemetryModel.find(filter).hint({ _id: 1 }).batchSize(batchSize);
|
|
1026
|
+
if (opts.limit != null) query.limit(opts.limit);
|
|
1027
|
+
const progress = () => {
|
|
1028
|
+
if (!opts.onProgress) return;
|
|
1029
|
+
try {
|
|
1030
|
+
opts.onProgress({ ...result });
|
|
1031
|
+
} catch (e) {
|
|
1032
|
+
logger.warn(`[telemetry] relink() onProgress threw \u2014 ignored, the backfill continues: ${e}`);
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
const cursor = query.cursor();
|
|
1036
|
+
let sinceProgress = 0;
|
|
1037
|
+
try {
|
|
1038
|
+
for await (const doc of cursor) {
|
|
1039
|
+
result.examined++;
|
|
1040
|
+
sinceProgress++;
|
|
1041
|
+
if (sinceProgress >= batchSize) {
|
|
1042
|
+
sinceProgress = 0;
|
|
1043
|
+
progress();
|
|
1044
|
+
}
|
|
1045
|
+
const spec = registry[doc.name];
|
|
1046
|
+
if (!spec) {
|
|
1047
|
+
result.skipped++;
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
const declared = (doc.subjects ?? []).map(
|
|
1051
|
+
(s) => s.role ? { type: s.type, id: s.id, role: s.role } : { type: s.type, id: s.id }
|
|
1052
|
+
);
|
|
1053
|
+
const m0 = counters.subjectLinkMisses;
|
|
1054
|
+
const e0 = counters.subjectLinkErrors + counters.subjectLinkTimeouts;
|
|
1055
|
+
const merged = await linkSubjects(doc.name, spec, doc.tenantId, declared);
|
|
1056
|
+
result.misses += counters.subjectLinkMisses - m0;
|
|
1057
|
+
if (counters.subjectLinkErrors + counters.subjectLinkTimeouts > e0) result.errors++;
|
|
1058
|
+
if (!merged) continue;
|
|
1059
|
+
const had = new Set(declared.map(refKey));
|
|
1060
|
+
const mergedKeys = [];
|
|
1061
|
+
const newKeys = [];
|
|
1062
|
+
for (const s of merged) {
|
|
1063
|
+
const key = refKey(s);
|
|
1064
|
+
if (mergedKeys.includes(key)) continue;
|
|
1065
|
+
mergedKeys.push(key);
|
|
1066
|
+
if (!had.has(key)) newKeys.push(key);
|
|
1067
|
+
}
|
|
1068
|
+
if (!newKeys.length) continue;
|
|
1069
|
+
result.linked++;
|
|
1070
|
+
result.subjects += newKeys.length;
|
|
1071
|
+
if (!dryRun) {
|
|
1072
|
+
await TelemetryModel.updateOne(
|
|
1073
|
+
{ _id: doc._id },
|
|
1074
|
+
{ $set: { subjects: merged, subjectKeys: mergedKeys } }
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
doc.subjectKeys = newKeys;
|
|
1078
|
+
for (const r of spec.rollups ?? []) {
|
|
1079
|
+
if (!r.by.includes("subject")) continue;
|
|
1080
|
+
result.rollups += await recordRollup(RollupModel, doc, doc.name, r, counters, { dryRun });
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
} finally {
|
|
1084
|
+
await cursor.close().catch(() => {
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
if (sinceProgress) progress();
|
|
1088
|
+
return result;
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
|
|
872
1092
|
// src/server/indexes.ts
|
|
873
1093
|
var INDEX_BUDGET = 24;
|
|
874
1094
|
function createSyncIndexes(ctx) {
|
|
@@ -1212,6 +1432,8 @@ function createIngest(opts) {
|
|
|
1212
1432
|
const occurredAt = Number.isFinite(occurredRaw) ? new Date(occurredRaw - clockSkewMs) : receivedAt;
|
|
1213
1433
|
const safeMap = (o) => o && typeof o === "object" ? new Map(Object.entries(o).map(([k, v]) => [k.replace(/\./g, "_"), v])) : /* @__PURE__ */ new Map();
|
|
1214
1434
|
noteUndeclaredAttrs(t.counters, name, spec, rec.attrs);
|
|
1435
|
+
const subjects = mergeSubjects(rec.subjects);
|
|
1436
|
+
const linked = t.linkSubjects ? await t.linkSubjects(name, spec, tenantId, subjects) : null;
|
|
1215
1437
|
const Model = t.models.byKind[spec.kind];
|
|
1216
1438
|
const d = new Model({
|
|
1217
1439
|
// facts the wire may not assert: tenant, service, env, origin, plane
|
|
@@ -1222,7 +1444,7 @@ function createIngest(opts) {
|
|
|
1222
1444
|
tenantId,
|
|
1223
1445
|
occurredAt,
|
|
1224
1446
|
severity: typeof rec.severity === "string" ? rec.severity : void 0,
|
|
1225
|
-
subjects:
|
|
1447
|
+
subjects: linked ?? subjects,
|
|
1226
1448
|
actor: ctx.actor ?? (typeof rec.actor === "string" ? rec.actor : batchActor),
|
|
1227
1449
|
onBehalfOf: typeof rec.onBehalfOf === "string" ? rec.onBehalfOf : void 0,
|
|
1228
1450
|
service: key.service,
|
|
@@ -3625,7 +3847,22 @@ function createTelemetry(config) {
|
|
|
3625
3847
|
inFlight.add(p);
|
|
3626
3848
|
void p.finally(() => inFlight.delete(p));
|
|
3627
3849
|
};
|
|
3628
|
-
const
|
|
3850
|
+
const linkSubjects = createSubjectLinking({
|
|
3851
|
+
linker: config.subjectLinker,
|
|
3852
|
+
timeoutMs: config.subjectLinkTimeoutMs,
|
|
3853
|
+
counters,
|
|
3854
|
+
logger
|
|
3855
|
+
});
|
|
3856
|
+
const emit = createEmitter({
|
|
3857
|
+
registry,
|
|
3858
|
+
byKind,
|
|
3859
|
+
RollupModel,
|
|
3860
|
+
rejects,
|
|
3861
|
+
counters,
|
|
3862
|
+
logger,
|
|
3863
|
+
track,
|
|
3864
|
+
linkSubjects
|
|
3865
|
+
});
|
|
3629
3866
|
const forget = createForget({
|
|
3630
3867
|
TelemetryModel,
|
|
3631
3868
|
RollupModel,
|
|
@@ -3643,6 +3880,14 @@ function createTelemetry(config) {
|
|
|
3643
3880
|
},
|
|
3644
3881
|
globalSubjectRefs: () => config.globalSubjectRefs === true
|
|
3645
3882
|
});
|
|
3883
|
+
const relink = createRelink({
|
|
3884
|
+
registry,
|
|
3885
|
+
TelemetryModel,
|
|
3886
|
+
RollupModel,
|
|
3887
|
+
counters,
|
|
3888
|
+
logger,
|
|
3889
|
+
linkSubjects
|
|
3890
|
+
});
|
|
3646
3891
|
const syncIndexes = createSyncIndexes({
|
|
3647
3892
|
registry,
|
|
3648
3893
|
TelemetryModel,
|
|
@@ -3654,6 +3899,21 @@ function createTelemetry(config) {
|
|
|
3654
3899
|
emit,
|
|
3655
3900
|
/** erasure: delete sole-party rows, redact shared ones, rekey rollups, drop aliases */
|
|
3656
3901
|
forget,
|
|
3902
|
+
/**
|
|
3903
|
+
* Backfill: re-ask the `subjectLinker` about records ALREADY on disk, and
|
|
3904
|
+
* replay the rollups the new subjects reach.
|
|
3905
|
+
*
|
|
3906
|
+
* Linking happens at write time, so configuring it fixes the future and
|
|
3907
|
+
* nothing else — a lifetime `by:['subject']` family is keyed on the subject
|
|
3908
|
+
* the record was written with, and a read-time join cannot reach back into
|
|
3909
|
+
* it. This is how a host catches up the backlog it adopted the hook with.
|
|
3910
|
+
*
|
|
3911
|
+
* DRY RUN BY DEFAULT: it rewrites historical aggregates, so the short call
|
|
3912
|
+
* reports and the writing call says `{ dryRun: false }`. Idempotent by
|
|
3913
|
+
* construction — a row that already carries the linked subject yields
|
|
3914
|
+
* nothing new, so a second run is a no-op.
|
|
3915
|
+
*/
|
|
3916
|
+
relink,
|
|
3657
3917
|
/**
|
|
3658
3918
|
* Tenant scope is not optional — force every read through here. The five
|
|
3659
3919
|
* dashboard query primitives (records/series/distribution/rollups/journey)
|
|
@@ -3688,6 +3948,17 @@ function createTelemetry(config) {
|
|
|
3688
3948
|
counters,
|
|
3689
3949
|
/** the registry, exposed for the router factories — hosts should import their own */
|
|
3690
3950
|
registry,
|
|
3951
|
+
/**
|
|
3952
|
+
* Write-time subject linking, exposed for the router factories. `null` when
|
|
3953
|
+
* no `subjectLinker` is configured.
|
|
3954
|
+
*
|
|
3955
|
+
* The wire path does not go through emit() — createIngest() builds its
|
|
3956
|
+
* record itself, because at-least-once delivery inverts the plane order
|
|
3957
|
+
* (insert first, THEN aggregate). So it reaches the linker the same way it
|
|
3958
|
+
* reaches the registry and the models: off the instance, running the one
|
|
3959
|
+
* implementation, rather than growing a second copy of the rules.
|
|
3960
|
+
*/
|
|
3961
|
+
linkSubjects,
|
|
3691
3962
|
logger,
|
|
3692
3963
|
/** mint an ingest key; the full key string is returned once, never again */
|
|
3693
3964
|
createKey: (input) => createKey(KeyModel, input),
|
|
@@ -3704,6 +3975,6 @@ function createTelemetry(config) {
|
|
|
3704
3975
|
};
|
|
3705
3976
|
}
|
|
3706
3977
|
|
|
3707
|
-
export { BODY_MAX_CHARS, COUNTER_MAP_MAX, COUNTER_OVERFLOW_KEY, DEFAULT_LIMITS, Env, INDEX_BUDGET, KeyKind, LogLevel, MAX_SUGGESTIONS, Origin, PLATFORM_SCOPE, RETENTION_DAYS, SAMPLE_RATE, SCHEMA_VERSION, TelemetryKind, TenantMode, boundedMeta, createDashboard, createIngest, createKey, createQueries, createTelemetry, createValues, defaultSpaDir, defineRegistry, deriveCatalog, deriveSuggestions, deriveViews, executeReport, findFamily, foldRollups, hashSecret, intervalForRange, isPlatformScope, median, newId, normalizeQuery, parseKeyString, parseReportQuery, plain, projectRegistry, rangeOf, reportToQuery, requireMilestoneFamily, resolveDim, resolveReport, summarizeStages, traceKeep, truncate, validateRegistry, verifySecret };
|
|
3978
|
+
export { BODY_MAX_CHARS, COUNTER_MAP_MAX, COUNTER_OVERFLOW_KEY, DEFAULT_LIMITS, Env, INDEX_BUDGET, KeyKind, LogLevel, MAX_SUGGESTIONS, Origin, PLATFORM_SCOPE, RELINK_BATCH_SIZE, RETENTION_DAYS, SAMPLE_RATE, SCHEMA_VERSION, SUBJECT_LINK_TIMEOUT_MS, SUBJECT_MAX, TelemetryKind, TenantMode, boundedMeta, createDashboard, createIngest, createKey, createQueries, createTelemetry, createValues, defaultSpaDir, defineRegistry, deriveCatalog, deriveSuggestions, deriveViews, executeReport, findFamily, foldRollups, hashSecret, intervalForRange, isPlatformScope, median, newId, normalizeQuery, parseKeyString, parseReportQuery, plain, projectRegistry, rangeOf, reportToQuery, requireMilestoneFamily, resolveDim, resolveReport, summarizeStages, traceKeep, truncate, validateRegistry, verifySecret };
|
|
3708
3979
|
//# sourceMappingURL=index.js.map
|
|
3709
3980
|
//# sourceMappingURL=index.js.map
|