effect-inspect 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 (38) hide show
  1. package/README.md +122 -0
  2. package/app/dist/client/assets/index-smV05cfr.js +25 -0
  3. package/app/dist/client/assets/rolldown-runtime-CbXtAM7H.js +1 -0
  4. package/app/dist/client/assets/routes-TKgeFdSW.js +5 -0
  5. package/app/dist/client/assets/styles-B3rAZGvS.css +2 -0
  6. package/app/dist/server/assets/_tanstack-start-manifest_v-Co953HeC.js +20 -0
  7. package/app/dist/server/assets/empty-plugin-adapters-D9UWiqvJ.js +5 -0
  8. package/app/dist/server/assets/router-CN98Ramo.js +491 -0
  9. package/app/dist/server/assets/routes-eZ4XqxE9.js +3999 -0
  10. package/app/dist/server/assets/start-5Z2QO8AU.js +4 -0
  11. package/app/dist/server/server.js +1812 -0
  12. package/dist/cli.d.ts +3 -0
  13. package/dist/cli.js +29 -0
  14. package/dist/client/Client.d.ts +52 -0
  15. package/dist/client/Client.js +224 -0
  16. package/dist/client/Edge.d.ts +31 -0
  17. package/dist/client/Edge.js +108 -0
  18. package/dist/client/Inspect.d.ts +49 -0
  19. package/dist/client/Inspect.js +55 -0
  20. package/dist/client/Tracer.d.ts +31 -0
  21. package/dist/client/Tracer.js +119 -0
  22. package/dist/collector/Config.d.ts +8 -0
  23. package/dist/collector/Config.js +9 -0
  24. package/dist/collector/Server.d.ts +24 -0
  25. package/dist/collector/Server.js +172 -0
  26. package/dist/collector/Store.d.ts +86 -0
  27. package/dist/collector/Store.js +119 -0
  28. package/dist/collector/WebApp.d.ts +3 -0
  29. package/dist/collector/WebApp.js +36 -0
  30. package/dist/collector/main.d.ts +1 -0
  31. package/dist/collector/main.js +22 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.js +3 -0
  34. package/dist/protocol/Codec.d.ts +575 -0
  35. package/dist/protocol/Codec.js +50 -0
  36. package/dist/protocol/Schema.d.ts +1237 -0
  37. package/dist/protocol/Schema.js +327 -0
  38. package/package.json +85 -0
