@composed-di/observability 0.5.0-alpha → 0.7.0-alpha

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.
Files changed (40) hide show
  1. package/dist/cli.js.map +1 -1
  2. package/dist/dashboardClient.d.ts +17 -11
  3. package/dist/dashboardClient.d.ts.map +1 -1
  4. package/dist/dashboardClient.js +28 -11
  5. package/dist/dashboardClient.js.map +1 -1
  6. package/dist/dashboardEventListener.d.ts +27 -8
  7. package/dist/dashboardEventListener.d.ts.map +1 -1
  8. package/dist/dashboardEventListener.js +38 -16
  9. package/dist/dashboardEventListener.js.map +1 -1
  10. package/dist/dashboardHtml.d.ts +6 -0
  11. package/dist/dashboardHtml.d.ts.map +1 -1
  12. package/dist/dashboardHtml.js +618 -98
  13. package/dist/dashboardHtml.js.map +1 -1
  14. package/dist/dashboardInstrumentation.d.ts +44 -0
  15. package/dist/dashboardInstrumentation.d.ts.map +1 -0
  16. package/dist/dashboardInstrumentation.js +119 -0
  17. package/dist/dashboardInstrumentation.js.map +1 -0
  18. package/dist/dashboardServer.d.ts +12 -8
  19. package/dist/dashboardServer.d.ts.map +1 -1
  20. package/dist/dashboardServer.js +52 -14
  21. package/dist/dashboardServer.js.map +1 -1
  22. package/dist/events.d.ts +20 -1
  23. package/dist/events.d.ts.map +1 -1
  24. package/dist/events.js +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/moduleGraph.d.ts.map +1 -1
  30. package/dist/moduleGraph.js.map +1 -1
  31. package/package.json +24 -23
  32. package/src/cli.ts +19 -19
  33. package/src/dashboardClient.ts +94 -83
  34. package/src/dashboardHtml.ts +620 -100
  35. package/src/dashboardInstrumentation.ts +153 -0
  36. package/src/dashboardServer.ts +183 -148
  37. package/src/events.ts +55 -35
  38. package/src/index.ts +5 -5
  39. package/src/moduleGraph.ts +9 -9
  40. package/src/dashboardEventListener.ts +0 -104
package/src/events.ts CHANGED
@@ -1,71 +1,89 @@
1
1
  /**
2
- * Event and state types shared between the dashboard event listener, the
2
+ * Event and state types shared between the dashboard instrumentation, the
3
3
  * dashboard server, and the browser client (over the SSE wire).
4
4
  */
5
5
 
6
6
  /** Classifies what a span represents in the service lifecycle. */
7
- export type SpanKind = 'initialize' | 'dispose' | 'call';
7
+ export type SpanKind = 'initialize' | 'dispose' | 'call'
8
8
 
9
9
  /** Emitted when a traced function starts executing. */
10
10
  export interface SpanStart {
11
- type: 'start';
11
+ type: 'start'
12
12
  /** Monotonically increasing span id, unique per tracer. */
13
- id: number;
13
+ id: number
14
14
  /** Id of the span that was active when this one started, if any. */
15
- parentId: number | null;
15
+ parentId: number | null
16
16
  /** The qualified span name, e.g. "Database.query". */
17
- name: string;
17
+ name: string
18
18
  /** The service the span belongs to, or null if it could not be attributed. */
19
- service: string | null;
19
+ service: string | null
20
20
  /** The method or lifecycle step, e.g. "query" or "initialize". */
21
- method: string;
22
- kind: SpanKind;
21
+ method: string
22
+ kind: SpanKind
23
23
  /** Epoch milliseconds. */
24
- time: number;
24
+ time: number
25
+ /** Method arguments serialized to JSON, when capture is enabled. */
26
+ args?: string
25
27
  }
26
28
 
27
29
  /** Emitted when a traced function finishes (or throws/rejects). */
28
30
  export interface SpanEnd {
29
- type: 'end';
31
+ type: 'end'
30
32
  /** Matches the id of the corresponding SpanStart. */
31
- id: number;
33
+ id: number
32
34
  /** Epoch milliseconds. */
33
- time: number;
34
- durationMs: number;
35
+ time: number
36
+ durationMs: number
35
37
  /** The error message when the traced function threw or rejected. */
36
- error: string | null;
38
+ error: string | null
39
+ /** Return / resolved value serialized to JSON, when capture is enabled. */
40
+ result?: string
37
41
  }
