live-traces 0.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.
Files changed (63) hide show
  1. package/README.md +193 -0
  2. package/dist/index.d.ts +19 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +24 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/react/hooks.d.ts +22 -0
  7. package/dist/react/hooks.d.ts.map +1 -0
  8. package/dist/react/hooks.js +68 -0
  9. package/dist/react/hooks.js.map +1 -0
  10. package/dist/react/index.d.ts +26 -0
  11. package/dist/react/index.d.ts.map +1 -0
  12. package/dist/react/index.js +27 -0
  13. package/dist/react/index.js.map +1 -0
  14. package/dist/react/store.d.ts +77 -0
  15. package/dist/react/store.d.ts.map +1 -0
  16. package/dist/react/store.js +273 -0
  17. package/dist/react/store.js.map +1 -0
  18. package/dist/src/LiveTrace.d.ts +55 -0
  19. package/dist/src/LiveTrace.d.ts.map +1 -0
  20. package/dist/src/LiveTrace.js +66 -0
  21. package/dist/src/LiveTrace.js.map +1 -0
  22. package/dist/src/Logger.d.ts +3 -0
  23. package/dist/src/Logger.d.ts.map +1 -0
  24. package/dist/src/Logger.js +30 -0
  25. package/dist/src/Logger.js.map +1 -0
  26. package/dist/src/Schema.d.ts +97 -0
  27. package/dist/src/Schema.d.ts.map +1 -0
  28. package/dist/src/Schema.js +60 -0
  29. package/dist/src/Schema.js.map +1 -0
  30. package/dist/src/Sink.d.ts +36 -0
  31. package/dist/src/Sink.d.ts.map +1 -0
  32. package/dist/src/Sink.js +55 -0
  33. package/dist/src/Sink.js.map +1 -0
  34. package/dist/src/Tracer.d.ts +24 -0
  35. package/dist/src/Tracer.d.ts.map +1 -0
  36. package/dist/src/Tracer.js +154 -0
  37. package/dist/src/Tracer.js.map +1 -0
  38. package/dist/src/WrappedSpan.d.ts +44 -0
  39. package/dist/src/WrappedSpan.d.ts.map +1 -0
  40. package/dist/src/WrappedSpan.js +104 -0
  41. package/dist/src/WrappedSpan.js.map +1 -0
  42. package/dist/src/transports/sse.d.ts +26 -0
  43. package/dist/src/transports/sse.d.ts.map +1 -0
  44. package/dist/src/transports/sse.js +118 -0
  45. package/dist/src/transports/sse.js.map +1 -0
  46. package/dist/src/types.d.ts +73 -0
  47. package/dist/src/types.d.ts.map +1 -0
  48. package/dist/src/types.js +69 -0
  49. package/dist/src/types.js.map +1 -0
  50. package/index.ts +58 -0
  51. package/package.json +87 -0
  52. package/react/hooks.ts +73 -0
  53. package/react/index.ts +30 -0
  54. package/react/store.ts +357 -0
  55. package/src/LiveTrace.ts +99 -0
  56. package/src/Logger.ts +33 -0
  57. package/src/Schema.ts +70 -0
  58. package/src/Sink.ts +108 -0
  59. package/src/Tracer.ts +176 -0
  60. package/src/WrappedSpan.ts +138 -0
  61. package/src/__tests__/tracer.test.ts +238 -0
  62. package/src/transports/sse.ts +127 -0
  63. package/src/types.ts +151 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * TraceSink — Buffered event sink with pluggable transport.
