@tangle-network/sdk-telemetry 0.1.8
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/LICENSE +11 -0
- package/README.md +24 -0
- package/dist/index.d.mts +968 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2170 -0
- package/dist/index.mjs.map +1 -0
- package/dist/stage-span.d.mts +139 -0
- package/dist/stage-span.d.mts.map +1 -0
- package/dist/stage-span.mjs +218 -0
- package/dist/stage-span.mjs.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
//#region src/stage-span.ts
|
|
2
|
+
/**
|
|
3
|
+
* One span shape for every lifecycle-op stage across every hop.
|
|
4
|
+
*
|
|
5
|
+
* The problem this solves: each service used to time its own stages with an
|
|
6
|
+
* isolated `Date.now()` map (worker `stageMs`, orchestrator `startupDiagnostics`,
|
|
7
|
+
* host `StartupBootstrapRecorder`, sidecar Prometheus) joined by disjoint ids,
|
|
8
|
+
* so no single query reconstructs one operation end-to-end. `StageSpan` is the
|
|
9
|
+
* shared record; `SpanRecorder` collects them under one correlation id (`ptid`)
|
|
10
|
+
* and renders the same `Server-Timing` slice everywhere. Existing recorders
|
|
11
|
+
* emit through this rather than hand-rolling a parallel timer.
|
|
12
|
+
*
|
|
13
|
+
* Runtime-portability: this module imports nothing Node-only (no `process`, no
|
|
14
|
+
* `@repo/shared`, no otel) so it is safe to bundle into the Cloudflare Worker
|
|
15
|
+
* via the `@tangle-network/sdk-telemetry/stage-span` subpath. The monotonic
|
|
16
|
+
* clock is `performance.now()`, which is a global in both Node 18+ and Workers.
|
|
17
|
+
*/
|
|
18
|
+
/** Request header that carries the one correlation id across every hop. */
|
|
19
|
+
const PROVISION_TRACE_ID_HEADER = "x-tangle-trace-id";
|
|
20
|
+
/** Correlation id prefix minted at the edge (`ptid_<uuid>`). */
|
|
21
|
+
const PTID_PREFIX = "ptid_";
|
|
22
|
+
/** Monotonic clock → nanoseconds. Portable across Node and Workers. */
|
|
23
|
+
function monotonicNs() {
|
|
24
|
+
return BigInt(Math.round(performance.now() * 1e6));
|
|
25
|
+
}
|
|
26
|
+
/** Duration of a span in fractional milliseconds. */
|
|
27
|
+
function spanDurationMs(span) {
|
|
28
|
+
return Number(span.endNs - span.startNs) / 1e6;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A supplied trace id is adopted only when it matches the token charset a
|
|
32
|
+
* `Server-Timing`/header value accepts unquoted, so a client-origin id can be
|
|
33
|
+
* threaded but a malformed/oversized one can never poison downstream headers
|
|
34
|
+
* or logs. Everything else is minted fresh.
|
|
35
|
+
*/
|
|
36
|
+
const PTID_ACCEPT = /^[\w-]{8,200}$/;
|
|
37
|
+
function isAdoptablePtid(value) {
|
|
38
|
+
return typeof value === "string" && PTID_ACCEPT.test(value);
|
|
39
|
+
}
|
|
40
|
+
/** Mint a fresh edge-origin correlation id. */
|
|
41
|
+
function mintPtid() {
|
|
42
|
+
return `${PTID_PREFIX}${crypto.randomUUID()}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Adopt an inbound `x-tangle-trace-id` when present and well-formed (so the
|
|
46
|
+
* SDK can supply a true client-origin root), otherwise mint one. This is the
|
|
47
|
+
* single decision every hop runs — the edge mints, every downstream hop adopts
|
|
48
|
+
* the forwarded value, so the id is born once and identical everywhere.
|
|
49
|
+
*/
|
|
50
|
+
function readOrMintPtid(headerValue) {
|
|
51
|
+
return isAdoptablePtid(headerValue) ? headerValue : mintPtid();
|
|
52
|
+
}
|
|
53
|
+
const TOKEN = /[^\w-]/g;
|
|
54
|
+
function sanitizeDesc(value) {
|
|
55
|
+
return value.replace(TOKEN, "");
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Collects {@link StageSpan}s for ONE op under ONE `ptid` and renders them to a
|
|
59
|
+
* `Server-Timing` slice / a durable span array / a residual reconciliation.
|
|
60
|
+
* There is exactly one recorder per op; existing per-service timers become thin
|
|
61
|
+
* adapters that call {@link record} or {@link stage} instead of keeping their
|
|
62
|
+
* own map.
|
|
63
|
+
*/
|
|
64
|
+
var SpanRecorder = class {
|
|
65
|
+
ptid;
|
|
66
|
+
op;
|
|
67
|
+
spans = [];
|
|
68
|
+
constructor(ptid, op) {
|
|
69
|
+
this.ptid = ptid;
|
|
70
|
+
this.op = op;
|
|
71
|
+
}
|
|
72
|
+
/** Push a fully-formed span (start/end already measured). Returns it. */
|
|
73
|
+
span(input) {
|
|
74
|
+
const span = {
|
|
75
|
+
ptid: this.ptid,
|
|
76
|
+
op: this.op,
|
|
77
|
+
...input
|
|
78
|
+
};
|
|
79
|
+
this.spans.push(span);
|
|
80
|
+
return span;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Time an async fn on the monotonic clock and record the span. A throw is
|
|
84
|
+
* recorded as `status:"error"` and re-thrown — the timing is never lost.
|
|
85
|
+
*/
|
|
86
|
+
async stage(stage, component, fn, opts) {
|
|
87
|
+
const startNs = monotonicNs();
|
|
88
|
+
let status = opts?.status ?? "completed";
|
|
89
|
+
try {
|
|
90
|
+
return await fn();
|
|
91
|
+
} catch (err) {
|
|
92
|
+
status = "error";
|
|
93
|
+
throw err;
|
|
94
|
+
} finally {
|
|
95
|
+
this.span({
|
|
96
|
+
stage,
|
|
97
|
+
component,
|
|
98
|
+
startNs,
|
|
99
|
+
endNs: monotonicNs(),
|
|
100
|
+
status,
|
|
101
|
+
parentStage: opts?.parentStage,
|
|
102
|
+
attrs: opts?.attrs
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Adapter for a stage already timed elsewhere (an existing `Date.now()` map,
|
|
108
|
+
* a fold-back duration from a downstream hop): synthesize a span of the given
|
|
109
|
+
* millisecond duration ending now. Only the duration is load-bearing.
|
|
110
|
+
*/
|
|
111
|
+
record(stage, component, durationMs, opts) {
|
|
112
|
+
const endNs = monotonicNs();
|
|
113
|
+
const startNs = endNs - BigInt(Math.round(Math.max(0, durationMs) * 1e6));
|
|
114
|
+
return this.span({
|
|
115
|
+
stage,
|
|
116
|
+
component,
|
|
117
|
+
startNs,
|
|
118
|
+
endNs,
|
|
119
|
+
status: opts?.status ?? "completed",
|
|
120
|
+
parentStage: opts?.parentStage,
|
|
121
|
+
attrs: opts?.attrs
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/** All spans in record order (durable store payload — the full tree). */
|
|
125
|
+
list() {
|
|
126
|
+
return this.spans;
|
|
127
|
+
}
|
|
128
|
+
/** Top-level spans (no `parentStage`) — the ones the residual accounts for. */
|
|
129
|
+
topLevel() {
|
|
130
|
+
return this.spans.filter((s) => s.parentStage === void 0);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Residual accounting: `sum(topLevelLeaves) + residual === total`, computed on
|
|
134
|
+
* the SAME rounded integers the header emits so the identity is exact. When
|
|
135
|
+
* `total` is omitted it is the wall span (max end − min start) across all
|
|
136
|
+
* spans. `residual` is the unaccounted gap — negative when stages overlap
|
|
137
|
+
* (parallelized work), the honest signal the CI dark-gap gate reads.
|
|
138
|
+
*/
|
|
139
|
+
reconcile(total) {
|
|
140
|
+
const leaves = this.topLevel().map((s) => ({
|
|
141
|
+
stage: s.stage,
|
|
142
|
+
dur: Math.round(spanDurationMs(s))
|
|
143
|
+
}));
|
|
144
|
+
const leavesSum = leaves.reduce((acc, l) => acc + l.dur, 0);
|
|
145
|
+
const totalRounded = Math.round(total ?? this.wallMs());
|
|
146
|
+
return {
|
|
147
|
+
total: totalRounded,
|
|
148
|
+
leavesSum,
|
|
149
|
+
residual: totalRounded - leavesSum,
|
|
150
|
+
leaves
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** Wall duration across all recorded spans, in ms. */
|
|
154
|
+
wallMs() {
|
|
155
|
+
if (this.spans.length === 0) return 0;
|
|
156
|
+
let min = this.spans[0].startNs;
|
|
157
|
+
let max = this.spans[0].endNs;
|
|
158
|
+
for (const s of this.spans) {
|
|
159
|
+
if (s.startNs < min) min = s.startNs;
|
|
160
|
+
if (s.endNs > max) max = s.endNs;
|
|
161
|
+
}
|
|
162
|
+
return Number(max - min) / 1e6;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Render the `Server-Timing` slice for this op: one `stage;dur=` per
|
|
166
|
+
* top-level span, then `<op>_unaccounted;dur=<residual>`, then
|
|
167
|
+
* `total;dur=<total>`. By construction the non-total entries sum to `total`,
|
|
168
|
+
* so a browser Network tab and the durable store agree. Skipped spans carry
|
|
169
|
+
* `desc="skipped"`; string attrs surface as `desc`. Nested children are
|
|
170
|
+
* omitted from the header (they live in {@link list}) to keep the sum exact.
|
|
171
|
+
*/
|
|
172
|
+
toServerTiming(opts) {
|
|
173
|
+
const { total, leaves, residual } = this.reconcile(opts?.total);
|
|
174
|
+
const byStage = new Map(this.topLevel().map((s) => [s.stage, s]));
|
|
175
|
+
const entries = [];
|
|
176
|
+
if (opts?.includePtid) entries.push(`ptid;desc="${sanitizeDesc(this.ptid)}"`);
|
|
177
|
+
for (const leaf of leaves) {
|
|
178
|
+
const span = byStage.get(leaf.stage);
|
|
179
|
+
const desc = span?.status === "skipped" ? "skipped" : typeof span?.attrs?.desc === "string" ? sanitizeDesc(span.attrs.desc) : void 0;
|
|
180
|
+
entries.push(desc ? `${leaf.stage};dur=${leaf.dur};desc="${desc}"` : `${leaf.stage};dur=${leaf.dur}`);
|
|
181
|
+
}
|
|
182
|
+
entries.push(`${this.op}_unaccounted;dur=${residual}`);
|
|
183
|
+
entries.push(`total;dur=${total}`);
|
|
184
|
+
return entries.join(", ");
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Parse a `Server-Timing` header value into its entries. Reused by the SDK
|
|
189
|
+
* (reading the create response) and the CI dark-gap gate. Tolerant of the
|
|
190
|
+
* `name;dur=X;desc="Y"` and bare-`name` forms Hono/the worker emit.
|
|
191
|
+
*/
|
|
192
|
+
function parseServerTiming(header) {
|
|
193
|
+
if (!header) return [];
|
|
194
|
+
const out = [];
|
|
195
|
+
for (const raw of header.split(",")) {
|
|
196
|
+
const parts = raw.trim().split(";");
|
|
197
|
+
const name = parts[0]?.trim();
|
|
198
|
+
if (!name) continue;
|
|
199
|
+
const entry = { name };
|
|
200
|
+
for (const param of parts.slice(1)) {
|
|
201
|
+
const eq = param.indexOf("=");
|
|
202
|
+
if (eq === -1) continue;
|
|
203
|
+
const key = param.slice(0, eq).trim();
|
|
204
|
+
let value = param.slice(eq + 1).trim();
|
|
205
|
+
if (value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
|
|
206
|
+
if (key === "dur") {
|
|
207
|
+
const num = Number(value);
|
|
208
|
+
if (Number.isFinite(num)) entry.dur = num;
|
|
209
|
+
} else if (key === "desc") entry.desc = value;
|
|
210
|
+
}
|
|
211
|
+
out.push(entry);
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
export { PROVISION_TRACE_ID_HEADER, PTID_PREFIX, SpanRecorder, isAdoptablePtid, mintPtid, monotonicNs, parseServerTiming, readOrMintPtid, spanDurationMs };
|
|
217
|
+
|
|
218
|
+
//# sourceMappingURL=stage-span.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stage-span.mjs","names":[],"sources":["../src/stage-span.ts"],"sourcesContent":["/**\n * One span shape for every lifecycle-op stage across every hop.\n *\n * The problem this solves: each service used to time its own stages with an\n * isolated `Date.now()` map (worker `stageMs`, orchestrator `startupDiagnostics`,\n * host `StartupBootstrapRecorder`, sidecar Prometheus) joined by disjoint ids,\n * so no single query reconstructs one operation end-to-end. `StageSpan` is the\n * shared record; `SpanRecorder` collects them under one correlation id (`ptid`)\n * and renders the same `Server-Timing` slice everywhere. Existing recorders\n * emit through this rather than hand-rolling a parallel timer.\n *\n * Runtime-portability: this module imports nothing Node-only (no `process`, no\n * `@repo/shared`, no otel) so it is safe to bundle into the Cloudflare Worker\n * via the `@tangle-network/sdk-telemetry/stage-span` subpath. The monotonic\n * clock is `performance.now()`, which is a global in both Node 18+ and Workers.\n */\n\n/** Request header that carries the one correlation id across every hop. */\nexport const PROVISION_TRACE_ID_HEADER = \"x-tangle-trace-id\";\n\n/** Correlation id prefix minted at the edge (`ptid_<uuid>`). */\nexport const PTID_PREFIX = \"ptid_\";\n\nexport type LifecycleOp =\n | \"create\"\n | \"branch\"\n | \"resume\"\n | \"suspend\"\n | \"snapshot\"\n | \"restore\"\n | \"exec\"\n | \"delete\"\n | \"turn\";\n\nexport type SpanComponent =\n | \"client\"\n | \"cf-worker\"\n | \"sandbox-api\"\n | \"orchestrator\"\n | \"host-agent\"\n | \"sidecar\"\n | \"storage\"\n | \"container\";\n\nexport type SpanStatus = \"completed\" | \"skipped\" | \"error\";\n\n/**\n * One timed stage of one lifecycle op, keyed by (ptid, op, stage, component).\n * `startNs`/`endNs` are monotonic-clock nanoseconds (see {@link monotonicNs});\n * only their difference is meaningful — they are NOT wall-clock timestamps.\n * `parentStage` builds a tree: a span with `parentStage` set is a child that\n * rolls up under its parent and does NOT count toward the top-level residual.\n */\nexport interface StageSpan {\n ptid: string;\n op: LifecycleOp;\n stage: string;\n component: SpanComponent;\n startNs: bigint;\n endNs: bigint;\n status: SpanStatus;\n parentStage?: string;\n attrs?: Record<string, string | number>;\n}\n\n/** Monotonic clock → nanoseconds. Portable across Node and Workers. */\nexport function monotonicNs(): bigint {\n return BigInt(Math.round(performance.now() * 1_000_000));\n}\n\n/** Duration of a span in fractional milliseconds. */\nexport function spanDurationMs(\n span: Pick<StageSpan, \"startNs\" | \"endNs\">,\n): number {\n return Number(span.endNs - span.startNs) / 1_000_000;\n}\n\n/**\n * A supplied trace id is adopted only when it matches the token charset a\n * `Server-Timing`/header value accepts unquoted, so a client-origin id can be\n * threaded but a malformed/oversized one can never poison downstream headers\n * or logs. Everything else is minted fresh.\n */\nconst PTID_ACCEPT = /^[\\w-]{8,200}$/;\n\nexport function isAdoptablePtid(\n value: string | null | undefined,\n): value is string {\n return typeof value === \"string\" && PTID_ACCEPT.test(value);\n}\n\n/** Mint a fresh edge-origin correlation id. */\nexport function mintPtid(): string {\n return `${PTID_PREFIX}${crypto.randomUUID()}`;\n}\n\n/**\n * Adopt an inbound `x-tangle-trace-id` when present and well-formed (so the\n * SDK can supply a true client-origin root), otherwise mint one. This is the\n * single decision every hop runs — the edge mints, every downstream hop adopts\n * the forwarded value, so the id is born once and identical everywhere.\n */\nexport function readOrMintPtid(headerValue: string | null | undefined): string {\n return isAdoptablePtid(headerValue) ? headerValue : mintPtid();\n}\n\n/** Input to {@link SpanRecorder.span} — ptid/op are fixed by the recorder. */\nexport type StageSpanInput = Omit<StageSpan, \"ptid\" | \"op\">;\n\n/** Options carried when recording a stage from a known duration. */\nexport interface RecordOptions {\n status?: SpanStatus;\n parentStage?: string;\n attrs?: Record<string, string | number>;\n}\n\n/** Parsed `name;dur=<ms>` entry (and any `desc`) from a Server-Timing header. */\nexport interface ServerTimingEntry {\n name: string;\n dur?: number;\n desc?: string;\n}\n\nconst TOKEN = /[^\\w-]/g;\n\nfunction sanitizeDesc(value: string): string {\n return value.replace(TOKEN, \"\");\n}\n\n/**\n * Collects {@link StageSpan}s for ONE op under ONE `ptid` and renders them to a\n * `Server-Timing` slice / a durable span array / a residual reconciliation.\n * There is exactly one recorder per op; existing per-service timers become thin\n * adapters that call {@link record} or {@link stage} instead of keeping their\n * own map.\n */\nexport class SpanRecorder {\n readonly ptid: string;\n readonly op: LifecycleOp;\n private readonly spans: StageSpan[] = [];\n\n constructor(ptid: string, op: LifecycleOp) {\n this.ptid = ptid;\n this.op = op;\n }\n\n /** Push a fully-formed span (start/end already measured). Returns it. */\n span(input: StageSpanInput): StageSpan {\n const span: StageSpan = { ptid: this.ptid, op: this.op, ...input };\n this.spans.push(span);\n return span;\n }\n\n /**\n * Time an async fn on the monotonic clock and record the span. A throw is\n * recorded as `status:\"error\"` and re-thrown — the timing is never lost.\n */\n async stage<T>(\n stage: string,\n component: SpanComponent,\n fn: () => Promise<T>,\n opts?: RecordOptions,\n ): Promise<T> {\n const startNs = monotonicNs();\n let status: SpanStatus = opts?.status ?? \"completed\";\n try {\n return await fn();\n } catch (err) {\n status = \"error\";\n throw err;\n } finally {\n this.span({\n stage,\n component,\n startNs,\n endNs: monotonicNs(),\n status,\n parentStage: opts?.parentStage,\n attrs: opts?.attrs,\n });\n }\n }\n\n /**\n * Adapter for a stage already timed elsewhere (an existing `Date.now()` map,\n * a fold-back duration from a downstream hop): synthesize a span of the given\n * millisecond duration ending now. Only the duration is load-bearing.\n */\n record(\n stage: string,\n component: SpanComponent,\n durationMs: number,\n opts?: RecordOptions,\n ): StageSpan {\n const endNs = monotonicNs();\n const startNs =\n endNs - BigInt(Math.round(Math.max(0, durationMs) * 1_000_000));\n return this.span({\n stage,\n component,\n startNs,\n endNs,\n status: opts?.status ?? \"completed\",\n parentStage: opts?.parentStage,\n attrs: opts?.attrs,\n });\n }\n\n /** All spans in record order (durable store payload — the full tree). */\n list(): readonly StageSpan[] {\n return this.spans;\n }\n\n /** Top-level spans (no `parentStage`) — the ones the residual accounts for. */\n topLevel(): StageSpan[] {\n return this.spans.filter((s) => s.parentStage === undefined);\n }\n\n /**\n * Residual accounting: `sum(topLevelLeaves) + residual === total`, computed on\n * the SAME rounded integers the header emits so the identity is exact. When\n * `total` is omitted it is the wall span (max end − min start) across all\n * spans. `residual` is the unaccounted gap — negative when stages overlap\n * (parallelized work), the honest signal the CI dark-gap gate reads.\n */\n reconcile(total?: number): {\n total: number;\n leavesSum: number;\n residual: number;\n leaves: Array<{ stage: string; dur: number }>;\n } {\n const leaves = this.topLevel().map((s) => ({\n stage: s.stage,\n dur: Math.round(spanDurationMs(s)),\n }));\n const leavesSum = leaves.reduce((acc, l) => acc + l.dur, 0);\n const totalRounded = Math.round(total ?? this.wallMs());\n return {\n total: totalRounded,\n leavesSum,\n residual: totalRounded - leavesSum,\n leaves,\n };\n }\n\n /** Wall duration across all recorded spans, in ms. */\n wallMs(): number {\n if (this.spans.length === 0) return 0;\n let min = this.spans[0].startNs;\n let max = this.spans[0].endNs;\n for (const s of this.spans) {\n if (s.startNs < min) min = s.startNs;\n if (s.endNs > max) max = s.endNs;\n }\n return Number(max - min) / 1_000_000;\n }\n\n /**\n * Render the `Server-Timing` slice for this op: one `stage;dur=` per\n * top-level span, then `<op>_unaccounted;dur=<residual>`, then\n * `total;dur=<total>`. By construction the non-total entries sum to `total`,\n * so a browser Network tab and the durable store agree. Skipped spans carry\n * `desc=\"skipped\"`; string attrs surface as `desc`. Nested children are\n * omitted from the header (they live in {@link list}) to keep the sum exact.\n */\n toServerTiming(opts?: { total?: number; includePtid?: boolean }): string {\n const { total, leaves, residual } = this.reconcile(opts?.total);\n const byStage = new Map(this.topLevel().map((s) => [s.stage, s]));\n const entries: string[] = [];\n if (opts?.includePtid) {\n entries.push(`ptid;desc=\"${sanitizeDesc(this.ptid)}\"`);\n }\n for (const leaf of leaves) {\n const span = byStage.get(leaf.stage);\n const desc =\n span?.status === \"skipped\"\n ? \"skipped\"\n : typeof span?.attrs?.desc === \"string\"\n ? sanitizeDesc(span.attrs.desc)\n : undefined;\n entries.push(\n desc\n ? `${leaf.stage};dur=${leaf.dur};desc=\"${desc}\"`\n : `${leaf.stage};dur=${leaf.dur}`,\n );\n }\n entries.push(`${this.op}_unaccounted;dur=${residual}`);\n entries.push(`total;dur=${total}`);\n return entries.join(\", \");\n }\n}\n\n/**\n * Parse a `Server-Timing` header value into its entries. Reused by the SDK\n * (reading the create response) and the CI dark-gap gate. Tolerant of the\n * `name;dur=X;desc=\"Y\"` and bare-`name` forms Hono/the worker emit.\n */\nexport function parseServerTiming(\n header: string | null | undefined,\n): ServerTimingEntry[] {\n if (!header) return [];\n const out: ServerTimingEntry[] = [];\n for (const raw of header.split(\",\")) {\n const parts = raw.trim().split(\";\");\n const name = parts[0]?.trim();\n if (!name) continue;\n const entry: ServerTimingEntry = { name };\n for (const param of parts.slice(1)) {\n const eq = param.indexOf(\"=\");\n if (eq === -1) continue;\n const key = param.slice(0, eq).trim();\n let value = param.slice(eq + 1).trim();\n if (value.startsWith('\"') && value.endsWith('\"')) {\n value = value.slice(1, -1);\n }\n if (key === \"dur\") {\n const num = Number(value);\n if (Number.isFinite(num)) entry.dur = num;\n } else if (key === \"desc\") {\n entry.desc = value;\n }\n }\n out.push(entry);\n }\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAa,4BAA4B;;AAGzC,MAAa,cAAc;;AA6C3B,SAAgB,cAAsB;AACpC,QAAO,OAAO,KAAK,MAAM,YAAY,KAAK,GAAG,IAAU,CAAC;;;AAI1D,SAAgB,eACd,MACQ;AACR,QAAO,OAAO,KAAK,QAAQ,KAAK,QAAQ,GAAG;;;;;;;;AAS7C,MAAM,cAAc;AAEpB,SAAgB,gBACd,OACiB;AACjB,QAAO,OAAO,UAAU,YAAY,YAAY,KAAK,MAAM;;;AAI7D,SAAgB,WAAmB;AACjC,QAAO,GAAG,cAAc,OAAO,YAAY;;;;;;;;AAS7C,SAAgB,eAAe,aAAgD;AAC7E,QAAO,gBAAgB,YAAY,GAAG,cAAc,UAAU;;AAoBhE,MAAM,QAAQ;AAEd,SAAS,aAAa,OAAuB;AAC3C,QAAO,MAAM,QAAQ,OAAO,GAAG;;;;;;;;;AAUjC,IAAa,eAAb,MAA0B;CACxB;CACA;CACA,QAAsC,EAAE;CAExC,YAAY,MAAc,IAAiB;AACzC,OAAK,OAAO;AACZ,OAAK,KAAK;;;CAIZ,KAAK,OAAkC;EACrC,MAAM,OAAkB;GAAE,MAAM,KAAK;GAAM,IAAI,KAAK;GAAI,GAAG;GAAO;AAClE,OAAK,MAAM,KAAK,KAAK;AACrB,SAAO;;;;;;CAOT,MAAM,MACJ,OACA,WACA,IACA,MACY;EACZ,MAAM,UAAU,aAAa;EAC7B,IAAI,SAAqB,MAAM,UAAU;AACzC,MAAI;AACF,UAAO,MAAM,IAAI;WACV,KAAK;AACZ,YAAS;AACT,SAAM;YACE;AACR,QAAK,KAAK;IACR;IACA;IACA;IACA,OAAO,aAAa;IACpB;IACA,aAAa,MAAM;IACnB,OAAO,MAAM;IACd,CAAC;;;;;;;;CASN,OACE,OACA,WACA,YACA,MACW;EACX,MAAM,QAAQ,aAAa;EAC3B,MAAM,UACJ,QAAQ,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,WAAW,GAAG,IAAU,CAAC;AACjE,SAAO,KAAK,KAAK;GACf;GACA;GACA;GACA;GACA,QAAQ,MAAM,UAAU;GACxB,aAAa,MAAM;GACnB,OAAO,MAAM;GACd,CAAC;;;CAIJ,OAA6B;AAC3B,SAAO,KAAK;;;CAId,WAAwB;AACtB,SAAO,KAAK,MAAM,QAAQ,MAAM,EAAE,gBAAgB,KAAA,EAAU;;;;;;;;;CAU9D,UAAU,OAKR;EACA,MAAM,SAAS,KAAK,UAAU,CAAC,KAAK,OAAO;GACzC,OAAO,EAAE;GACT,KAAK,KAAK,MAAM,eAAe,EAAE,CAAC;GACnC,EAAE;EACH,MAAM,YAAY,OAAO,QAAQ,KAAK,MAAM,MAAM,EAAE,KAAK,EAAE;EAC3D,MAAM,eAAe,KAAK,MAAM,SAAS,KAAK,QAAQ,CAAC;AACvD,SAAO;GACL,OAAO;GACP;GACA,UAAU,eAAe;GACzB;GACD;;;CAIH,SAAiB;AACf,MAAI,KAAK,MAAM,WAAW,EAAG,QAAO;EACpC,IAAI,MAAM,KAAK,MAAM,GAAG;EACxB,IAAI,MAAM,KAAK,MAAM,GAAG;AACxB,OAAK,MAAM,KAAK,KAAK,OAAO;AAC1B,OAAI,EAAE,UAAU,IAAK,OAAM,EAAE;AAC7B,OAAI,EAAE,QAAQ,IAAK,OAAM,EAAE;;AAE7B,SAAO,OAAO,MAAM,IAAI,GAAG;;;;;;;;;;CAW7B,eAAe,MAA0D;EACvE,MAAM,EAAE,OAAO,QAAQ,aAAa,KAAK,UAAU,MAAM,MAAM;EAC/D,MAAM,UAAU,IAAI,IAAI,KAAK,UAAU,CAAC,KAAK,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;EACjE,MAAM,UAAoB,EAAE;AAC5B,MAAI,MAAM,YACR,SAAQ,KAAK,cAAc,aAAa,KAAK,KAAK,CAAC,GAAG;AAExD,OAAK,MAAM,QAAQ,QAAQ;GACzB,MAAM,OAAO,QAAQ,IAAI,KAAK,MAAM;GACpC,MAAM,OACJ,MAAM,WAAW,YACb,YACA,OAAO,MAAM,OAAO,SAAS,WAC3B,aAAa,KAAK,MAAM,KAAK,GAC7B,KAAA;AACR,WAAQ,KACN,OACI,GAAG,KAAK,MAAM,OAAO,KAAK,IAAI,SAAS,KAAK,KAC5C,GAAG,KAAK,MAAM,OAAO,KAAK,MAC/B;;AAEH,UAAQ,KAAK,GAAG,KAAK,GAAG,mBAAmB,WAAW;AACtD,UAAQ,KAAK,aAAa,QAAQ;AAClC,SAAO,QAAQ,KAAK,KAAK;;;;;;;;AAS7B,SAAgB,kBACd,QACqB;AACrB,KAAI,CAAC,OAAQ,QAAO,EAAE;CACtB,MAAM,MAA2B,EAAE;AACnC,MAAK,MAAM,OAAO,OAAO,MAAM,IAAI,EAAE;EACnC,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,IAAI;EACnC,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,MAAI,CAAC,KAAM;EACX,MAAM,QAA2B,EAAE,MAAM;AACzC,OAAK,MAAM,SAAS,MAAM,MAAM,EAAE,EAAE;GAClC,MAAM,KAAK,MAAM,QAAQ,IAAI;AAC7B,OAAI,OAAO,GAAI;GACf,MAAM,MAAM,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM;GACrC,IAAI,QAAQ,MAAM,MAAM,KAAK,EAAE,CAAC,MAAM;AACtC,OAAI,MAAM,WAAW,KAAI,IAAI,MAAM,SAAS,KAAI,CAC9C,SAAQ,MAAM,MAAM,GAAG,GAAG;AAE5B,OAAI,QAAQ,OAAO;IACjB,MAAM,MAAM,OAAO,MAAM;AACzB,QAAI,OAAO,SAAS,IAAI,CAAE,OAAM,MAAM;cAC7B,QAAQ,OACjB,OAAM,OAAO;;AAGjB,MAAI,KAAK,MAAM;;AAEjB,QAAO"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tangle-network/sdk-telemetry",
|
|
3
|
+
"version": "0.1.8",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.mjs",
|
|
6
|
+
"types": "./dist/index.d.mts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.mts",
|
|
10
|
+
"import": "./dist/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"./stage-span": {
|
|
13
|
+
"types": "./dist/stage-span.d.mts",
|
|
14
|
+
"import": "./dist/stage-span.mjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@tangle-network/agent-core": "0.4.19"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@opentelemetry/api": "^1.9.1",
|
|
25
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.216.0",
|
|
26
|
+
"@opentelemetry/resources": "^2.7.1",
|
|
27
|
+
"@opentelemetry/sdk-trace-node": "^2.7.1",
|
|
28
|
+
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
29
|
+
"@types/node": "25.6.0",
|
|
30
|
+
"langfuse-node": "^3.38.20",
|
|
31
|
+
"tsdown": "0.21.10",
|
|
32
|
+
"typescript": "^6.0.3",
|
|
33
|
+
"vitest": "^4.1.5"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@opentelemetry/api": ">=1.0.0",
|
|
37
|
+
"@opentelemetry/exporter-trace-otlp-http": ">=0.50.0",
|
|
38
|
+
"@opentelemetry/resources": ">=1.0.0",
|
|
39
|
+
"@opentelemetry/sdk-trace-node": ">=1.0.0",
|
|
40
|
+
"@opentelemetry/semantic-conventions": ">=1.0.0",
|
|
41
|
+
"langfuse-node": ">=3.0.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"langfuse-node": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"@opentelemetry/api": {
|
|
48
|
+
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"@opentelemetry/exporter-trace-otlp-http": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"@opentelemetry/resources": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"@opentelemetry/sdk-trace-node": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"@opentelemetry/semantic-conventions": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "tsdown",
|
|
65
|
+
"check-types": "tsc --noEmit",
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:watch": "vitest"
|
|
68
|
+
}
|
|
69
|
+
}
|