38
42
 
39
- export type SpanEvent = SpanStart | SpanEnd;
43
+ export type SpanEvent = SpanStart | SpanEnd
40
44
 
41
45
  export type ServiceStatus =
42
46
  | 'pending'
43
47
  | 'initializing'
44
48
  | 'ready'
45
49
  | 'error'
46
- | 'disposed';
50
+ | 'disposed'
51
+
52
+ /** Aggregated counters for a single method of a service. */
53
+ export interface MethodStats {
54
+ /** Number of completed call spans for this method. */
55
+ calls: number
56
+ /** Number of those calls that ended with an error. */
57
+ errors: number
58
+ /** Sum of completed call durations, for averaging. */
59
+ totalMs: number
60
+ /** Duration of the most recently completed call. */
61
+ lastMs: number
62
+ }
47
63
 
48
64
  /** Aggregated per-service counters maintained by the dashboard. */
49
65
  export interface ServiceStats {
50
- status: ServiceStatus;
66
+ status: ServiceStatus
51
67
  /** Duration of the last successful initialization, if any. */
52
- initMs: number | null;
68
+ initMs: number | null
53
69
  /** Number of completed method-call spans. */
54
- calls: number;
70
+ calls: number
55
71
  /** Number of spans (of any kind) that ended with an error. */
56
- errors: number;
72
+ errors: number
57
73
  /** Sum of completed method-call durations, for averaging. */
58
- totalCallMs: number;
74
+ totalCallMs: number
75
+ /** Per-method call breakdown, keyed by method name. */
76
+ methods: Record<string, MethodStats>
59
77
  }
60
78
 
61
79
  export interface GraphNode {
62
- name: string;
80
+ name: string
63
81
  }
64
82
 
65
83
  /** A dependency edge: `from` depends on `to`. */
66
84
  export interface GraphEdge {
67
- from: string;
68
- to: string;
85
+ from: string
86
+ to: string
69
87
  }
70
88
 
71
89
  /**
@@ -74,21 +92,23 @@ export interface GraphEdge {
74
92
  * updated per-service stats so the client needs no correlation logic.
75
93
  */
76
94
  export interface WireEvent {
77
- span: SpanEvent;
95
+ span: SpanEvent
78
96
  /** Service of the span; on end events resolved from the matching start. */
79
- service: string | null;
80
- method: string;
81
- kind: SpanKind;
97
+ service: string | null
98
+ method: string
99
+ kind: SpanKind
82
100
  /** Service of the parent span, when the span was started by another service. */
83
- parentService: string | null;
101
+ parentService: string | null
102
+ /** Serialized arguments; on end events resolved from the matching start. */
103
+ args: string | null
84
104
  /** Updated stats for `service`, present when the event changed them. */
85
- stats: ServiceStats | null;
105
+ stats: ServiceStats | null
86
106
  }
87
107
 
88
108
  /** Full state sent to a client when it connects. */
89
109
  export interface DashboardSnapshot {
90
- nodes: GraphNode[];
91
- edges: GraphEdge[];
92
- services: Record<string, ServiceStats>;
93
- recent: WireEvent[];
110
+ nodes: GraphNode[]
111
+ edges: GraphEdge[]
112
+ services: Record<string, ServiceStats>
113
+ recent: WireEvent[]
94
114
  }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- export * from './events';
2
- export * from './dashboardEventListener';
3
- export * from './dashboardClient';
4
- export * from './dashboardServer';
5
- export * from './moduleGraph';
1
+ export * from './events'
2
+ export * from './dashboardInstrumentation'
3
+ export * from './dashboardClient'
4
+ export * from './dashboardServer'
5
+ export * from './moduleGraph'
@@ -2,12 +2,12 @@ import {
2
2
  ServiceKey,
3
3
  ServiceModule,
4
4
  ServiceSelectorKey,
5
- } from '@composed-di/core';
6
- import { GraphEdge, GraphNode } from './events';
5
+ } from '@composed-di/core'
6
+ import { GraphEdge, GraphNode } from './events'
7
7
 
8
8
  export interface ModuleGraph {
9
- nodes: GraphNode[];
10
- edges: GraphEdge[];
9
+ nodes: GraphNode[]
10
+ edges: GraphEdge[]
11
11
  }
12
12
 