3
+ *
4
+ * The sink has two parts:
5
+ * - TraceSinkHandle: synchronous emit() for use inside Tracer.span() (which is sync)
6
+ * - TraceSink: Effect service that manages the handle + flush daemon
7
+ *
8
+ * TraceTransport: pluggable backend (Durable Streams, SSE, WebSocket, console, etc.)
9
+ */
10
+ import * as Context from "effect/Context";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Layer from "effect/Layer";
13
+ import type { TraceEvent } from "./types.js";
14
+ export interface TraceTransport {
15
+ /** Send a batch of events. Called periodically by the flush daemon. */
16
+ readonly send: (events: ReadonlyArray<TraceEvent>) => Effect.Effect<void>;
17
+ }
18
+ declare const TraceTransportTag_base: Context.TagClass<TraceTransportTag, "@live-traces/TraceTransport", TraceTransport>;
19
+ export declare class TraceTransportTag extends TraceTransportTag_base {
20
+ }
21
+ export interface TraceSinkHandle {
22
+ /** Synchronous, non-blocking. Buffers internally. */
23
+ readonly emit: (event: TraceEvent) => void;
24
+ }
25
+ declare const TraceSink_base: Context.TagClass<TraceSink, "@live-traces/TraceSink", TraceSinkHandle>;
26
+ export declare class TraceSink extends TraceSink_base {
27
+ }
28
+ export interface TraceSinkConfig {
29
+ /** Flush interval in milliseconds. Default: 200 */
30
+ readonly flushIntervalMs?: number;
31
+ }
32
+ export declare const TraceSinkLive: (config?: TraceSinkConfig) => Layer.Layer<TraceSink, never, TraceTransportTag>;
33
+ export declare const ConsoleTransport: TraceTransport;
34
+ export declare const ConsoleTransportLayer: Layer.Layer<TraceTransportTag>;
35
+ export {};
36
+ //# sourceMappingURL=Sink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sink.d.ts","sourceRoot":"","sources":["../../src/Sink.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,MAAM,WAAW,cAAc;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAC7E;;AAED,qBAAa,iBAAkB,SAAQ,sBAA+E;CAAG;AAMzH,MAAM,WAAW,eAAe;IAC5B,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CAC9C;;AAMD,qBAAa,SAAU,SAAQ,cAAmE;CAAG;AAMrG,MAAM,WAAW,eAAe;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,eAAO,MAAM,aAAa,GAAI,SAAS,eAAe,KAAG,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,iBAAiB,CAwCnG,CAAC;AAMN,eAAO,MAAM,gBAAgB,EAAE,cAQ9B,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAsD,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * TraceSink — Buffered event sink with pluggable transport.
3
+ *
4
+ * The sink has two parts:
5
+ * - TraceSinkHandle: synchronous emit() for use inside Tracer.span() (which is sync)
6
+ * - TraceSink: Effect service that manages the handle + flush daemon
7
+ *
8
+ * TraceTransport: pluggable backend (Durable Streams, SSE, WebSocket, console, etc.)
9
+ */
10
+ import * as Context from "effect/Context";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Layer from "effect/Layer";
13
+ import * as Schedule from "effect/Schedule";
14
+ export class TraceTransportTag extends Context.Tag("@live-traces/TraceTransport")() {
15
+ }
16
+ // ============================================================================
17
+ // TraceSink — Effect service managing buffer + flush lifecycle
18
+ // ============================================================================
19
+ export class TraceSink extends Context.Tag("@live-traces/TraceSink")() {
20
+ }
21
+ export const TraceSinkLive = (config) => Layer.scoped(TraceSink, Effect.gen(function* () {
22
+ const transport = yield* TraceTransportTag;
23
+ const intervalMs = config?.flushIntervalMs ?? 200;
24
+ let buffer = [];
25
+ const flush = Effect.suspend(() => {
26
+ if (buffer.length === 0)
27
+ return Effect.void;
28
+ const batch = buffer;
29
+ buffer = [];
30
+ return transport.send(batch);
31
+ });
32
+ // Daemon fiber: flush every intervalMs
33
+ yield* flush.pipe(Effect.schedule(Schedule.spaced(intervalMs)), Effect.catchAllCause((cause) => Effect.logDebug("live-traces flush daemon error").pipe(Effect.annotateLogs("cause", String(cause)))), Effect.forkScoped);
34
+ // Final flush on scope close
35
+ yield* Effect.addFinalizer(() => flush.pipe(Effect.catchAllCause((cause) => Effect.logDebug("live-traces finalizer flush error").pipe(Effect.annotateLogs("cause", String(cause))))));
36
+ const handle = {
37
+ emit: (event) => {
38
+ buffer.push(event);
39
+ },
40
+ };
41
+ return handle;
42
+ }));
43
+ // ============================================================================
44
+ // Console transport — for development/debugging
45
+ // ============================================================================
46
+ export const ConsoleTransport = {
47
+ send: (events) => Effect.sync(() => {
48
+ for (const event of events) {
49
+ // eslint-disable-next-line no-console
50
+ console.log(`[live-trace] ${event._tag}`, JSON.stringify(event));
51
+ }
52
+ }),
53
+ };
54
+ export const ConsoleTransportLayer = Layer.succeed(TraceTransportTag, ConsoleTransport);
55
+ //# sourceMappingURL=Sink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sink.js","sourceRoot":"","sources":["../../src/Sink.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAa5C,MAAM,OAAO,iBAAkB,SAAQ,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,EAAqC;CAAG;AAWzH,+EAA+E;AAC/E,+DAA+D;AAC/D,+EAA+E;AAE/E,MAAM,OAAO,SAAU,SAAQ,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,EAA8B;CAAG;AAWrG,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAwB,EAAoD,EAAE,CACxG,KAAK,CAAC,MAAM,CACR,SAAS,EACT,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAChB,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC;IAC3C,MAAM,UAAU,GAAG,MAAM,EAAE,eAAe,IAAI,GAAG,CAAC;IAElD,IAAI,MAAM,GAAiB,EAAE,CAAC;IAE9B,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;QAC9B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC;QAC5C,MAAM,KAAK,GAAG,MAAM,CAAC;QACrB,MAAM,GAAG,EAAE,CAAC;QACZ,OAAO,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,uCAAuC;IACvC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CACb,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,EAC5C,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,gCAAgC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EACpI,MAAM,CAAC,UAAU,CACpB,CAAC;IAEF,6BAA6B;IAC7B,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,CAC5B,KAAK,CAAC,IAAI,CACN,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,EAAE,EAAE,CAC3B,MAAM,CAAC,QAAQ,CAAC,mCAAmC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CACzG,CACJ,CACJ,CAAC;IAEF,MAAM,MAAM,GAAoB;QAC5B,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;YACZ,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;KACJ,CAAC;IAEF,OAAO,MAAM,CAAC;AAClB,CAAC,CAAC,CACL,CAAC;AAEN,+EAA+E;AAC/E,gDAAgD;AAChD,+EAA+E;AAE/E,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC5C,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CACb,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;QACb,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACzB,sCAAsC;YACtC,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACrE,CAAC;IACL,CAAC,CAAC;CACT,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAmC,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import * as Layer from "effect/Layer";
2
+ import { TraceSink } from "./Sink.js";
3
+ /**
4
+ * Creates a LiveTraceLayer that wraps the current tracer.
5
+ *
6
+ * **Important**: This layer reads the current tracer from the FiberRef
7
+ * at build time via `Effect.tracerWith`. For it to wrap the OTel tracer,
8
+ * TelemetryLive (or any layer that calls `Layer.setTracer`) must be
9
+ * built BEFORE this layer. In a `Layer.provideMerge` pipe chain, that
10
+ * means TelemetryLive should appear AFTER (outer) this layer:
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const EnvLayer = ServerLive.pipe(
15
+ * Layer.provideMerge(ServicesLayer),
16
+ * // LiveTraceLayer BEFORE TelemetryLive in pipe = builds AFTER
17
+ * Layer.provideMerge(makeLiveTraceLayer()),
18
+ * // TelemetryLive AFTER in pipe = builds FIRST (sets OTel tracer)
19
+ * Layer.provideMerge(TelemetryLive),
20
+ * )
21
+ * ```
22
+ */
23
+ export declare const LiveTraceLayer: Layer.Layer<never, never, TraceSink>;
24
+ //# sourceMappingURL=Tracer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tracer.d.ts","sourceRoot":"","sources":["../../src/Tracer.ts"],"names":[],"mappings":"AAsCA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAItC,OAAO,EAAE,SAAS,EAAwB,MAAM,WAAW,CAAC;AAI5D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CA2D/D,CAAC"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * LiveTraceLayer — Effect Tracer decorator.
3
+ *
4
+ * Wraps whatever base tracer is in DefaultServices (native or OTel).
5
+ * Intercepts span creation within `LiveTrace.withTrace()` scopes
6
+ * and emits TraceEvents to a TraceSink.
7
+ *
8
+ * - Works standalone (no @effect/opentelemetry needed)
9
+ * - Works alongside OTel (wraps the OTel tracer, both systems run)
10
+ * - Zero FiberRef access in Tracer.span() — uses parent chain + attributes
11
+ *
12
+ * ## Layer composition: why ordering matters
13
+ *
14
+ * `Layer.setTracer` modifies the `currentServices` FiberRef — each call
15
+ * overwrites the previous tracer. When composing via `Layer.provideMerge`,
16
+ * the argument (`self`) is built FIRST. In a pipe chain:
17
+ *
18
+ * ```
19
+ * X.pipe(Layer.provideMerge(A), Layer.provideMerge(B))
20
+ * ```
21
+ *
22
+ * Build order: B (outermost self) → A (inner self) → X
23
+ *
24
+ * For LiveTraceLayer to wrap OTel's tracer, TelemetryLive must build
25
+ * BEFORE LiveTraceLayer so that `Effect.tracerWith` captures the OTel
26
+ * tracer. This means TelemetryLive must be OUTER (later in the pipe)
27
+ * and LiveTraceLayer must be INNER (earlier in the pipe):
28
+ *
29
+ * ```ts
30
+ * X.pipe(
31
+ * Layer.provideMerge(makeLiveTraceLayer()), // inner: builds 2nd, wraps OTel
32
+ * Layer.provideMerge(TelemetryLive), // outer: builds 1st, sets OTel
33
+ * )
34
+ * ```
35
+ */
36
+ import * as Effect from "effect/Effect";
37
+ import * as Layer from "effect/Layer";
38
+ import * as Option from "effect/Option";
39
+ import * as Tracer from "effect/Tracer";
40
+ import { TraceSink } from "./Sink.js";
41
+ import { LIVE_TRACE, LIVE_TRACE_ID, LIVE_TRACE_SCOPE_ID, LIVE_TRACE_SCOPE_TYPE } from "./types.js";
42
+ import { isWrappedSpan, shouldExclude, WrappedSpan } from "./WrappedSpan.js";
43
+ /**
44
+ * Creates a LiveTraceLayer that wraps the current tracer.
45
+ *
46
+ * **Important**: This layer reads the current tracer from the FiberRef
47
+ * at build time via `Effect.tracerWith`. For it to wrap the OTel tracer,
48
+ * TelemetryLive (or any layer that calls `Layer.setTracer`) must be
49
+ * built BEFORE this layer. In a `Layer.provideMerge` pipe chain, that
50
+ * means TelemetryLive should appear AFTER (outer) this layer:
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const EnvLayer = ServerLive.pipe(
55
+ * Layer.provideMerge(ServicesLayer),
56
+ * // LiveTraceLayer BEFORE TelemetryLive in pipe = builds AFTER
57
+ * Layer.provideMerge(makeLiveTraceLayer()),
58
+ * // TelemetryLive AFTER in pipe = builds FIRST (sets OTel tracer)
59
+ * Layer.provideMerge(TelemetryLive),
60
+ * )
61
+ * ```
62
+ */
63
+ export const LiveTraceLayer = Layer.unwrapEffect(Effect.gen(function* () {
64
+ // Capture the current tracer from DefaultServices FiberRef.
65
+ // When composed correctly (TelemetryLive outer, us inner),
66
+ // this captures the OTel tracer. When standalone, this
67
+ // captures the native tracer.
68
+ const baseTracer = yield* Effect.tracerWith(Effect.succeed);
69
+ const sink = yield* TraceSink;
70
+ const wrappedTracer = Tracer.make({
71
+ span(name, parent, context, links, startTime, kind, options) {
72
+ // Create the inner span via the base tracer (OTel or native)
73
+ const innerSpan = baseTracer.span(name, parent, context, links, startTime, kind, options);
74
+ // Should we exclude this span from live tracing?
75
+ if (shouldExclude(options?.attributes)) {
76
+ return innerSpan;
77
+ }
78
+ // Check: is this the root of a live-traced scope?
79
+ if (options?.attributes?.[LIVE_TRACE] === true) {
80
+ return createRootWrappedSpan(innerSpan, sink, options.attributes);
81
+ }
82
+ // Check: is the parent a WrappedSpan? (inside a traced scope)
83
+ if (Option.isSome(parent) && isWrappedSpan(parent.value)) {
84
+ const parentWrapped = parent.value;
85
+ const wrapped = new WrappedSpan(innerSpan, sink, parentWrapped.liveTraceId, parentWrapped.liveScope);
86
+ // IMPORTANT: options.attributes (e.g. "ui.step") are applied by Effect
87
+ // AFTER tracer.span() returns. innerSpan.attributes is empty at this point.
88
+ // We must include options.attributes directly.
89
+ sink.emit({
90
+ _tag: "SpanStart",
91
+ traceId: parentWrapped.liveTraceId,
92
+ spanId: innerSpan.spanId,
93
+ parentSpanId: parentWrapped.spanId,
94
+ name,
95
+ attributes: { ...Object.fromEntries(innerSpan.attributes), ...options?.attributes },
96
+ timestamp: Date.now(),
97
+ });
98
+ return wrapped;
99
+ }
100
+ // Not in a traced scope — pass through unchanged
101
+ return innerSpan;
102
+ },
103
+ context(f, fiber) {
104
+ // Delegate context propagation to the base tracer.
105
+ // This preserves OTel's OtelApi.context.with() behavior for
106
+ // W3C traceparent header propagation.
107
+ return baseTracer.context(f, fiber);
108
+ },
109
+ });
110
+ return Layer.setTracer(wrappedTracer);
111
+ }));
112
+ function createRootWrappedSpan(innerSpan, sink, attributes) {
113
+ const traceId = attributes[LIVE_TRACE_ID];
114
+ const scope = {
115
+ type: attributes[LIVE_TRACE_SCOPE_TYPE] ?? "user",
116
+ id: attributes[LIVE_TRACE_SCOPE_ID] ?? "unknown",
117
+ };
118
+ // Emit TraceStart
119
+ sink.emit({
120
+ _tag: "TraceStart",
121
+ traceId,
122
+ label: attributes["live-trace.label"] ?? innerSpan.name,
123
+ scope,
124
+ timestamp: Date.now(),
125
+ });
126
+ // Emit SpanStart for the root span itself
127
+ // Include attributes from the options — Effect applies them AFTER tracer.span() returns
128
+ sink.emit({
129
+ _tag: "SpanStart",
130
+ traceId,
131
+ spanId: innerSpan.spanId,
132
+ name: innerSpan.name,
133
+ attributes: { ...Object.fromEntries(innerSpan.attributes), ...attributes },
134
+ timestamp: Date.now(),
135
+ });
136
+ const wrapped = new WrappedSpan(innerSpan, sink, traceId, scope);
137
+ // Override end to also emit TraceEnd after SpanEnd
138
+ const originalEnd = wrapped.end.bind(wrapped);
139
+ wrapped.end = (endTime, exit) => {
140
+ originalEnd(endTime, exit);
141
+ const startTime = innerSpan.status._tag === "Ended" ? innerSpan.status.startTime : BigInt(0);
142
+ const durationMs = Number(endTime - startTime) / 1_000_000;
143
+ sink.emit({
144
+ _tag: "TraceEnd",
145
+ traceId,
146
+ status: exit._tag === "Success" ? "completed" : "failed",
147
+ durationMs,
148
+ error: exit._tag === "Failure" ? String(exit.cause) : undefined,
149
+ timestamp: Date.now(),
150
+ });
151
+ };
152
+ return wrapped;
153
+ }
154
+ //# sourceMappingURL=Tracer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tracer.js","sourceRoot":"","sources":["../../src/Tracer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * WrappedSpan — Span decorator that intercepts lifecycle events.
3
+ *
4
+ * Implements the full Tracer.Span interface by delegating to an inner span
5
+ * (OTel or native) while emitting TraceEvents to a TraceSink.
6
+ *
7
+ * Detection: Use `isWrappedSpan(span)` to check if a span is wrapped.
8
+ * The Symbol brand avoids instanceof checks across package boundaries.
9
+ */
10
+ import type { Context } from "effect/Context";
11
+ import type { Exit } from "effect/Exit";
12
+ import type { Option } from "effect/Option";
13
+ import type { AnySpan, Span, SpanKind, SpanLink, SpanStatus } from "effect/Tracer";
14
+ import type { TraceSinkHandle } from "./Sink.js";
15
+ import type { TraceScope } from "./types.js";
16
+ export declare const LiveTraceSymbol: unique symbol;
17
+ export declare class WrappedSpan implements Span {
18
+ readonly inner: Span;
19
+ readonly sink: TraceSinkHandle;
20
+ readonly _tag: "Span";
21
+ readonly [LiveTraceSymbol] = true;
22
+ /** The logical trace ID for routing (e.g., "doc:abc123") */
23
+ readonly liveTraceId: string;
24
+ /** Scope for stream routing */
25
+ readonly liveScope: TraceScope;
26
+ constructor(inner: Span, sink: TraceSinkHandle, liveTraceId: string, liveScope: TraceScope);
27
+ get name(): string;
28
+ get spanId(): string;
29
+ get traceId(): string;
30
+ get parent(): Option<AnySpan>;
31
+ get context(): Context<never>;
32
+ get status(): SpanStatus;
33
+ get attributes(): ReadonlyMap<string, unknown>;
34
+ get links(): ReadonlyArray<SpanLink>;
35
+ get sampled(): boolean;
36
+ get kind(): SpanKind;
37
+ attribute(key: string, value: unknown): void;
38
+ event(name: string, startTime: bigint, attributes?: Record<string, unknown>): void;
39
+ end(endTime: bigint, exit: Exit<unknown, unknown>): void;
40
+ addLinks(links: ReadonlyArray<SpanLink>): void;
41
+ }
42
+ export declare const isWrappedSpan: (span: AnySpan) => span is WrappedSpan;
43
+ export declare const shouldExclude: (attributes?: Record<string, unknown>) => boolean;
44
+ //# sourceMappingURL=WrappedSpan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WrappedSpan.d.ts","sourceRoot":"","sources":["../../src/WrappedSpan.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,eAAO,MAAM,eAAe,EAAE,OAAO,MAA+C,CAAC;AAErF,qBAAa,WAAY,YAAW,IAAI;IAWhC,QAAQ,CAAC,KAAK,EAAE,IAAI;IACpB,QAAQ,CAAC,IAAI,EAAE,eAAe;IAXlC,QAAQ,CAAC,IAAI,EAAG,MAAM,CAAU;IAChC,QAAQ,CAAC,CAAC,eAAe,CAAC,QAAQ;IAElC,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,+BAA+B;IAC/B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;gBAGlB,KAAK,EAAE,IAAI,EACX,IAAI,EAAE,eAAe,EAC9B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,UAAU;IAQzB,IAAI,IAAI,IAAI,MAAM,CAEjB;IACD,IAAI,MAAM,IAAI,MAAM,CAEnB;IACD,IAAI,OAAO,IAAI,MAAM,CAEpB;IACD,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,CAE5B;IACD,IAAI,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,CAE5B;IACD,IAAI,MAAM,IAAI,UAAU,CAEvB;IACD,IAAI,UAAU,IAAI,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAE7C;IACD,IAAI,KAAK,IAAI,aAAa,CAAC,QAAQ,CAAC,CAEnC;IACD,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,IAAI,IAAI,QAAQ,CAEnB;IAID,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAI5C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAgBlF,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,IAAI;IAiBxD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,IAAI;CAGjD;AAED,eAAO,MAAM,aAAa,GAAI,MAAM,OAAO,KAAG,IAAI,IAAI,WAAsC,CAAC;AAE7F,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OAAgD,CAAC"}
@@ -0,0 +1,104 @@
1
+ import { TRACE_INTERNAL } from "./types.js";
2
+ export const LiveTraceSymbol = Symbol.for("@live-traces/WrappedSpan");
3
+ export class WrappedSpan {
4
+ inner;
5
+ sink;
6
+ _tag = "Span";
7
+ [LiveTraceSymbol] = true;
8
+ /** The logical trace ID for routing (e.g., "doc:abc123") */
9
+ liveTraceId;
10
+ /** Scope for stream routing */
11
+ liveScope;
12
+ constructor(inner, sink, liveTraceId, liveScope) {
13
+ this.inner = inner;
14
+ this.sink = sink;
15
+ this.liveTraceId = liveTraceId;
16
+ this.liveScope = liveScope;
17
+ }
18
+ // -- Delegated reads --
19
+ get name() {
20
+ return this.inner.name;
21
+ }
22
+ get spanId() {
23
+ return this.inner.spanId;
24
+ }
25
+ get traceId() {
26
+ return this.inner.traceId;
27
+ }
28
+ get parent() {
29
+ return this.inner.parent;
30
+ }
31
+ get context() {
32
+ return this.inner.context;
33
+ }
34
+ get status() {
35
+ return this.inner.status;
36
+ }
37
+ get attributes() {
38
+ return this.inner.attributes;
39
+ }
40
+ get links() {
41
+ return this.inner.links;
42
+ }
43
+ get sampled() {
44
+ return this.inner.sampled;
45
+ }
46
+ get kind() {
47
+ return this.inner.kind;
48
+ }
49
+ // -- Intercepted mutations --
50
+ attribute(key, value) {
51
+ this.inner.attribute(key, value);
52
+ }
53
+ event(name, startTime, attributes) {
54
+ this.inner.event(name, startTime, attributes);
55
+ // Emit as SpanEvent to sink
56
+ const level = attributes?.["effect.logLevel"];
57
+ this.sink.emit({
58
+ _tag: "SpanEvent",
59
+ traceId: this.liveTraceId,
60
+ spanId: this.spanId,
61
+ name,
62
+ level: normalizeLevel(level),
63
+ attributes,
64
+ timestamp: Date.now(),
65
+ });
66
+ }
67
+ end(endTime, exit) {
68
+ this.inner.end(endTime, exit);
69
+ const startTime = this.inner.status._tag === "Ended" ? this.inner.status.startTime : BigInt(0);
70
+ const durationMs = Number(endTime - startTime) / 1_000_000;
71
+ const status = exit._tag === "Success" ? "ok" : "error";
72
+ this.sink.emit({
73
+ _tag: "SpanEnd",
74
+ traceId: this.liveTraceId,
75
+ spanId: this.spanId,
76
+ status,
77
+ durationMs,
78
+ timestamp: Date.now(),
79
+ });
80
+ }
81
+ addLinks(links) {
82
+ this.inner.addLinks(links);
83
+ }
84
+ }
85
+ export const isWrappedSpan = (span) => LiveTraceSymbol in span;
86
+ export const shouldExclude = (attributes) => attributes?.[TRACE_INTERNAL] === true;
87
+ function normalizeLevel(level) {
88
+ if (!level)
89
+ return undefined;
90
+ switch (level) {
91
+ case "DEBUG":
92
+ return "Debug";
93
+ case "INFO":
94
+ return "Info";
95
+ case "WARNING":
96
+ case "WARN":
97
+ return "Warning";
98
+ case "ERROR":
99
+ return "Error";
100
+ default:
101
+ return undefined;
102
+ }
103
+ }
104
+ //# sourceMappingURL=WrappedSpan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WrappedSpan.js","sourceRoot":"","sources":["../../src/WrappedSpan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import * as Layer from "effect/Layer";
2
+ import type { TraceEvent, TraceScope } from "../types.js";
3
+ import { TraceTransportTag } from "../Sink.js";
4
+ type Subscriber = (events: ReadonlyArray<TraceEvent>) => void;
5
+ /**
6
+ * In-process pub/sub broker. Routes trace events to subscribers keyed by
7
+ * `scope.type/scope.id`. A subscriber for `team/abc` receives only events
8
+ * for that team's traces.
9
+ */
10
+ export declare class SseBroker {
11
+ private readonly subscribers;
12
+ private readonly scopeByTraceId;
13
+ private key;
14
+ /** Subscribe to events for a specific scope. Returns an unsubscribe fn. */
15
+ subscribe(scope: TraceScope, listener: Subscriber): () => void;
16
+ /** Publish a batch - fan out per scope. Called by the transport. */
17
+ publish(events: ReadonlyArray<TraceEvent>): void;
18
+ /** Active subscriber count for a scope (useful for tests / metrics). */
19
+ subscriberCount(scope: TraceScope): number;
20
+ }
21
+ /** Singleton broker. Same instance is used by transport + HTTP handlers. */
22
+ export declare function getSseBroker(): SseBroker;
23
+ /** Effect Layer wiring the SSE transport into the trace sink. */
24
+ export declare const SSETransportLayer: Layer.Layer<TraceTransportTag>;
25
+ export {};
26
+ //# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../src/transports/sse.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,EAAE,iBAAiB,EAAuB,MAAM,YAAY,CAAC;AAEpE,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;AAE9D;;;;GAIG;AACH,qBAAa,SAAS;IAClB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiC;IAEhE,OAAO,CAAC,GAAG;IAIX,2EAA2E;IAC3E,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,GAAG,MAAM,IAAI;IAgB9D,oEAAoE;IACpE,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,IAAI;IAgChD,wEAAwE;IACxE,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM;CAG7C;AAID,4EAA4E;AAC5E,wBAAgB,YAAY,IAAI,SAAS,CAGxC;AASD,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAkD,CAAC"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * SSE TraceTransport - Server-Sent Events transport.
3
+ *
4
+ * Pushes batched trace events into an in-process broker. The broker fans
5
+ * batches out to any number of subscribers (HTTP connections holding an
6
+ * `EventSource`). Stream routing is per-`TraceScope` - a subscriber gets
7
+ * only events for the scope it subscribed to.
8
+ *
9
+ * ```ts
10
+ * import { Layer } from "effect";
11
+ * import {
12
+ * LiveTraceLayer, TraceSinkLive,
13
+ * SSETransportLayer, getSseBroker,
14
+ * } from "live-traces";
15
+ *
16
+ * // Server layer
17
+ * const TraceLive = LiveTraceLayer.pipe(
18
+ * Layer.provide(TraceSinkLive({ flushIntervalMs: 100 })),
19
+ * Layer.provide(SSETransportLayer),
20
+ * );
21
+ *
22
+ * // HTTP handler
23
+ * app.get("/traces/:scopeType/:scopeId", (req, res) => {
24
+ * res.setHeader("Content-Type", "text/event-stream");
25
+ * const unsubscribe = getSseBroker().subscribe(
26
+ * { type: req.params.scopeType, id: req.params.scopeId },
27
+ * (events) => res.write(`data: ${JSON.stringify(events)}\n\n`),
28
+ * );
29
+ * req.on("close", unsubscribe);
30
+ * });
31
+ * ```
32
+ */
33
+ import * as Effect from "effect/Effect";
34
+ import * as Layer from "effect/Layer";
35
+ import { TraceTransportTag } from "../Sink.js";
36
+ /**
37
+ * In-process pub/sub broker. Routes trace events to subscribers keyed by
38
+ * `scope.type/scope.id`. A subscriber for `team/abc` receives only events
39
+ * for that team's traces.
40
+ */
41
+ export class SseBroker {
42
+ subscribers = new Map();
43
+ scopeByTraceId = new Map();
44
+ key(scope) {
45
+ return `${scope.type}/${scope.id}`;
46
+ }
47
+ /** Subscribe to events for a specific scope. Returns an unsubscribe fn. */
48
+ subscribe(scope, listener) {
49
+ const k = this.key(scope);
50
+ let set = this.subscribers.get(k);
51
+ if (!set) {
52
+ set = new Set();
53
+ this.subscribers.set(k, set);
54
+ }
55
+ set.add(listener);
56
+ return () => {
57
+ const s = this.subscribers.get(k);
58
+ if (!s)
59
+ return;
60
+ s.delete(listener);
61
+ if (s.size === 0)
62
+ this.subscribers.delete(k);
63
+ };
64
+ }
65
+ /** Publish a batch - fan out per scope. Called by the transport. */
66
+ publish(events) {
67
+ for (const e of events) {
68
+ if (e._tag === "TraceStart")
69
+ this.scopeByTraceId.set(e.traceId, e.scope);
70
+ }
71
+ const grouped = new Map();
72
+ for (const e of events) {
73
+ const scope = this.scopeByTraceId.get(e.traceId);
74
+ if (!scope)
75
+ continue;
76
+ const k = this.key(scope);
77
+ const arr = grouped.get(k) ?? [];
78
+ arr.push(e);
79
+ grouped.set(k, arr);
80
+ }
81
+ for (const [k, batch] of grouped) {
82
+ const set = this.subscribers.get(k);
83
+ if (!set)
84
+ continue;
85
+ for (const listener of set) {
86
+ try {
87
+ listener(batch);
88
+ }
89
+ catch {
90
+ // Subscriber threw - keep the broker healthy.
91
+ }
92
+ }
93
+ }
94
+ for (const e of events) {
95
+ if (e._tag === "TraceEnd")
96
+ this.scopeByTraceId.delete(e.traceId);
97
+ }
98
+ }
99
+ /** Active subscriber count for a scope (useful for tests / metrics). */
100
+ subscriberCount(scope) {
101
+ return this.subscribers.get(this.key(scope))?.size ?? 0;
102
+ }
103
+ }
104
+ let _broker = null;
105
+ /** Singleton broker. Same instance is used by transport + HTTP handlers. */
106
+ export function getSseBroker() {
107
+ if (!_broker)
108
+ _broker = new SseBroker();
109
+ return _broker;
110
+ }
111
+ const sseTransport = {
112
+ send: (events) => Effect.sync(() => {
113
+ getSseBroker().publish(events);
114
+ }),
115
+ };
116
+ /** Effect Layer wiring the SSE transport into the trace sink. */
117
+ export const SSETransportLayer = Layer.succeed(TraceTransportTag, sseTransport);
118
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../../../src/transports/sse.ts"],"names":[],"mappings":"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"}