@@ -0,0 +1,327 @@
1
+ /**
2
+ * The effect-inspect wire protocol.
3
+ *
4
+ * Three unions: {@link ClientMessage} (instrumented program → collector),
5
+ * {@link CollectorMessage} (collector → instrumented program) and
6
+ * {@link WebappMessage} (collector → webapp).
7
+ *
8
+ * Informed by `effect/devtools/DevToolsSchema` but deliberately different:
9
+ * spans are delta-encoded as `SpanStart` / `SpanEnd` rather than a full span
10
+ * snapshot re-sent on end, every message carries a `sessionId`, and attribute
11
+ * values are a bounded {@link Json} union instead of `Schema.Any`.
12
+ */
13
+ import { Schema } from 'effect';
14
+ /**
15
+ * Monotonic nanosecond timestamp, matching `Tracer.Span#startTime`.
16
+ *
17
+ * Carried as a decimal string on the wire because JSON numbers cannot hold
18
+ * nanos without precision loss.
19
+ */
20
+ export const Timestamp = Schema.BigIntFromString;
21
+ export const Json = Schema.Union([
22
+ Schema.String,
23
+ Schema.Finite,
24
+ Schema.Boolean,
25
+ Schema.Null,
26
+ Schema.Array(Schema.suspend(() => Json)),
27
+ Schema.Record(Schema.String, Schema.suspend(() => Json)),
28
+ ]);
29
+ /** Span attributes, log annotations, and metric/event attributes. */
30
+ export const Attributes = Schema.Record(Schema.String, Json);
31
+ /** Identifies one run of an instrumented program. */
32
+ export const SessionId = Schema.String;
33
+ const sessionId = { sessionId: SessionId };
34
+ /** Mirrors `Tracer.SpanKind`. */
35
+ export const SpanKind = Schema.Literals(['internal', 'server', 'client', 'producer', 'consumer']);
36
+ /** Mirrors `LogLevel.LogLevel`. */
37
+ export const LogLevel = Schema.Literals([
38
+ 'All',
39
+ 'Fatal',
40
+ 'Error',
41
+ 'Warn',
42
+ 'Info',
43
+ 'Debug',
44
+ 'Trace',
45
+ 'None',
46
+ ]);
47
+ /**
48
+ * Wall-clock anchor for a session's monotonic clock.
49
+ *
50
+ * `startTime` is nanos from the same monotonic source as span times;
51
+ * `wallClockEpochMillis` is `Date.now()` sampled at the same instant. Together
52
+ * they let the webapp render absolute times and align sessions with each other.
53
+ */
54
+ export const Clock = Schema.Struct({
55
+ startTime: Timestamp,
56
+ wallClockEpochMillis: Schema.Natural,
57
+ });
58
+ /** First message on a client connection; opens the session. */
59
+ export const Hello = Schema.Struct({
60
+ _tag: Schema.tag('Hello'),
61
+ ...sessionId,
62
+ program: Schema.String,
63
+ pid: Schema.Natural,
64
+ runtime: Schema.String,
65
+ protocolVersion: Schema.Natural,
66
+ clock: Clock,
67
+ });
68
+ /** The protocol version a `Hello` must carry. Bump on any breaking change. */
69
+ export const protocolVersion = 1;
70
+ /**
71
+ * A span parent that lives outside this session's span tree — propagated from
72
+ * another tracing system, so the collector has ids but no span body.
73
+ */
74
+ export const ExternalParent = Schema.Struct({
75
+ _tag: Schema.tag('ExternalParent'),
76
+ spanId: Schema.String,
77
+ traceId: Schema.String,
78
+ sampled: Schema.Boolean,
79
+ });
80
+ /** A span parent within this session, referenced by id only. */
81
+ export const LocalParent = Schema.Struct({
82
+ _tag: Schema.tag('LocalParent'),
83
+ spanId: Schema.String,
84
+ });
85
+ export const SpanParent = Schema.Union([LocalParent, ExternalParent]);
86
+ /**
87
+ * A span opened. Sent once, when the span starts — never re-sent.
88
+ *
89
+ * `parent` is absent for a root span.
90
+ */
91
+ export const SpanStart = Schema.Struct({
92
+ _tag: Schema.tag('SpanStart'),
93
+ ...sessionId,
94
+ spanId: Schema.String,
95
+ traceId: Schema.String,
96
+ parent: Schema.optional(SpanParent),
97
+ name: Schema.String,
98
+ kind: SpanKind,
99
+ startTime: Timestamp,
100
+ attributes: Attributes,
101
+ sampled: Schema.Boolean,
102
+ fiberId: Schema.optional(Schema.Natural),
103
+ });
104
+ /**
105
+ * How a span finished.
106
+ *
107
+ * The full `Cause` is not carried: the webapp only needs to colour the span and
108
+ * show a message, so the producer flattens failures to `error` (rendered
109
+ * message) plus optional `stack`. `_tag: "Failure"` covers expected errors,
110
+ * defects and interrupts alike, distinguished by `kind`.
111
+ */
112
+ export const SpanOutcomeSuccess = Schema.Struct({
113
+ _tag: Schema.tag('Success'),
114
+ });
115
+ export const SpanOutcomeFailure = Schema.Struct({
116
+ _tag: Schema.tag('Failure'),
117
+ kind: Schema.Literals(['Fail', 'Die', 'Interrupt']),
118
+ error: Schema.String,
119
+ stack: Schema.optional(Schema.String),
120
+ });
121
+ export const SpanOutcome = Schema.Union([SpanOutcomeSuccess, SpanOutcomeFailure]);
122
+ /**
123
+ * A span closed.
124
+ *
125
+ * `attributes` carries only attributes added after `SpanStart` was sent; the
126
+ * collector merges them over the ones it already has.
127
+ */
128
+ export const SpanEnd = Schema.Struct({
129
+ _tag: Schema.tag('SpanEnd'),
130
+ ...sessionId,
131
+ spanId: Schema.String,
132
+ endTime: Timestamp,
133
+ outcome: SpanOutcome,
134
+ attributes: Attributes,
135
+ });
136
+ /** A point-in-time event recorded against a span. */
137
+ export const SpanEvent = Schema.Struct({
138
+ _tag: Schema.tag('SpanEvent'),
139
+ ...sessionId,
140
+ spanId: Schema.String,
141
+ name: Schema.String,
142
+ time: Timestamp,
143
+ attributes: Attributes,
144
+ });
145
+ /** A log record, in the same ordered stream as spans. */
146
+ export const Log = Schema.Struct({
147
+ _tag: Schema.tag('Log'),
148
+ ...sessionId,
149
+ time: Timestamp,
150
+ level: LogLevel,
151
+ message: Json,
152
+ spanId: Schema.optional(Schema.String),
153
+ fiberId: Schema.optional(Schema.Natural),
154
+ annotations: Attributes,
155
+ });
156
+ const metric = (type, state) => Schema.Struct({
157
+ type: Schema.tag(type),
158
+ name: Schema.String,
159
+ description: Schema.optional(Schema.String),
160
+ attributes: Schema.Record(Schema.String, Schema.String),
161
+ state,
162
+ });
163
+ export const Counter = metric('Counter', Schema.Struct({ count: Schema.Natural, incremental: Schema.Boolean }));
164
+ export const Gauge = metric('Gauge', Schema.Struct({ value: Schema.Finite }));
165
+ export const Histogram = metric('Histogram', Schema.Struct({
166
+ buckets: Schema.Array(Schema.Tuple([Schema.Finite, Schema.Natural])),
167
+ count: Schema.Natural,
168
+ min: Schema.Finite,
169
+ max: Schema.Finite,
170
+ sum: Schema.Finite,
171
+ }));
172
+ export const Frequency = metric('Frequency', Schema.Struct({ occurrences: Schema.Record(Schema.String, Schema.Natural) }));
173
+ export const Summary = metric('Summary', Schema.Struct({
174
+ quantiles: Schema.Array(Schema.Tuple([
175
+ Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 1 })),
176
+ Schema.NullOr(Schema.Finite),
177
+ ])),
178
+ count: Schema.Natural,
179
+ min: Schema.Finite,
180
+ max: Schema.Finite,
181
+ sum: Schema.Finite,
182
+ }));
183
+ export const Metric = Schema.Union([Counter, Gauge, Histogram, Frequency, Summary]);
184
+ /** A snapshot of every metric, taken at `time`. */
185
+ export const Metrics = Schema.Struct({
186
+ _tag: Schema.tag('Metrics'),
187
+ ...sessionId,
188
+ time: Timestamp,
189
+ metrics: Schema.Array(Metric),
190
+ });
191
+ /** A fiber lifecycle transition. */
192
+ export const FiberEvent = Schema.Struct({
193
+ _tag: Schema.tag('FiberEvent'),
194
+ ...sessionId,
195
+ fiberId: Schema.Natural,
196
+ event: Schema.Literals(['Start', 'End', 'Suspend', 'Resume']),
197
+ parentFiberId: Schema.optional(Schema.Natural),
198
+ time: Timestamp,
199
+ });
200
+ /**
201
+ * A `process.memoryUsage()` reading, sampled on an interval by the client.
202
+ *
203
+ * Node and Bun only: a runtime without `process.memoryUsage` emits none of
204
+ * these and a session simply has no memory track. Figures are bytes, as the
205
+ * runtime reports them; `time` shares the monotonic base of span times, so the
206
+ * webapp can draw the curve against the same x-axis as the flame chart.
207
+ */
208
+ export const MemorySample = Schema.Struct({
209
+ _tag: Schema.tag('MemorySample'),
210
+ ...sessionId,
211
+ time: Timestamp,
212
+ heapUsed: Schema.Natural,
213
+ heapTotal: Schema.Natural,
214
+ rss: Schema.Natural,
215
+ external: Schema.Natural,
216
+ });
217
+ export const Ping = Schema.Struct({
218
+ _tag: Schema.tag('Ping'),
219
+ ...sessionId,
220
+ });
221
+ export const Pong = Schema.Struct({
222
+ _tag: Schema.tag('Pong'),
223
+ ...sessionId,
224
+ });
225
+ /** Asks the client for a {@link Metrics} snapshot. */
226
+ export const MetricsRequest = Schema.Struct({
227
+ _tag: Schema.tag('MetricsRequest'),
228
+ });
229
+ /** Telemetry the instrumented program sends to the collector. */
230
+ export const ClientMessage = Schema.Union([
231
+ Hello,
232
+ SpanStart,
233
+ SpanEnd,
234
+ SpanEvent,
235
+ Log,
236
+ Metrics,
237
+ FiberEvent,
238
+ MemorySample,
239
+ Ping,
240
+ ]);
241
+ /** What the collector sends back to the instrumented program. */
242
+ export const CollectorMessage = Schema.Union([Pong, MetricsRequest]);
243
+ /** A session the collector knows about, as listed to the webapp. */
244
+ export const Session = Schema.Struct({
245
+ sessionId: SessionId,
246
+ program: Schema.String,
247
+ pid: Schema.Natural,
248
+ runtime: Schema.String,
249
+ clock: Clock,
250
+ active: Schema.Boolean,
251
+ endedAtEpochMillis: Schema.optional(Schema.Natural),
252
+ });
253
+ /** Every session the collector holds. Sent on connect and on change. */
254
+ export const SessionList = Schema.Struct({
255
+ _tag: Schema.tag('SessionList'),
256
+ sessions: Schema.Array(Session),
257
+ });
258
+ /**
259
+ * Everything already recorded for a session, replayed in arrival order.
260
+ *
261
+ * Sent once when the webapp subscribes, before any live telemetry for that
262
+ * session. `complete` is false when the backlog is split across several
263
+ * messages, so the webapp knows more is coming.
264
+ */
265
+ export const Backlog = Schema.Struct({
266
+ _tag: Schema.tag('Backlog'),
267
+ ...sessionId,
268
+ messages: Schema.Array(ClientMessage),
269
+ complete: Schema.Boolean,
270
+ });
271
+ /** A live client message forwarded to the webapp as it arrives. */
272
+ export const Live = Schema.Struct({
273
+ _tag: Schema.tag('Live'),
274
+ message: ClientMessage,
275
+ });
276
+ /**
277
+ * A session ended — its program exited or its connection dropped.
278
+ *
279
+ * **Nothing emits this.** The collector marks the session `active: false` and
280
+ * re-sends the whole {@link SessionList} on every change, which already tells
281
+ * the webapp everything this message would. The variant is kept because the
282
+ * protocol is extended additively and removing it would break the union for
283
+ * anyone decoding an older stream; the webapp handler for it was deleted
284
+ * rather than left looking implemented.
285
+ */
286
+ export const SessionEnded = Schema.Struct({
287
+ _tag: Schema.tag('SessionEnded'),
288
+ ...sessionId,
289
+ endedAtEpochMillis: Schema.Natural,
290
+ });
291
+ /** What the collector sends to the webapp. */
292
+ export const WebappMessage = Schema.Union([SessionList, Backlog, Live, SessionEnded]);
293
+ /** Asks the collector to stream one session: its backlog, then live messages. */
294
+ export const Subscribe = Schema.Struct({
295
+ _tag: Schema.tag('Subscribe'),
296
+ ...sessionId,
297
+ });
298
+ /** Stops the stream started by {@link Subscribe}. */
299
+ export const Unsubscribe = Schema.Struct({
300
+ _tag: Schema.tag('Unsubscribe'),
301
+ ...sessionId,
302
+ });
303
+ /** What the webapp sends to the collector. */
304
+ export const WebappRequest = Schema.Union([Subscribe, Unsubscribe]);
305
+ /**
306
+ * Line 1 of a saved trace file.
307
+ *
308
+ * The rest of the file is {@link ClientMessage} NDJSON, byte-identical to what
309
+ * the wire carries — a trace file is the protocol message stream with a header
310
+ * on top, not a second representation of a trace. So any new `ClientMessage`
311
+ * variant is carried by a saved trace for free, and {@link traceFileFormatVersion}
312
+ * only moves if *this* struct or the line layout changes.
313
+ *
314
+ * The full {@link Session} is embedded because a loaded trace has no
315
+ * `SessionList` to get its `clock` from, and without the clock the webapp
316
+ * cannot show absolute times.
317
+ */
318
+ export const TraceFileHeader = Schema.Struct({
319
+ _tag: Schema.tag('TraceFileHeader'),
320
+ formatVersion: Schema.Natural,
321
+ /** {@link protocolVersion} at save time. Recorded for diagnosis, not enforced. */
322
+ protocolVersion: Schema.Natural,
323
+ session: Session,
324
+ savedAtEpochMillis: Schema.Natural,
325
+ });
326
+ /** The trace file layout version. Bump only on a change to the header or the line layout. */
327
+ export const traceFileFormatVersion = 1;
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "effect-inspect",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "A live inspector for Effect programs",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/julia-script/effect-inspect.git"
9
+ },
10
+ "bin": {
11
+ "effect-inspect": "dist/cli.js"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "app/dist"
16
+ ],
17
+ "type": "module",
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ }
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "scripts": {
30
+ "lint": "oxlint --disable-nested-config .",
31
+ "collector": "bun run src/collector/main.ts",
32
+ "format": "oxfmt --write .",
33
+ "check": "oxfmt --check . && oxlint --disable-nested-config . && bun run typecheck",
34
+ "test": "bun test src app",
35
+ "check:write": "oxfmt --write . && oxlint --disable-nested-config --fix .",
36
+ "prepare": "effect-tsgo patch --oxlint",
37
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && bun run build:app",
38
+ "dev": "tsc --watch --preserveWatchOutput",
39
+ "typecheck": "tsc --noEmit && tsc --noEmit -p app/tsconfig.json && tsc --noEmit -p examples/tsconfig.json",
40
+ "dev:app": "vite dev --config app/vite.config.ts",
41
+ "build:app": "vite build --config app/vite.config.ts",
42
+ "stub:collector": "bun run app/stub-collector.ts",
43
+ "example:webapp": "bun run examples/webapp.ts",
44
+ "example:concurrent": "bun run examples/concurrent.ts",
45
+ "example:deep": "bun run examples/deep.ts",
46
+ "example:failing": "bun run examples/failing.ts",
47
+ "example:slow": "bun run examples/slow.ts",
48
+ "example:firehose": "bun run examples/firehose.ts"
49
+ },
50
+ "dependencies": {
51
+ "@effect/atom-react": "4.0.0-rc.117",
52
+ "@effect/platform-node": "4.0.0-rc.117",
53
+ "@tanstack/react-router": "^1.170.39",
54
+ "@tanstack/react-start": "^1.168.58",
55
+ "class-variance-authority": "^0.7.1",
56
+ "clsx": "^2.1.1",
57
+ "effect": "4.0.0-rc.117",
58
+ "lucide-react": "^1.48.0",
59
+ "react": "^19.3.0",
60
+ "react-dom": "^19.3.0",
61
+ "scheduler": "0.27.0",
62
+ "shadow-plugin": "^2.1.1",
63
+ "tailwind-merge": "^3.7.0"
64
+ },
65
+ "devDependencies": {
66
+ "@changesets/cli": "^2.29.8",
67
+ "@effect/platform-bun": "4.0.0-rc.117",
68
+ "@effect/tsgo": "0.45.0",
69
+ "@tailwindcss/vite": "^4.3.3",
70
+ "@types/bun": "1.4.2",
71
+ "@types/react": "^19.3.0",
72
+ "@types/react-dom": "^19.3.0",
73
+ "@vitejs/plugin-react": "^6.1.1",
74
+ "oxfmt": "0.70.0",
75
+ "oxlint": "1.82.0",
76
+ "oxlint-tsgolint": "7.0.2001",
77
+ "playwright-core": "^1.63.0",
78
+ "tailwindcss": "^4.3.3",
79
+ "typescript": "7.0.2",
80
+ "vite": "^8.3.0"
81
+ },
82
+ "engines": {
83
+ "node": ">=22"
84
+ }
85
+ }