13
13
  /**
@@ -18,20 +18,20 @@ export interface ModuleGraph {
18
18
  export function moduleGraph(module: ServiceModule): ModuleGraph {
19
19
  const nodes = module.factories.map((factory) => ({
20
20
  name: factory.provides.name,
21
- }));
21
+ }))
22
22
 
23
23
  const edges = module.factories.flatMap((factory) =>
24
24
  factory.dependsOn.flatMap((dependency: ServiceKey<unknown>) => {
25
25
  const targets =
26
26
  dependency instanceof ServiceSelectorKey
27
27
  ? dependency.values
28
- : [dependency];
28
+ : [dependency]
29
29
  return targets.map((target) => ({
30
30
  from: factory.provides.name,
31
31
  to: target.name,
32
- }));
32
+ }))
33
33
  }),
34
- );
34
+ )
35
35
 
36
- return { nodes, edges };
36
+ return { nodes, edges }
37
37
  }
@@ -1,104 +0,0 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
- import { performance } from 'node:perf_hooks';
3
- import type {
4
- DisposeContext,
5
- EventSpan,
6
- InitializeContext,
7
- MethodCallContext,
8
- ServiceEventListener,
9
- } from '@composed-di/core';
10
- import { SpanEvent, SpanKind } from './events';
11
-
12
- /** The span context propagated across sync and async call boundaries. */
13
- interface SpanContext {
14
- id: number;
15
- }
16
-
17
- /**
18
- * A ServiceEventListener that turns service events into structured
19
- * start/end span events for the realtime dashboard.
20
- *
21
- * - Uses AsyncLocalStorage to link spans to the span that was active when
22
- * they started, which lets the dashboard draw cross-service call edges
23
- * (e.g. UserService.getUser -> Database.query). Because a listener does
24
- * not control the invocation of the operation it observes, the context
25
- * is entered with `enterWith` and can outlive the span within the same
26
- * synchronous frame; the dashboard tolerates this, as parents are only
27
- * resolved among still-open spans.
28
- * - Emits events synchronously to subscribers; it never buffers.
29
- */
30
- export class DashboardEventListener implements ServiceEventListener {
31
- private readonly context = new AsyncLocalStorage<SpanContext>();
32
- private readonly listeners = new Set<(event: SpanEvent) => void>();
33
- private nextId = 1;
34
-
35
- /**
36
- * Subscribes to span events.
37
- *
38
- * @returns A function that removes the subscription.
39
- */
40
- subscribe(listener: (event: SpanEvent) => void): () => void {
41
- this.listeners.add(listener);
42
- return () => this.listeners.delete(listener);
43
- }
44
-
45
- onInitialize({ key }: InitializeContext): EventSpan {
46
- return this.startSpan(key.name, 'initialize', 'initialize');
47
- }
48
-
49
- onDispose({ key }: DisposeContext): EventSpan {
50
- return this.startSpan(key.name, 'dispose', 'dispose');
51
- }
52
-
53
- onMethodCall({ key, methodName }: MethodCallContext): EventSpan {
54
- return this.startSpan(key.name, methodName, 'call');
55
- }
56
-
57
- private startSpan(service: string, method: string, kind: SpanKind): EventSpan {
58
- const id = this.nextId++;
59
- const parent = this.context.getStore();
60
-
61
- this.emit({
62
- type: 'start',
63
- id,
64
- parentId: parent?.id ?? null,
65
- name: `${service}.${method}`,
66
- service,
67
- method,
68
- kind,
69
- time: Date.now(),
70
- });
71
-
72
- const startedAt = performance.now();
73
- let ended = false;
74
- const end = (error: string | null) => {
75
- if (ended) return;
76
- ended = true;
77
- this.emit({
78
- type: 'end',
79
- id,
80
- time: Date.now(),
81
- durationMs: performance.now() - startedAt,
82
- error,
83
- });
84
- };
85
-
86
- // The observed operation runs right after this hook returns, in the
87
- // same synchronous frame, so entering the context here makes this span
88
- // the parent of any spans started inside the operation.
89
- this.context.enterWith({ id });
90
-
91
- return {
92
- end: () => end(null),
93
- error: (error) => end(errorMessage(error)),
94
- };
95
- }
96
-
97
- private emit(event: SpanEvent): void {
98
- this.listeners.forEach((listener) => listener(event));
99
- }
100
- }
101
-
102
- function errorMessage(error: unknown): string {
103
- return error instanceof Error ? error.message : String(error);
104
- }