nestjs-otel 8.0.4-alpha.7 → 8.1.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/README.md CHANGED
@@ -310,7 +310,9 @@ export class BookService {
310
310
 
311
311
  Wide events (also known as canonical log lines) emit one context-rich event per request, with attributes accumulated across the whole request lifecycle. See [A Practitioner's Guide to Wide Events](https://jeremymorrell.dev/blog/a-practitioners-guide-to-wide-events/) for the pattern.
312
312
 
313
- This library implements the pattern on top of OpenTelemetry: the `WideEventInterceptor` opens an attribute bag per request and, when the request finishes, flushes everything onto the span that was active when the request entered the interceptor (usually the root HTTP span created by your instrumentation). The `WideEventService` lets any provider enrich that bag from anywhere in the request's async call chain — no request-scoped injection needed.
313
+ This library implements the pattern on top of OpenTelemetry: the `WideEventInterceptor` opens an attribute bag per request and, when the request finishes, flushes everything onto a single span — marking it with `nestjs_otel.wide_event = true` so you can filter wide-event spans in your backend. The `WideEventService` lets any provider enrich that bag from anywhere in the request's async call chain — no request-scoped injection needed.
314
+
315
+ By default the attributes land on the best-available recording span at flush time (the HTTP root on Express / plain Fastify, or the active handler span when an instrumentation such as `@opentelemetry/instrumentation-nestjs-core` or `@fastify/otel` nests a span per request phase). To guarantee they land on the **trace root span** regardless of instrumentation nesting, register the [`WideEventSpanProcessor`](#targeting-the-root-span-wideeventspanprocessor).
314
316
 
315
317
  1. Register the interceptor globally:
316
318
 
@@ -368,10 +370,11 @@ export class CheckoutService {
368
370
  }
369
371
  ```
370
372
 
371
- All accumulated attributes land on the root span as a single wide event:
373
+ All accumulated attributes land on a single span as one wide event:
372
374
 
373
375
  ```
374
- http_request
376
+ GET /checkout
377
+ ├── nestjs_otel.wide_event: true
375
378
  ├── code.function.name: CheckoutController.checkout
376
379
  ├── user.id: u-123
377
380
  ├── cart.items: 3
@@ -384,7 +387,8 @@ http_request
384
387
 
385
388
  Notes:
386
389
 
387
- - The interceptor seeds `code.function.name` (`<Controller>.<handler>`) automatically and records `error.type`/`error.message` when the handler throws.
390
+ - The flushed span is marked `nestjs_otel.wide_event = true`. This marker is set last, so a handler-provided field can never overwrite it.
391
+ - The interceptor seeds `code.function.name` (`<Controller>.<handler>`) automatically and records `error.type`/`error.message` (plus `error.stack` when present) when the handler throws.
388
392
  - `WideEventService` methods are safe no-ops outside a request handled by the interceptor.
389
393
  - An async-context-aware context manager is required (the default with `NodeSDK` / `AsyncLocalStorageContextManager`), same as for tracing in general.
390
394
 
@@ -425,6 +429,28 @@ export class BooksService {
425
429
  }
426
430
  ```
427
431
 
432
+ ### Targeting the root span (`WideEventSpanProcessor`)
433
+
434
+ Instrumentations like `@opentelemetry/instrumentation-nestjs-core` and `@fastify/otel` wrap each request phase (middleware, guard, handler) in its own span. As a result, the span active when the interceptor runs is a nested child, not the trace root — so by default the wide event lands on that child span (e.g. `AppController.handler`) rather than on the root `GET /route`.
435
+
436
+ Register the `WideEventSpanProcessor` on your `NodeSDK` to make the interceptor flush onto the **local-root span** (the span with no in-process parent — normally the HTTP server span) instead:
437
+
438
+ ```ts
439
+ import { NodeSDK } from '@opentelemetry/sdk-node';
440
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
441
+ import { WideEventSpanProcessor } from 'nestjs-otel';
442
+
443
+ export const otelSDK = new NodeSDK({
444
+ spanProcessors: [
445
+ new WideEventSpanProcessor(),
446
+ new BatchSpanProcessor(traceExporter),
447
+ ],
448
+ // ...instrumentations, contextManager, etc.
449
+ });
450
+ ```
451
+
452
+ The processor only tracks which span is the local root per trace; it never exports spans, so keep your existing exporting processor in the list. It is optional — without it, wide events still flush onto the best-available recording span (see above), just not necessarily the trace root.
453
+
428
454
  ## Metric Service
429
455
 
430
456
  [OpenTelemetry Metrics](https://www.npmjs.com/package/@opentelemetry/api) allow a user to collect data and export it to metrics backend like Prometheus.
package/lib/index.d.ts CHANGED
@@ -13,4 +13,5 @@ export * from "./wide-events/wide-event.context";
13
13
  export * from "./wide-events/wide-event.interceptor";
14
14
  export * from "./wide-events/wide-event.middleware";
15
15
  export * from "./wide-events/wide-event.service";
16
+ export * from "./wide-events/wide-event.span-processor";
16
17
  export * from "./wide-events/wide-event-field.decorator";
package/lib/index.js CHANGED
@@ -29,4 +29,5 @@ __exportStar(require("./wide-events/wide-event.context"), exports);
29
29
  __exportStar(require("./wide-events/wide-event.interceptor"), exports);
30
30
  __exportStar(require("./wide-events/wide-event.middleware"), exports);
31
31
  __exportStar(require("./wide-events/wide-event.service"), exports);
32
+ __exportStar(require("./wide-events/wide-event.span-processor"), exports);
32
33
  __exportStar(require("./wide-events/wide-event-field.decorator"), exports);
@@ -16,6 +16,13 @@ export declare class WideEventInterceptor implements NestInterceptor {
16
16
  constructor(options?: OpenTelemetryModuleOptions | undefined);
17
17
  intercept(executionContext: ExecutionContext, next: CallHandler): Observable<unknown>;
18
18
  private rootSpanFromRequest;
19
+ /**
20
+ * Picks the span the wide event should be written to, preferring the
21
+ * local-root span (when the WideEventSpanProcessor is registered), then the
22
+ * middleware-captured root span, then the interceptor-time active span —
23
+ * skipping any that have already ended.
24
+ */
25
+ private targetSpan;
19
26
  private pickSpan;
20
27
  private seed;
21
28
  private flush;
@@ -19,6 +19,7 @@ const rxjs_1 = require("rxjs");
19
19
  const operators_1 = require("rxjs/operators");
20
20
  const opentelemetry_constants_1 = require("../opentelemetry.constants");
21
21
  const wide_event_context_1 = require("./wide-event.context");
22
+ const wide_event_span_processor_1 = require("./wide-event.span-processor");
22
23
  /**
23
24
  * Opens a wide event for each request and flushes the accumulated
24
25
  * attributes onto the span that was active when the request entered the
@@ -64,7 +65,7 @@ let WideEventInterceptor = class WideEventInterceptor {
64
65
  bag.set("error.stack", error.stack);
65
66
  }
66
67
  }
67
- this.pickSpan(rootSpan, activeSpan)?.setStatus({
68
+ this.targetSpan(rootSpan, activeSpan)?.setStatus({
68
69
  code: api_1.SpanStatusCode.ERROR,
69
70
  });
70
71
  },
@@ -91,12 +92,22 @@ let WideEventInterceptor = class WideEventInterceptor {
91
92
  return (request?.[wide_event_context_1.WIDE_EVENT_ROOT_SPAN] ??
92
93
  request?.raw?.[wide_event_context_1.WIDE_EVENT_ROOT_SPAN]);
93
94
  }
94
- pickSpan(rootSpan, activeSpan) {
95
- if (rootSpan?.isRecording()) {
96
- return rootSpan;
97
- }
98
- if (activeSpan?.isRecording()) {
99
- return activeSpan;
95
+ /**
96
+ * Picks the span the wide event should be written to, preferring the
97
+ * local-root span (when the WideEventSpanProcessor is registered), then the
98
+ * middleware-captured root span, then the interceptor-time active span —
99
+ * skipping any that have already ended.
100
+ */
101
+ targetSpan(rootSpan, activeSpan) {
102
+ const traceId = (activeSpan ?? rootSpan)?.spanContext().traceId;
103
+ const localRoot = traceId ? (0, wide_event_span_processor_1.getLocalRootSpan)(traceId) : undefined;
104
+ return this.pickSpan(localRoot, rootSpan, activeSpan);
105
+ }
106
+ pickSpan(...candidates) {
107
+ for (const candidate of candidates) {
108
+ if (candidate?.isRecording()) {
109
+ return candidate;
110
+ }
100
111
  }
101
112
  return;
102
113
  }
@@ -121,7 +132,7 @@ let WideEventInterceptor = class WideEventInterceptor {
121
132
  }
122
133
  }
123
134
  flush(bag, rootSpan, activeSpan) {
124
- const span = this.pickSpan(rootSpan, activeSpan);
135
+ const span = this.targetSpan(rootSpan, activeSpan);
125
136
  if (!span) {
126
137
  return;
127
138
  }
@@ -6,6 +6,7 @@ const rxjs_1 = require("rxjs");
6
6
  const wide_event_context_1 = require("./wide-event.context");
7
7
  const wide_event_interceptor_1 = require("./wide-event.interceptor");
8
8
  const wide_event_service_1 = require("./wide-event.service");
9
+ const wide_event_span_processor_1 = require("./wide-event.span-processor");
9
10
  class CatsController {
10
11
  findAll() { }
11
12
  }
@@ -238,6 +239,26 @@ describe("WideEventInterceptor", () => {
238
239
  expect(active?.attributes["code.function.name"]).toBe("CatsController.findAll");
239
240
  expect(ended?.attributes["nestjs_otel.wide_event"]).toBeUndefined();
240
241
  });
242
+ it("should prefer the processor's local-root span over the active span", async () => {
243
+ // #given: a registered local-root span plus a nested active span in the
244
+ // same trace (as instrumentation-nestjs-core would create).
245
+ const processor = new wide_event_span_processor_1.WideEventSpanProcessor();
246
+ const tracer = api_1.trace.getTracer("test");
247
+ const rootSpan = tracer.startSpan("GET /actors");
248
+ processor.onStart(rootSpan, {});
249
+ const childContext = api_1.trace.setSpan(api_1.context.active(), rootSpan);
250
+ const nestedSpan = tracer.startSpan("ActorController.findAll", undefined, childContext);
251
+ await api_1.context.with(api_1.trace.setSpan(api_1.context.active(), nestedSpan), async () => (0, rxjs_1.lastValueFrom)(interceptor.intercept(executionContext, callHandler(() => (0, rxjs_1.of)("ok")))));
252
+ nestedSpan.end();
253
+ rootSpan.end();
254
+ // #then: marker lands on the local-root span, not the nested span
255
+ const finished = traceExporter.getFinishedSpans();
256
+ const root = finished.find((s) => s.name === "GET /actors");
257
+ const nested = finished.find((s) => s.name === "ActorController.findAll");
258
+ expect(root?.attributes["nestjs_otel.wide_event"]).toBe(true);
259
+ expect(root?.attributes["code.function.name"]).toBe("CatsController.findAll");
260
+ expect(nested?.attributes["nestjs_otel.wide_event"]).toBeUndefined();
261
+ });
241
262
  it("should propagate the handler result untouched when no span is active", async () => {
242
263
  const result = await (0, rxjs_1.lastValueFrom)(interceptor.intercept(executionContext, callHandler(() => (0, rxjs_1.of)("ok"))));
243
264
  expect(result).toBe("ok");
@@ -0,0 +1,36 @@
1
+ import type { Context } from "@opentelemetry/api";
2
+ import type { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base";
3
+ /**
4
+ * The local-root span for the given trace (the span with no in-process
5
+ * parent — usually the HTTP server span created by instrumentation), or
6
+ * undefined when the WideEventSpanProcessor is not registered or the span has
7
+ * already ended.
8
+ *
9
+ * @internal
10
+ */
11
+ export declare function getLocalRootSpan(traceId: string): Span | undefined;
12
+ /**
13
+ * Tracks the local-root span of every trace so the WideEventInterceptor can
14
+ * flush accumulated attributes onto it instead of a nested instrumentation
15
+ * span (e.g. the per-request span created by instrumentation-nestjs-core or
16
+ * the per-phase spans created by @fastify/otel).
17
+ *
18
+ * Register it on your NodeSDK alongside the exporting processor:
19
+ *
20
+ * ```ts
21
+ * new NodeSDK({
22
+ * spanProcessors: [
23
+ * new WideEventSpanProcessor(),
24
+ * new BatchSpanProcessor(traceExporter),
25
+ * ],
26
+ * });
27
+ * ```
28
+ *
29
+ * @publicApi
30
+ */
31
+ export declare class WideEventSpanProcessor implements SpanProcessor {
32
+ onStart(span: Span, _parentContext: Context): void;
33
+ onEnd(span: ReadableSpan): void;
34
+ forceFlush(): Promise<void>;
35
+ shutdown(): Promise<void>;
36
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WideEventSpanProcessor = void 0;
4
+ exports.getLocalRootSpan = getLocalRootSpan;
5
+ const localRootSpans = new Map();
6
+ const isLocalRoot = (span) => {
7
+ const parent = span.parentSpanContext;
8
+ return !parent || parent.isRemote === true;
9
+ };
10
+ /**
11
+ * The local-root span for the given trace (the span with no in-process
12
+ * parent — usually the HTTP server span created by instrumentation), or
13
+ * undefined when the WideEventSpanProcessor is not registered or the span has
14
+ * already ended.
15
+ *
16
+ * @internal
17
+ */
18
+ function getLocalRootSpan(traceId) {
19
+ return localRootSpans.get(traceId);
20
+ }
21
+ /**
22
+ * Tracks the local-root span of every trace so the WideEventInterceptor can
23
+ * flush accumulated attributes onto it instead of a nested instrumentation
24
+ * span (e.g. the per-request span created by instrumentation-nestjs-core or
25
+ * the per-phase spans created by @fastify/otel).
26
+ *
27
+ * Register it on your NodeSDK alongside the exporting processor:
28
+ *
29
+ * ```ts
30
+ * new NodeSDK({
31
+ * spanProcessors: [
32
+ * new WideEventSpanProcessor(),
33
+ * new BatchSpanProcessor(traceExporter),
34
+ * ],
35
+ * });
36
+ * ```
37
+ *
38
+ * @publicApi
39
+ */
40
+ class WideEventSpanProcessor {
41
+ onStart(span, _parentContext) {
42
+ if (isLocalRoot(span)) {
43
+ localRootSpans.set(span.spanContext().traceId, span);
44
+ }
45
+ }
46
+ onEnd(span) {
47
+ if (isLocalRoot(span)) {
48
+ localRootSpans.delete(span.spanContext().traceId);
49
+ }
50
+ }
51
+ forceFlush() {
52
+ return Promise.resolve();
53
+ }
54
+ shutdown() {
55
+ localRootSpans.clear();
56
+ return Promise.resolve();
57
+ }
58
+ }
59
+ exports.WideEventSpanProcessor = WideEventSpanProcessor;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const api_1 = require("@opentelemetry/api");
4
+ const sdk_trace_node_1 = require("@opentelemetry/sdk-trace-node");
5
+ const wide_event_span_processor_1 = require("./wide-event.span-processor");
6
+ describe("WideEventSpanProcessor", () => {
7
+ let processor;
8
+ let provider;
9
+ beforeAll(() => {
10
+ provider = new sdk_trace_node_1.NodeTracerProvider();
11
+ provider.register();
12
+ });
13
+ beforeEach(() => {
14
+ processor = new wide_event_span_processor_1.WideEventSpanProcessor();
15
+ });
16
+ afterAll(async () => {
17
+ await provider.shutdown();
18
+ });
19
+ const startSpan = (name) => api_1.trace.getTracer("test").startSpan(name);
20
+ it("should register a parentless span as the local root for its trace", () => {
21
+ const root = startSpan("GET /test");
22
+ processor.onStart(root, api_1.context.active());
23
+ expect((0, wide_event_span_processor_1.getLocalRootSpan)(root.spanContext().traceId)).toBe(root);
24
+ root.end();
25
+ });
26
+ it("should not register a span whose parent is local", () => {
27
+ const root = startSpan("GET /test");
28
+ processor.onStart(root, api_1.context.active());
29
+ const child = api_1.trace
30
+ .getTracer("test")
31
+ .startSpan("child", undefined, api_1.trace.setSpan(api_1.context.active(), root));
32
+ processor.onStart(child, api_1.context.active());
33
+ // local root for the trace is still the root, not the child
34
+ expect((0, wide_event_span_processor_1.getLocalRootSpan)(root.spanContext().traceId)).toBe(root);
35
+ child.end();
36
+ root.end();
37
+ });
38
+ it("should drop the local root from the registry when it ends", () => {
39
+ const root = startSpan("GET /test");
40
+ const traceId = root.spanContext().traceId;
41
+ processor.onStart(root, api_1.context.active());
42
+ processor.onEnd(root);
43
+ expect((0, wide_event_span_processor_1.getLocalRootSpan)(traceId)).toBeUndefined();
44
+ root.end();
45
+ });
46
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-otel",
3
- "version": "8.0.4-alpha.7",
3
+ "version": "8.1.0",
4
4
  "description": "NestJS OpenTelemetry Library",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index",
@@ -1,24 +0,0 @@
1
- {
2
- "hooks": {
3
- "PreToolUse": [
4
- {
5
- "matcher": "Bash",
6
- "hooks": [
7
- {
8
- "type": "command",
9
- "command": "CMD=$(python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',d).get('command',''))\" 2>/dev/null || true); case \"$CMD\" in *grep*|*rg\\ *|*ripgrep*|*find\\ *|*fd\\ *|*ack\\ *|*ag\\ *) [ -f graphify-out/graph.json ] && echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"additionalContext\":\"MANDATORY: graphify-out/graph.json exists. You MUST run `graphify query \\\"<question>\\\"` before grepping raw files. Only grep after graphify has oriented you, or to modify/debug specific lines.\"}}' || true ;; esac"
10
- }
11
- ]
12
- },
13
- {
14
- "matcher": "Read|Glob",
15
- "hooks": [
16
- {
17
- "type": "command",
18
- "command": "HIT=$(python3 -c \"import json,sys;d=json.load(sys.stdin);t=d.get('tool_input',d);s=(str(t.get('file_path') or '')+' '+str(t.get('pattern') or '')+' '+str(t.get('path') or '')).lower().replace(chr(92),'/');exts=('.py','.js','.ts','.tsx','.jsx','.go','.rs','.java','.rb','.c','.h','.cpp','.hpp','.cc','.cs','.kt','.swift','.php','.scala','.lua','.sh','.md','.rst','.txt','.mdx');sys.stdout.write('1' if 'graphify-out/' not in s and any(e in s for e in exts) else '')\" 2>/dev/null || true); if [ \"$HIT\" = 1 ] && [ -f graphify-out/graph.json ]; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"additionalContext\":\"MANDATORY: graphify-out/graph.json exists. You MUST run graphify before reading source files. Use: `graphify query \\\"<question>\\\"` (scoped subgraph), `graphify explain \\\"<concept>\\\"`, or `graphify path \\\"<A>\\\" \\\"<B>\\\"`. Only read raw files after graphify has oriented you, or to modify/debug specific lines. This rule applies to subagents too \u2014 include it in every subagent prompt involving code exploration.\"}}'; fi || true"
19
- }
20
- ]
21
- }
22
- ]
23
- }
24
- }
@@ -1,20 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(cp tests/e2e/wide-events/wide-event.spec.ts /tmp/we.bak)",
5
- "Bash(cp /tmp/we.bak tests/e2e/wide-events/wide-event.spec.ts)",
6
- "Bash(graphify query *)",
7
- "Bash(rtk npm *)",
8
- "Bash(rtk jest *)",
9
- "WebFetch(domain:jeremymorrell.dev)",
10
- "WebFetch(domain:boristane.com)",
11
- "WebFetch(domain:isburmistrov.substack.com)",
12
- "Bash(rtk npx *)",
13
- "Bash(rtk ls *)",
14
- "Bash(rtk grep *)",
15
- "Bash(rtk read *)",
16
- "Bash(cd /Users/pragmaticivan/Code/pragmaticivan/nestjs-otel-prom-grafana-tempo)",
17
- "Bash(rtk find *)"
18
- ]
19
- }
20
- }
package/.tmp_build.out DELETED
@@ -1,3 +0,0 @@
1
- go: cannot find main module, but found .git/config in /Users/pragmaticivan/Code/pragmaticivan/nestjs-otel
2
- to create a module there, run:
3
- go mod init
@@ -1,12 +0,0 @@
1
- {
2
- "0": "Metric Decorators & Data",
3
- "1": "Span Decorator & Tests",
4
- "2": "Module Bootstrap & Tracing",
5
- "3": "Wide Events",
6
- "4": "Traceable Decorator",
7
- "5": "Counter Decorators & Metadata Utils",
8
- "6": "Test Service Fixtures",
9
- "7": "Metric Injection Tokens",
10
- "8": "Baggage Decorator",
11
- "9": "CurrentSpan Decorator"
12
- }
@@ -1 +0,0 @@
1
- /Users/pragmaticivan/.local/share/mise/installs/python/3.14.6/bin/python3.14
@@ -1 +0,0 @@
1
- /Users/pragmaticivan/Code/pragmaticivan/nestjs-otel
@@ -1,89 +0,0 @@
1
- # Graph Report - . (2026-06-15)
2
-
3
- ## Corpus Check
4
- - Corpus is ~6,192 words - fits in a single context window. You may not need a graph.
5
-
6
- ## Summary
7
- - 157 nodes · 286 edges · 10 communities (6 shown, 4 thin omitted)
8
- - Extraction: 100% EXTRACTED · 0% INFERRED · 0% AMBIGUOUS
9
- - Token cost: 0 input · 0 output
10
-
11
- ## Community Hubs (Navigation)
12
- - [[_COMMUNITY_Metric Decorators & Data|Metric Decorators & Data]]
13
- - [[_COMMUNITY_Span Decorator & Tests|Span Decorator & Tests]]
14
- - [[_COMMUNITY_Module Bootstrap & Tracing|Module Bootstrap & Tracing]]
15
- - [[_COMMUNITY_Wide Events|Wide Events]]
16
- - [[_COMMUNITY_Traceable Decorator|Traceable Decorator]]
17
- - [[_COMMUNITY_Counter Decorators & Metadata Utils|Counter Decorators & Metadata Utils]]
18
- - [[_COMMUNITY_Test Service Fixtures|Test Service Fixtures]]
19
- - [[_COMMUNITY_Metric Injection Tokens|Metric Injection Tokens]]
20
- - [[_COMMUNITY_Baggage Decorator|Baggage Decorator]]
21
- - [[_COMMUNITY_CurrentSpan Decorator|CurrentSpan Decorator]]
22
-
23
- ## God Nodes (most connected - your core abstractions)
24
- 1. `TestSpan` - 17 edges
25
- 2. `OtelMetricOptions` - 12 edges
26
- 3. `OpenTelemetryModuleOptions` - 9 edges
27
- 4. `MetricService` - 9 edges
28
- 5. `getOrCreate()` - 8 edges
29
- 6. `OpenTelemetryCoreModule` - 8 edges
30
- 7. `WideEventService` - 8 edges
31
- 8. `OpenTelemetryModuleAsyncOptions` - 7 edges
32
- 9. `TestService` - 7 edges
33
- 10. `getWideEventBag()` - 7 edges
34
-
35
- ## Surprising Connections (you probably didn't know these)
36
- - `InjectMetric()` --calls--> `getToken()` [EXTRACTED]
37
- src/metrics/injector.ts → src/metrics/utils.ts
38
- - `record()` --calls--> `getWideEventBag()` [EXTRACTED]
39
- src/wide-events/wide-event-field.decorator.ts → src/wide-events/wide-event.context.ts
40
-
41
- ## Import Cycles
42
- - None detected.
43
-
44
- ## Communities (10 total, 4 thin omitted)
45
-
46
- ### Community 0 - "Metric Decorators & Data"
47
- Cohesion: 0.14
48
- Nodes (21): MetricParamDecorator, OtelCounter, OtelGauge, OtelHistogram, OtelObservableCounter, OtelObservableGauge, OtelObservableUpDownCounter, OtelUpDownCounter (+13 more)
49
-
50
- ### Community 1 - "Span Decorator & Tests"
51
- Cohesion: 0.08
52
- Nodes (10): ExtendedSpanOptions, handleOnResult(), recordException(), Span(), SpanDecoratorOptions, LazyThenable, makeFailingLazyThenable(), makeLazyThenable() (+2 more)
53
-
54
- ### Community 2 - "Module Bootstrap & Tracing"
55
- Cohesion: 0.15
56
- Nodes (8): OpenTelemetryMetrics, OpenTelemetryModuleAsyncOptions, OpenTelemetryModuleOptions, OpenTelemetryOptionsFactory, OpenTelemetryWideEvents, OpenTelemetryCoreModule, OpenTelemetryModule, TraceService
57
-
58
- ### Community 3 - "Wide Events"
59
- Cohesion: 0.13
60
- Nodes (10): getWideEventBag(), WIDE_EVENT_CONTEXT_KEY, WideEventBag, isAttributeValue(), record(), WideEventField(), CatsController, executionContext (+2 more)
61
-
62
- ### Community 4 - "Traceable Decorator"
63
- Cohesion: 0.22
64
- Nodes (3): TestTraceable, TestTraceableWithOptions, Traceable()
65
-
66
- ### Community 5 - "Counter Decorators & Metadata Utils"
67
- Cohesion: 0.27
68
- Nodes (4): OtelInstanceCounter(), OtelMethodCounter(), TestClass, copyMetadataFromFunctionToFunction()
69
-
70
- ## Knowledge Gaps
71
- - **20 isolated node(s):** `OpenTelemetryMetrics`, `OpenTelemetryWideEvents`, `MetricParamDecorator`, `OtelCounter`, `OtelUpDownCounter` (+15 more)
72
- These have ≤1 connection - possible missing edges or undocumented components.
73
- - **4 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
74
-
75
- ## Suggested Questions
76
- _Questions this graph is uniquely positioned to answer:_
77
-
78
- - **Why does `TestService` connect `Test Service Fixtures` to `Wide Events`?**
79
- _High betweenness centrality (0.076) - this node is a cross-community bridge._
80
- - **What connects `OpenTelemetryMetrics`, `OpenTelemetryWideEvents`, `MetricParamDecorator` to the rest of the system?**
81
- _20 weakly-connected nodes found - possible documentation gaps or missing edges._
82
- - **Should `Metric Decorators & Data` be split into smaller, more focused modules?**
83
- _Cohesion score 0.13636363636363635 - nodes in this community are weakly interconnected._
84
- - **Should `Span Decorator & Tests` be split into smaller, more focused modules?**
85
- _Cohesion score 0.08374384236453201 - nodes in this community are weakly interconnected._
86
- - **Should `Module Bootstrap & Tracing` be split into smaller, more focused modules?**
87
- _Cohesion score 0.14814814814814814 - nodes in this community are weakly interconnected._
88
- - **Should `Wide Events` be split into smaller, more focused modules?**
89
- _Cohesion score 0.1282051282051282 - nodes in this community are weakly interconnected._
@@ -1,12 +0,0 @@
1
- {
2
- "runs": [
3
- {
4
- "date": "2026-06-16T02:18:50.326220+00:00",
5
- "input_tokens": 0,
6
- "output_tokens": 0,
7
- "files": 33
8
- }
9
- ],
10
- "total_input_tokens": 0,
11
- "total_output_tokens": 0
12
- }