@moostjs/otel 0.5.33 → 0.6.1

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
@@ -1 +1,41 @@
1
- # @moostjs/otel
1
+ # @moostjs/otel
2
+
3
+ OpenTelemetry integration for [Moost](https://moost.org). Provides automatic tracing for event handlers, custom span creation, metrics collection, and trace propagation. Works by replacing Moost's context injector with a span-aware implementation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @moostjs/otel @opentelemetry/api @opentelemetry/sdk-trace-base
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { enableOtelForMoost } from '@moostjs/otel'
15
+
16
+ // Call before creating your Moost app
17
+ enableOtelForMoost()
18
+ ```
19
+
20
+ ## Composables
21
+
22
+ - `useOtelContext()` — Access tracing utilities (span, propagation headers, custom attributes).
23
+ - `useTrace()` — Get the OpenTelemetry `trace` API.
24
+ - `useSpan()` — Get the current event's root span.
25
+ - `useOtelPropagation()` — Get W3C trace-context headers for outgoing requests.
26
+
27
+ ## Decorators
28
+
29
+ - `@OtelIgnoreSpan()` — Suppress span export for a controller or handler.
30
+ - `@OtelIgnoreMeter()` — Suppress metrics for a controller or handler.
31
+
32
+ ## Span Processors
33
+
34
+ - `MoostBatchSpanProcessor` — Batch processor that respects `@OtelIgnoreSpan()`.
35
+ - `MoostSimpleSpanProcessor` — Simple processor that respects `@OtelIgnoreSpan()`.
36
+
37
+ ## [Official Documentation](https://moost.org/moost/otel)
38
+
39
+ ## License
40
+
41
+ MIT
package/dist/index.cjs CHANGED
@@ -6,11 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key$1; i < n; i++) {
10
+ key$1 = keys[i];
11
+ if (!__hasOwnProp.call(to, key$1) && key$1 !== except) __defProp(to, key$1, {
12
+ get: ((k) => from[k]).bind(null, key$1),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key$1)) || desc.enumerable
14
14
  });
15
15
  }
16
16
  return to;
@@ -23,18 +23,34 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  const __opentelemetry_api = __toESM(require("@opentelemetry/api"));
25
25
  const moost = __toESM(require("moost"));
26
+ const __moostjs_event_http = __toESM(require("@moostjs/event-http"));
26
27
  const __opentelemetry_sdk_trace_base = __toESM(require("@opentelemetry/sdk-trace-base"));
27
28
 
28
29
  //#region packages/otel/src/context.ts
30
+ /** Event context key storing the root OpenTelemetry span. */ const otelSpanKey = (0, moost.key)("otel.span");
31
+ /** Event context key storing the resolved route path for span naming. */ const otelRouteKey = (0, moost.key)("otel.route");
32
+ /** Event context key storing the event start time (epoch ms) for duration metrics. */ const otelStartTimeKey = (0, moost.key)("otel.startTime");
33
+ /** Event context key storing custom span attributes set via `useOtelContext().customSpanAttr()`. */ const customSpanAttrsKey = (0, moost.key)("customSpanAttrs");
34
+ /** Event context key storing custom metric attributes set via `useOtelContext().customMetricAttr()`. */ const customMetricAttrsKey = (0, moost.key)("customMetricAttrs");
29
35
  const spanStack = [];
30
- function useOtelContext() {
31
- const eventContext = (0, moost.useAsyncEventContext)();
32
- const store = eventContext.store("otel");
33
- const csa = eventContext.store("customSpanAttrs");
34
- const cma = eventContext.store("customMetricAttrs");
35
- const customSpanAttr = (name, value) => csa.set(name, value);
36
- const customMetricAttr = (name, value) => cma.set(name, value);
37
- const getSpan = () => store.get("span");
36
+ /**
37
+ * Provides OpenTelemetry tracing utilities scoped to the current event.
38
+ * Must be called within an active event handler context.
39
+ *
40
+ * @returns Tracing utilities including span access, propagation headers, and custom attributes.
41
+ */ function useOtelContext(ctx) {
42
+ const _ctx = ctx || (0, moost.current)();
43
+ const customSpanAttr = (name, value) => {
44
+ const attrs = _ctx.has(customSpanAttrsKey) ? _ctx.get(customSpanAttrsKey) : {};
45
+ attrs[name] = value;
46
+ _ctx.set(customSpanAttrsKey, attrs);
47
+ };
48
+ const customMetricAttr = (name, value) => {
49
+ const attrs = _ctx.has(customMetricAttrsKey) ? _ctx.get(customMetricAttrsKey) : {};
50
+ attrs[name] = value;
51
+ _ctx.set(customMetricAttrsKey, attrs);
52
+ };
53
+ const getSpan = () => _ctx.has(otelSpanKey) ? _ctx.get(otelSpanKey) : void 0;
38
54
  const getSpanContext = () => {
39
55
  const span = getSpan();
40
56
  if (span) return span.spanContext();
@@ -75,19 +91,23 @@ function useOtelContext() {
75
91
  getSpan,
76
92
  getSpanContext,
77
93
  getPropagationHeaders,
78
- registerSpan: (span) => store.set("span", span),
94
+ registerSpan: (span) => _ctx.set(otelSpanKey, span),
79
95
  pushSpan,
80
96
  customSpanAttr,
81
97
  customMetricAttr
82
98
  };
83
99
  }
84
- function useTrace() {
100
+ /** Returns the OpenTelemetry `trace` API for creating tracers and spans. */ function useTrace() {
85
101
  return __opentelemetry_api.trace;
86
102
  }
87
- function useSpan() {
103
+ /** Returns the root span for the current event, or `undefined` if no span is active. */ function useSpan() {
88
104
  return useOtelContext().getSpan();
89
105
  }
90
- function useOtelPropagation() {
106
+ /**
107
+ * Returns W3C trace-context propagation data for the current event.
108
+ * Use the returned `headers` (traceparent, tracestate) in outgoing HTTP requests
109
+ * to propagate the trace to downstream services.
110
+ */ function useOtelPropagation() {
91
111
  const { getPropagationHeaders, getSpanContext } = useOtelContext();
92
112
  return {
93
113
  ...getSpanContext(),
@@ -156,24 +176,24 @@ function getMoostMetrics() {
156
176
 
157
177
  //#endregion
158
178
  //#region packages/otel/src/span-injector.ts
159
- function _define_property(obj, key, value) {
160
- if (key in obj) Object.defineProperty(obj, key, {
179
+ function _define_property(obj, key$1, value) {
180
+ if (key$1 in obj) Object.defineProperty(obj, key$1, {
161
181
  value,
162
182
  enumerable: true,
163
183
  configurable: true,
164
184
  writable: true
165
185
  });
166
- else obj[key] = value;
186
+ else obj[key$1] = value;
167
187
  return obj;
168
188
  }
169
189
  const tracer = __opentelemetry_api.trace.getTracer("moost-tracer");
170
- var SpanInjector = class extends moost.ContextInjector {
190
+ /** Context injector that wraps Moost lifecycle hooks with OpenTelemetry spans and records metrics. */ var SpanInjector = class extends moost.ContextInjector {
171
191
  with(name, attributes, cb) {
172
192
  const fn = typeof attributes === "function" ? attributes : cb;
173
193
  const attrs = typeof attributes === "object" ? attributes : void 0;
174
194
  if (name === "Event:start" && attrs?.eventType) return this.startEvent(attrs.eventType, fn);
175
- else if (name !== "Event:start") if (this.getIgnoreSpan()) return fn();
176
- else {
195
+ else if (name !== "Event:start") {
196
+ if (this.getIgnoreSpan()) return fn();
177
197
  const span = tracer.startSpan(name, {
178
198
  kind: __opentelemetry_api.SpanKind.INTERNAL,
179
199
  attributes: attrs
@@ -217,7 +237,8 @@ var SpanInjector = class extends moost.ContextInjector {
217
237
  return cb();
218
238
  }
219
239
  getEventType() {
220
- return (0, moost.useAsyncEventContext)().getCtx().event.type;
240
+ const ctx = (0, moost.current)();
241
+ return ctx.has(moost.eventTypeKey) ? ctx.get(moost.eventTypeKey) : void 0;
221
242
  }
222
243
  getIgnoreSpan() {
223
244
  const { getMethodMeta, getControllerMeta } = (0, moost.useControllerContext)();
@@ -249,7 +270,8 @@ var SpanInjector = class extends moost.ContextInjector {
249
270
  hook(method, name, route) {
250
271
  if (method === "WF_STEP") return;
251
272
  if (method === "__SYSTEM__") return;
252
- const { getSpan } = useOtelContext();
273
+ const ctx = (0, moost.current)();
274
+ const { getSpan } = useOtelContext(ctx);
253
275
  if (name === "Handler:not_found") {
254
276
  const chm = this.getControllerHandlerMeta();
255
277
  const span = getSpan();
@@ -262,7 +284,7 @@ var SpanInjector = class extends moost.ContextInjector {
262
284
  }
263
285
  this.startEventMetrics(chm.attrs, route);
264
286
  } else if (name === "Controller:registered") {
265
- const _route = (0, moost.useAsyncEventContext)().store("otel").get("route");
287
+ const _route = ctx.has(otelRouteKey) ? ctx.get(otelRouteKey) : void 0;
266
288
  const chm = this.getControllerHandlerMeta();
267
289
  if (!chm.ignoreMeter) this.startEventMetrics(chm.attrs, _route);
268
290
  const span = getSpan();
@@ -272,7 +294,7 @@ var SpanInjector = class extends moost.ContextInjector {
272
294
  else span.updateName(`${chm.attrs["moost.event_type"]} ${_route || "<unresolved>"}`);
273
295
  }
274
296
  }
275
- if (name !== "Controller:registered") (0, moost.useAsyncEventContext)().store("otel").set("route", route);
297
+ if (name !== "Controller:registered") ctx.set(otelRouteKey, route);
276
298
  }
277
299
  withSpan(span, cb, opts) {
278
300
  return withSpan(span, cb, (_span, exception, result) => {
@@ -282,20 +304,22 @@ var SpanInjector = class extends moost.ContextInjector {
282
304
  if (!chm.ignoreMeter) this.endEventMetrics(chm.attrs, result instanceof Error ? result : exception);
283
305
  }
284
306
  if (opts.endSpan) {
285
- const customAttrs = (0, moost.useAsyncEventContext)().store("customSpanAttrs").value;
307
+ const ctx = (0, moost.current)();
308
+ const customAttrs = ctx.has(customSpanAttrsKey) ? ctx.get(customSpanAttrsKey) : void 0;
286
309
  if (customAttrs) _span.setAttributes(customAttrs);
287
310
  _span.end();
288
311
  }
289
312
  });
290
313
  }
291
314
  startEventMetrics(a, route) {
292
- (0, moost.useAsyncEventContext)().store("otel").set("startTime", Date.now());
315
+ (0, moost.current)().set(otelStartTimeKey, Date.now());
293
316
  }
294
317
  endEventMetrics(a, error) {
295
- const otelStore = (0, moost.useAsyncEventContext)().store("otel");
296
- const route = otelStore.get("route");
297
- const duration = Date.now() - (otelStore.get("startTime") || Date.now() - 1);
298
- const customAttrs = (0, moost.useAsyncEventContext)().store("customMetricAttrs").value || {};
318
+ const ctx = (0, moost.current)();
319
+ const route = ctx.has(otelRouteKey) ? ctx.get(otelRouteKey) : void 0;
320
+ const startTime = ctx.has(otelStartTimeKey) ? ctx.get(otelStartTimeKey) : void 0;
321
+ const duration = Date.now() - (startTime || Date.now() - 1);
322
+ const customAttrs = ctx.has(customMetricAttrsKey) ? ctx.get(customMetricAttrsKey) : {};
299
323
  const attrs = {
300
324
  ...customAttrs,
301
325
  route,
@@ -310,10 +334,19 @@ var SpanInjector = class extends moost.ContextInjector {
310
334
  this.metrics.moostEventDuration.record(duration, attrs);
311
335
  }
312
336
  getRequest() {
313
- return (0, moost.useAsyncEventContext)().store("event").get("req");
337
+ try {
338
+ return (0, moost.current)().get(__moostjs_event_http.httpKind.keys.req);
339
+ } catch {
340
+ return void 0;
341
+ }
314
342
  }
315
343
  getResponse() {
316
- return (0, moost.useAsyncEventContext)().store("event").get("res");
344
+ try {
345
+ const response = (0, moost.current)().get(__moostjs_event_http.httpKind.keys.response);
346
+ return response?.getRawRes(true);
347
+ } catch {
348
+ return void 0;
349
+ }
317
350
  }
318
351
  constructor(...args) {
319
352
  super(...args), _define_property(this, "metrics", getMoostMetrics());
@@ -322,13 +355,17 @@ var SpanInjector = class extends moost.ContextInjector {
322
355
 
323
356
  //#endregion
324
357
  //#region packages/otel/src/init.ts
325
- function enableOtelForMoost() {
358
+ /**
359
+ * Enables OpenTelemetry integration for Moost by replacing the default
360
+ * context injector with a span-aware `SpanInjector`.
361
+ * Call this before creating or starting your Moost application.
362
+ */ function enableOtelForMoost() {
326
363
  (0, moost.replaceContextInjector)(new SpanInjector());
327
364
  }
328
365
 
329
366
  //#endregion
330
367
  //#region packages/otel/src/otel.mate.ts
331
- function getOtelMate() {
368
+ /** Returns the shared `Mate` instance extended with OpenTelemetry metadata fields. */ function getOtelMate() {
332
369
  return (0, moost.getMoostMate)();
333
370
  }
334
371
 
@@ -347,13 +384,16 @@ const mate = getOtelMate();
347
384
 
348
385
  //#endregion
349
386
  //#region packages/otel/src/processors/span-filter.ts
350
- function shouldSpanBeIgnored(span) {
387
+ /** Returns `true` if the span has the `moost.ignore` attribute set by `@OtelIgnoreSpan()`. */ function shouldSpanBeIgnored(span) {
351
388
  return span.attributes["moost.ignore"] === true;
352
389
  }
353
390
 
354
391
  //#endregion
355
392
  //#region packages/otel/src/processors/batch-processor.ts
356
- var MoostBatchSpanProcessor = class extends __opentelemetry_sdk_trace_base.BatchSpanProcessor {
393
+ /**
394
+ * Batch span processor that filters out spans marked with `@OtelIgnoreSpan()`.
395
+ * Drop-in replacement for `BatchSpanProcessor` from `@opentelemetry/sdk-trace-base`.
396
+ */ var MoostBatchSpanProcessor = class extends __opentelemetry_sdk_trace_base.BatchSpanProcessor {
357
397
  onEnd(span) {
358
398
  if (shouldSpanBeIgnored(span)) return;
359
399
  super.onEnd(span);
@@ -362,7 +402,10 @@ var MoostBatchSpanProcessor = class extends __opentelemetry_sdk_trace_base.Batch
362
402
 
363
403
  //#endregion
364
404
  //#region packages/otel/src/processors/simple-processor.ts
365
- var MoostSimpleSpanProcessor = class extends __opentelemetry_sdk_trace_base.SimpleSpanProcessor {
405
+ /**
406
+ * Simple span processor that filters out spans marked with `@OtelIgnoreSpan()`.
407
+ * Drop-in replacement for `SimpleSpanProcessor` from `@opentelemetry/sdk-trace-base`.
408
+ */ var MoostSimpleSpanProcessor = class extends __opentelemetry_sdk_trace_base.SimpleSpanProcessor {
366
409
  onEnd(span) {
367
410
  if (shouldSpanBeIgnored(span)) return;
368
411
  super.onEnd(span);
@@ -375,8 +418,13 @@ exports.MoostSimpleSpanProcessor = MoostSimpleSpanProcessor;
375
418
  exports.OtelIgnoreMeter = OtelIgnoreMeter;
376
419
  exports.OtelIgnoreSpan = OtelIgnoreSpan;
377
420
  exports.SpanInjector = SpanInjector;
421
+ exports.customMetricAttrsKey = customMetricAttrsKey;
422
+ exports.customSpanAttrsKey = customSpanAttrsKey;
378
423
  exports.enableOtelForMoost = enableOtelForMoost;
379
424
  exports.getOtelMate = getOtelMate;
425
+ exports.otelRouteKey = otelRouteKey;
426
+ exports.otelSpanKey = otelSpanKey;
427
+ exports.otelStartTimeKey = otelStartTimeKey;
380
428
  exports.shouldSpanBeIgnored = shouldSpanBeIgnored;
381
429
  exports.useOtelContext = useOtelContext;
382
430
  exports.useOtelPropagation = useOtelPropagation;
package/dist/index.d.ts CHANGED
@@ -1,19 +1,29 @@
1
1
  import * as _opentelemetry_api from '@opentelemetry/api';
2
2
  import { Span, SpanOptions } from '@opentelemetry/api';
3
+ import * as moost from 'moost';
3
4
  import { Mate, TMoostMetadata, TMateParamMeta, ContextInjector, TContextInjectorHook } from 'moost';
5
+ import { EventContext } from '@wooksjs/event-core';
4
6
  import { BatchSpanProcessor, ReadableSpan, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
5
- import { IncomingMessage, ServerResponse } from 'http';
7
+ import * as http from 'http';
8
+ import { ServerResponse } from 'http';
6
9
 
7
- interface TOtelContext {
8
- otel?: {
9
- span?: Span;
10
- route?: string;
11
- startTime?: number;
12
- };
13
- customSpanAttrs?: Record<string, string>;
14
- customMetricAttrs?: Record<string, string>;
15
- }
16
- declare function useOtelContext(): {
10
+ /** Event context key storing the root OpenTelemetry span. */
11
+ declare const otelSpanKey: moost.Key<Span | undefined>;
12
+ /** Event context key storing the resolved route path for span naming. */
13
+ declare const otelRouteKey: moost.Key<string | undefined>;
14
+ /** Event context key storing the event start time (epoch ms) for duration metrics. */
15
+ declare const otelStartTimeKey: moost.Key<number | undefined>;
16
+ /** Event context key storing custom span attributes set via `useOtelContext().customSpanAttr()`. */
17
+ declare const customSpanAttrsKey: moost.Key<Record<string, string | number>>;
18
+ /** Event context key storing custom metric attributes set via `useOtelContext().customMetricAttr()`. */
19
+ declare const customMetricAttrsKey: moost.Key<Record<string, string | number>>;
20
+ /**
21
+ * Provides OpenTelemetry tracing utilities scoped to the current event.
22
+ * Must be called within an active event handler context.
23
+ *
24
+ * @returns Tracing utilities including span access, propagation headers, and custom attributes.
25
+ */
26
+ declare function useOtelContext(ctx?: EventContext): {
17
27
  trace: _opentelemetry_api.TraceAPI;
18
28
  withChildSpan: <T>(name: string, cb: (...a: any[]) => T, opts?: SpanOptions) => () => T;
19
29
  getSpan: () => Span | undefined;
@@ -25,13 +35,20 @@ declare function useOtelContext(): {
25
35
  traceparent?: undefined;
26
36
  tracestate?: undefined;
27
37
  };
28
- registerSpan: (span: Span) => unknown;
38
+ registerSpan: (span: Span) => void;
29
39
  pushSpan: (span: Span) => void;
30
- customSpanAttr: (name: string, value: string | number) => unknown;
31
- customMetricAttr: (name: string, value: string | number) => unknown;
40
+ customSpanAttr: (name: string, value: string | number) => void;
41
+ customMetricAttr: (name: string, value: string | number) => void;
32
42
  };
43
+ /** Returns the OpenTelemetry `trace` API for creating tracers and spans. */
33
44
  declare function useTrace(): _opentelemetry_api.TraceAPI;
45
+ /** Returns the root span for the current event, or `undefined` if no span is active. */
34
46
  declare function useSpan(): Span | undefined;
47
+ /**
48
+ * Returns W3C trace-context propagation data for the current event.
49
+ * Use the returned `headers` (traceparent, tracestate) in outgoing HTTP requests
50
+ * to propagate the trace to downstream services.
51
+ */
35
52
  declare function useOtelPropagation(): {
36
53
  headers: {
37
54
  traceparent: string;
@@ -47,6 +64,11 @@ declare function useOtelPropagation(): {
47
64
  traceState?: _opentelemetry_api.TraceState;
48
65
  };
49
66
 
67
+ /**
68
+ * Enables OpenTelemetry integration for Moost by replacing the default
69
+ * context injector with a span-aware `SpanInjector`.
70
+ * Call this before creating or starting your Moost application.
71
+ */
50
72
  declare function enableOtelForMoost(): void;
51
73
 
52
74
  /**
@@ -61,27 +83,39 @@ declare const OtelIgnoreSpan: () => MethodDecorator & ClassDecorator & Parameter
61
83
  */
62
84
  declare const OtelIgnoreMeter: () => MethodDecorator & ClassDecorator & ParameterDecorator & PropertyDecorator;
63
85
 
86
+ /** OpenTelemetry metadata fields attached to classes and methods by OTEL decorators. */
64
87
  interface TOtelMate {
65
88
  otelIgnoreSpan: boolean;
66
89
  otelIgnoreMeter: boolean;
67
90
  }
91
+ /** Returns the shared `Mate` instance extended with OpenTelemetry metadata fields. */
68
92
  declare function getOtelMate(): Mate<TMoostMetadata & TOtelMate & {
69
- params: Array<TOtelMate & TMateParamMeta>;
93
+ params: (TOtelMate & TMateParamMeta)[];
70
94
  }, TMoostMetadata & TOtelMate & {
71
- params: Array<TOtelMate & TMateParamMeta>;
95
+ params: (TOtelMate & TMateParamMeta)[];
72
96
  }>;
73
97
 
98
+ /**
99
+ * Batch span processor that filters out spans marked with `@OtelIgnoreSpan()`.
100
+ * Drop-in replacement for `BatchSpanProcessor` from `@opentelemetry/sdk-trace-base`.
101
+ */
74
102
  declare class MoostBatchSpanProcessor extends BatchSpanProcessor {
75
103
  onEnd(span: ReadableSpan): void;
76
104
  }
77
105
 
106
+ /**
107
+ * Simple span processor that filters out spans marked with `@OtelIgnoreSpan()`.
108
+ * Drop-in replacement for `SimpleSpanProcessor` from `@opentelemetry/sdk-trace-base`.
109
+ */
78
110
  declare class MoostSimpleSpanProcessor extends SimpleSpanProcessor {
79
111
  onEnd(span: ReadableSpan): void;
80
112
  }
81
113
 
114
+ /** Returns `true` if the span has the `moost.ignore` attribute set by `@OtelIgnoreSpan()`. */
82
115
  declare function shouldSpanBeIgnored(span: ReadableSpan): boolean;
83
116
 
84
117
  type TAttributes = Record<string, string | number | boolean>;
118
+ /** Context injector that wraps Moost lifecycle hooks with OpenTelemetry spans and records metrics. */
85
119
  declare class SpanInjector extends ContextInjector<TContextInjectorHook> {
86
120
  metrics: {
87
121
  moostEventDuration: _opentelemetry_api.Histogram;
@@ -90,7 +124,7 @@ declare class SpanInjector extends ContextInjector<TContextInjectorHook> {
90
124
  with<T>(name: TContextInjectorHook, cb: () => T): T;
91
125
  protected patchRsponse(): void;
92
126
  protected startEvent<T>(eventType: string, cb: () => T): T;
93
- getEventType(): string;
127
+ getEventType(): string | undefined;
94
128
  getIgnoreSpan(): boolean | undefined;
95
129
  getControllerHandlerMeta(): {
96
130
  ignoreMeter: boolean | undefined;
@@ -102,8 +136,8 @@ declare class SpanInjector extends ContextInjector<TContextInjectorHook> {
102
136
  'moost.handler_label': string | undefined;
103
137
  'moost.handler_id': string | undefined;
104
138
  'moost.ignore': boolean | undefined;
105
- 'moost.route': string | undefined;
106
- 'moost.event_type': string;
139
+ 'moost.route': string;
140
+ 'moost.event_type': string | undefined;
107
141
  };
108
142
  };
109
143
  hook(method: string, name: 'Handler:not_found' | 'Handler:routed' | 'Controller:registered', route?: string): void;
@@ -113,14 +147,16 @@ declare class SpanInjector extends ContextInjector<TContextInjectorHook> {
113
147
  }): T;
114
148
  startEventMetrics(a: Record<string, string | number | boolean | undefined>, route?: string): void;
115
149
  endEventMetrics(a: Record<string, string | number | boolean | undefined>, error?: Error): void;
116
- getRequest(): IncomingMessage | undefined;
117
- getResponse(): (ServerResponse<IncomingMessage> & {
150
+ getRequest(): http.IncomingMessage | undefined;
151
+ getResponse(): (ServerResponse<http.IncomingMessage> & {
118
152
  _statusCode?: number;
119
153
  _contentLength?: number;
120
154
  }) | undefined;
121
155
  }
122
156
 
157
+ /** Callback invoked after span execution for enrichment. When provided, you must call `span.end()` yourself. */
123
158
  type TPostSpanProcessFn<T> = (span: Span, exception: Error | undefined, result: Awaited<T> | undefined) => void;
159
+ /** Input for creating a new span with `withSpan()`. */
124
160
  interface TSpanInput {
125
161
  name: string;
126
162
  options?: SpanOptions;
@@ -139,5 +175,5 @@ interface TSpanInput {
139
175
  */
140
176
  declare function withSpan<T>(span: TSpanInput | Span, cb: () => T, postProcess?: TPostSpanProcessFn<T>): T;
141
177
 
142
- export { MoostBatchSpanProcessor, MoostSimpleSpanProcessor, OtelIgnoreMeter, OtelIgnoreSpan, SpanInjector, enableOtelForMoost, getOtelMate, shouldSpanBeIgnored, useOtelContext, useOtelPropagation, useSpan, useTrace, withSpan };
143
- export type { TOtelContext, TOtelMate, TPostSpanProcessFn, TSpanInput };
178
+ export { MoostBatchSpanProcessor, MoostSimpleSpanProcessor, OtelIgnoreMeter, OtelIgnoreSpan, SpanInjector, customMetricAttrsKey, customSpanAttrsKey, enableOtelForMoost, getOtelMate, otelRouteKey, otelSpanKey, otelStartTimeKey, shouldSpanBeIgnored, useOtelContext, useOtelPropagation, useSpan, useTrace, withSpan };
179
+ export type { TOtelMate, TPostSpanProcessFn, TSpanInput };
package/dist/index.mjs CHANGED
@@ -1,17 +1,33 @@
1
1
  import { SpanKind, SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
2
- import { ContextInjector, getConstructor, getMoostMate, replaceContextInjector, useAsyncEventContext, useControllerContext } from "moost";
2
+ import { ContextInjector, current, eventTypeKey, getConstructor, getMoostMate, key, replaceContextInjector, useControllerContext } from "moost";
3
+ import { httpKind } from "@moostjs/event-http";
3
4
  import { BatchSpanProcessor, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
4
5
 
5
6
  //#region packages/otel/src/context.ts
7
+ /** Event context key storing the root OpenTelemetry span. */ const otelSpanKey = key("otel.span");
8
+ /** Event context key storing the resolved route path for span naming. */ const otelRouteKey = key("otel.route");
9
+ /** Event context key storing the event start time (epoch ms) for duration metrics. */ const otelStartTimeKey = key("otel.startTime");
10
+ /** Event context key storing custom span attributes set via `useOtelContext().customSpanAttr()`. */ const customSpanAttrsKey = key("customSpanAttrs");
11
+ /** Event context key storing custom metric attributes set via `useOtelContext().customMetricAttr()`. */ const customMetricAttrsKey = key("customMetricAttrs");
6
12
  const spanStack = [];
7
- function useOtelContext() {
8
- const eventContext = useAsyncEventContext();
9
- const store = eventContext.store("otel");
10
- const csa = eventContext.store("customSpanAttrs");
11
- const cma = eventContext.store("customMetricAttrs");
12
- const customSpanAttr = (name, value) => csa.set(name, value);
13
- const customMetricAttr = (name, value) => cma.set(name, value);
14
- const getSpan = () => store.get("span");
13
+ /**
14
+ * Provides OpenTelemetry tracing utilities scoped to the current event.
15
+ * Must be called within an active event handler context.
16
+ *
17
+ * @returns Tracing utilities including span access, propagation headers, and custom attributes.
18
+ */ function useOtelContext(ctx) {
19
+ const _ctx = ctx || current();
20
+ const customSpanAttr = (name, value) => {
21
+ const attrs = _ctx.has(customSpanAttrsKey) ? _ctx.get(customSpanAttrsKey) : {};
22
+ attrs[name] = value;
23
+ _ctx.set(customSpanAttrsKey, attrs);
24
+ };
25
+ const customMetricAttr = (name, value) => {
26
+ const attrs = _ctx.has(customMetricAttrsKey) ? _ctx.get(customMetricAttrsKey) : {};
27
+ attrs[name] = value;
28
+ _ctx.set(customMetricAttrsKey, attrs);
29
+ };
30
+ const getSpan = () => _ctx.has(otelSpanKey) ? _ctx.get(otelSpanKey) : void 0;
15
31
  const getSpanContext = () => {
16
32
  const span = getSpan();
17
33
  if (span) return span.spanContext();
@@ -52,19 +68,23 @@ function useOtelContext() {
52
68
  getSpan,
53
69
  getSpanContext,
54
70
  getPropagationHeaders,
55
- registerSpan: (span) => store.set("span", span),
71
+ registerSpan: (span) => _ctx.set(otelSpanKey, span),
56
72
  pushSpan,
57
73
  customSpanAttr,
58
74
  customMetricAttr
59
75
  };
60
76
  }
61
- function useTrace() {
77
+ /** Returns the OpenTelemetry `trace` API for creating tracers and spans. */ function useTrace() {
62
78
  return trace;
63
79
  }
64
- function useSpan() {
80
+ /** Returns the root span for the current event, or `undefined` if no span is active. */ function useSpan() {
65
81
  return useOtelContext().getSpan();
66
82
  }
67
- function useOtelPropagation() {
83
+ /**
84
+ * Returns W3C trace-context propagation data for the current event.
85
+ * Use the returned `headers` (traceparent, tracestate) in outgoing HTTP requests
86
+ * to propagate the trace to downstream services.
87
+ */ function useOtelPropagation() {
68
88
  const { getPropagationHeaders, getSpanContext } = useOtelContext();
69
89
  return {
70
90
  ...getSpanContext(),
@@ -133,24 +153,24 @@ function getMoostMetrics() {
133
153
 
134
154
  //#endregion
135
155
  //#region packages/otel/src/span-injector.ts
136
- function _define_property(obj, key, value) {
137
- if (key in obj) Object.defineProperty(obj, key, {
156
+ function _define_property(obj, key$1, value) {
157
+ if (key$1 in obj) Object.defineProperty(obj, key$1, {
138
158
  value,
139
159
  enumerable: true,
140
160
  configurable: true,
141
161
  writable: true
142
162
  });
143
- else obj[key] = value;
163
+ else obj[key$1] = value;
144
164
  return obj;
145
165
  }
146
166
  const tracer = trace.getTracer("moost-tracer");
147
- var SpanInjector = class extends ContextInjector {
167
+ /** Context injector that wraps Moost lifecycle hooks with OpenTelemetry spans and records metrics. */ var SpanInjector = class extends ContextInjector {
148
168
  with(name, attributes, cb) {
149
169
  const fn = typeof attributes === "function" ? attributes : cb;
150
170
  const attrs = typeof attributes === "object" ? attributes : void 0;
151
171
  if (name === "Event:start" && attrs?.eventType) return this.startEvent(attrs.eventType, fn);
152
- else if (name !== "Event:start") if (this.getIgnoreSpan()) return fn();
153
- else {
172
+ else if (name !== "Event:start") {
173
+ if (this.getIgnoreSpan()) return fn();
154
174
  const span = tracer.startSpan(name, {
155
175
  kind: SpanKind.INTERNAL,
156
176
  attributes: attrs
@@ -194,7 +214,8 @@ var SpanInjector = class extends ContextInjector {
194
214
  return cb();
195
215
  }
196
216
  getEventType() {
197
- return useAsyncEventContext().getCtx().event.type;
217
+ const ctx = current();
218
+ return ctx.has(eventTypeKey) ? ctx.get(eventTypeKey) : void 0;
198
219
  }
199
220
  getIgnoreSpan() {
200
221
  const { getMethodMeta, getControllerMeta } = useControllerContext();
@@ -226,7 +247,8 @@ var SpanInjector = class extends ContextInjector {
226
247
  hook(method, name, route) {
227
248
  if (method === "WF_STEP") return;
228
249
  if (method === "__SYSTEM__") return;
229
- const { getSpan } = useOtelContext();
250
+ const ctx = current();
251
+ const { getSpan } = useOtelContext(ctx);
230
252
  if (name === "Handler:not_found") {
231
253
  const chm = this.getControllerHandlerMeta();
232
254
  const span = getSpan();
@@ -239,7 +261,7 @@ var SpanInjector = class extends ContextInjector {
239
261
  }
240
262
  this.startEventMetrics(chm.attrs, route);
241
263
  } else if (name === "Controller:registered") {
242
- const _route = useAsyncEventContext().store("otel").get("route");
264
+ const _route = ctx.has(otelRouteKey) ? ctx.get(otelRouteKey) : void 0;
243
265
  const chm = this.getControllerHandlerMeta();
244
266
  if (!chm.ignoreMeter) this.startEventMetrics(chm.attrs, _route);
245
267
  const span = getSpan();
@@ -249,7 +271,7 @@ var SpanInjector = class extends ContextInjector {
249
271
  else span.updateName(`${chm.attrs["moost.event_type"]} ${_route || "<unresolved>"}`);
250
272
  }
251
273
  }
252
- if (name !== "Controller:registered") useAsyncEventContext().store("otel").set("route", route);
274
+ if (name !== "Controller:registered") ctx.set(otelRouteKey, route);
253
275
  }
254
276
  withSpan(span, cb, opts) {
255
277
  return withSpan(span, cb, (_span, exception, result) => {
@@ -259,20 +281,22 @@ var SpanInjector = class extends ContextInjector {
259
281
  if (!chm.ignoreMeter) this.endEventMetrics(chm.attrs, result instanceof Error ? result : exception);
260
282
  }
261
283
  if (opts.endSpan) {
262
- const customAttrs = useAsyncEventContext().store("customSpanAttrs").value;
284
+ const ctx = current();
285
+ const customAttrs = ctx.has(customSpanAttrsKey) ? ctx.get(customSpanAttrsKey) : void 0;
263
286
  if (customAttrs) _span.setAttributes(customAttrs);
264
287
  _span.end();
265
288
  }
266
289
  });
267
290
  }
268
291
  startEventMetrics(a, route) {
269
- useAsyncEventContext().store("otel").set("startTime", Date.now());
292
+ current().set(otelStartTimeKey, Date.now());
270
293
  }
271
294
  endEventMetrics(a, error) {
272
- const otelStore = useAsyncEventContext().store("otel");
273
- const route = otelStore.get("route");
274
- const duration = Date.now() - (otelStore.get("startTime") || Date.now() - 1);
275
- const customAttrs = useAsyncEventContext().store("customMetricAttrs").value || {};
295
+ const ctx = current();
296
+ const route = ctx.has(otelRouteKey) ? ctx.get(otelRouteKey) : void 0;
297
+ const startTime = ctx.has(otelStartTimeKey) ? ctx.get(otelStartTimeKey) : void 0;
298
+ const duration = Date.now() - (startTime || Date.now() - 1);
299
+ const customAttrs = ctx.has(customMetricAttrsKey) ? ctx.get(customMetricAttrsKey) : {};
276
300
  const attrs = {
277
301
  ...customAttrs,
278
302
  route,
@@ -287,10 +311,19 @@ var SpanInjector = class extends ContextInjector {
287
311
  this.metrics.moostEventDuration.record(duration, attrs);
288
312
  }
289
313
  getRequest() {
290
- return useAsyncEventContext().store("event").get("req");
314
+ try {
315
+ return current().get(httpKind.keys.req);
316
+ } catch {
317
+ return void 0;
318
+ }
291
319
  }
292
320
  getResponse() {
293
- return useAsyncEventContext().store("event").get("res");
321
+ try {
322
+ const response = current().get(httpKind.keys.response);
323
+ return response?.getRawRes(true);
324
+ } catch {
325
+ return void 0;
326
+ }
294
327
  }
295
328
  constructor(...args) {
296
329
  super(...args), _define_property(this, "metrics", getMoostMetrics());
@@ -299,13 +332,17 @@ var SpanInjector = class extends ContextInjector {
299
332
 
300
333
  //#endregion
301
334
  //#region packages/otel/src/init.ts
302
- function enableOtelForMoost() {
335
+ /**
336
+ * Enables OpenTelemetry integration for Moost by replacing the default
337
+ * context injector with a span-aware `SpanInjector`.
338
+ * Call this before creating or starting your Moost application.
339
+ */ function enableOtelForMoost() {
303
340
  replaceContextInjector(new SpanInjector());
304
341
  }
305
342
 
306
343
  //#endregion
307
344
  //#region packages/otel/src/otel.mate.ts
308
- function getOtelMate() {
345
+ /** Returns the shared `Mate` instance extended with OpenTelemetry metadata fields. */ function getOtelMate() {
309
346
  return getMoostMate();
310
347
  }
311
348
 
@@ -324,13 +361,16 @@ const mate = getOtelMate();
324
361
 
325
362
  //#endregion
326
363
  //#region packages/otel/src/processors/span-filter.ts
327
- function shouldSpanBeIgnored(span) {
364
+ /** Returns `true` if the span has the `moost.ignore` attribute set by `@OtelIgnoreSpan()`. */ function shouldSpanBeIgnored(span) {
328
365
  return span.attributes["moost.ignore"] === true;
329
366
  }
330
367
 
331
368
  //#endregion
332
369
  //#region packages/otel/src/processors/batch-processor.ts
333
- var MoostBatchSpanProcessor = class extends BatchSpanProcessor {
370
+ /**
371
+ * Batch span processor that filters out spans marked with `@OtelIgnoreSpan()`.
372
+ * Drop-in replacement for `BatchSpanProcessor` from `@opentelemetry/sdk-trace-base`.
373
+ */ var MoostBatchSpanProcessor = class extends BatchSpanProcessor {
334
374
  onEnd(span) {
335
375
  if (shouldSpanBeIgnored(span)) return;
336
376
  super.onEnd(span);
@@ -339,7 +379,10 @@ var MoostBatchSpanProcessor = class extends BatchSpanProcessor {
339
379
 
340
380
  //#endregion
341
381
  //#region packages/otel/src/processors/simple-processor.ts
342
- var MoostSimpleSpanProcessor = class extends SimpleSpanProcessor {
382
+ /**
383
+ * Simple span processor that filters out spans marked with `@OtelIgnoreSpan()`.
384
+ * Drop-in replacement for `SimpleSpanProcessor` from `@opentelemetry/sdk-trace-base`.
385
+ */ var MoostSimpleSpanProcessor = class extends SimpleSpanProcessor {
343
386
  onEnd(span) {
344
387
  if (shouldSpanBeIgnored(span)) return;
345
388
  super.onEnd(span);
@@ -347,4 +390,4 @@ var MoostSimpleSpanProcessor = class extends SimpleSpanProcessor {
347
390
  };
348
391
 
349
392
  //#endregion
350
- export { MoostBatchSpanProcessor, MoostSimpleSpanProcessor, OtelIgnoreMeter, OtelIgnoreSpan, SpanInjector, enableOtelForMoost, getOtelMate, shouldSpanBeIgnored, useOtelContext, useOtelPropagation, useSpan, useTrace, withSpan };
393
+ export { MoostBatchSpanProcessor, MoostSimpleSpanProcessor, OtelIgnoreMeter, OtelIgnoreSpan, SpanInjector, customMetricAttrsKey, customSpanAttrsKey, enableOtelForMoost, getOtelMate, otelRouteKey, otelSpanKey, otelStartTimeKey, shouldSpanBeIgnored, useOtelContext, useOtelPropagation, useSpan, useTrace, withSpan };
package/package.json CHANGED
@@ -1,11 +1,34 @@
1
1
  {
2
2
  "name": "@moostjs/otel",
3
- "version": "0.5.33",
3
+ "version": "0.6.1",
4
4
  "description": "@moostjs/otel",
5
+ "keywords": [
6
+ "composables",
7
+ "framework",
8
+ "moost",
9
+ "moostjs",
10
+ "prostojs",
11
+ "wooksjs"
12
+ ],
13
+ "homepage": "https://github.com/moostjs/moostjs/tree/main/packages/otel#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/moostjs/moostjs/issues"
16
+ },
17
+ "license": "MIT",
18
+ "author": "Artem Maltsev",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/moostjs/moostjs.git",
22
+ "directory": "packages/otel"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": false,
5
29
  "main": "dist/index.cjs",
6
30
  "module": "dist/index.mjs",
7
31
  "types": "dist/index.d.ts",
8
- "sideEffects": false,
9
32
  "exports": {
10
33
  "./package.json": "./package.json",
11
34
  ".": {
@@ -14,38 +37,18 @@
14
37
  "require": "./dist/index.cjs"
15
38
  }
16
39
  },
17
- "files": [
18
- "dist"
19
- ],
20
- "repository": {
21
- "type": "git",
22
- "url": "git+https://github.com/moostjs/moostjs.git",
23
- "directory": "packages/otel"
24
- },
25
- "keywords": [
26
- "moost",
27
- "moostjs",
28
- "composables",
29
- "framework",
30
- "wooksjs",
31
- "prostojs"
32
- ],
33
- "author": "Artem Maltsev",
34
- "license": "MIT",
35
- "bugs": {
36
- "url": "https://github.com/moostjs/moostjs/issues"
37
- },
38
- "homepage": "https://github.com/moostjs/moostjs/tree/main/packages/otel#readme",
39
- "peerDependencies": {
40
- "moost": "^0.5.33"
41
- },
42
40
  "dependencies": {
43
41
  "@opentelemetry/api": "^1.9.0",
44
- "@opentelemetry/sdk-trace-base": "^2.0.1"
42
+ "@opentelemetry/sdk-trace-base": "^2.5.1"
45
43
  },
46
44
  "devDependencies": {
47
45
  "vitest": "3.2.4"
48
46
  },
47
+ "peerDependencies": {
48
+ "@wooksjs/event-core": "^0.7.3",
49
+ "@moostjs/event-http": "^0.6.1",
50
+ "moost": "^0.6.1"
51
+ },
49
52
  "scripts": {
50
53
  "pub": "pnpm publish --access public",
51
54
  "test": "vitest"