@ereo/cli 0.1.27 → 0.1.28

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/dist/index.js CHANGED
@@ -12,6 +12,1398 @@ var __export = (target, all) => {
12
12
  };
13
13
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
14
14
 
15
+ // ../trace/src/span.ts
16
+ function randomHex(len) {
17
+ let s = "";
18
+ while (s.length < len) {
19
+ s += Math.random().toString(16).slice(2);
20
+ }
21
+ return s.slice(0, len);
22
+ }
23
+ function generateSpanId() {
24
+ idCounter++;
25
+ const time = Date.now().toString(16).slice(-8);
26
+ const counter = (idCounter & 65535).toString(16).padStart(4, "0");
27
+ const random = randomHex(4);
28
+ return `${time}${counter}${random}`;
29
+ }
30
+ function generateTraceId() {
31
+ return randomHex(32);
32
+ }
33
+
34
+ class SpanImpl {
35
+ id;
36
+ traceId;
37
+ _parentId;
38
+ _name;
39
+ _layer;
40
+ _status = "ok";
41
+ _startTime;
42
+ _endTime = 0;
43
+ _attributes = {};
44
+ _events = [];
45
+ _children = [];
46
+ _ended = false;
47
+ _onEnd;
48
+ _childFactory;
49
+ _onEvent;
50
+ constructor(traceId, parentId, name, layer, onEnd, childFactory, onEvent) {
51
+ this.id = generateSpanId();
52
+ this.traceId = traceId;
53
+ this._parentId = parentId;
54
+ this._name = name;
55
+ this._layer = layer;
56
+ this._startTime = performance.now();
57
+ this._onEnd = onEnd;
58
+ this._childFactory = childFactory;
59
+ this._onEvent = onEvent;
60
+ }
61
+ setAttribute(key, value) {
62
+ if (this._ended)
63
+ return;
64
+ this._attributes[key] = value;
65
+ }
66
+ event(name, attributes) {
67
+ if (this._ended)
68
+ return;
69
+ const evt = { name, time: performance.now(), attributes };
70
+ this._events.push(evt);
71
+ this._onEvent?.(this.id, evt);
72
+ }
73
+ end() {
74
+ if (this._ended)
75
+ return;
76
+ this._ended = true;
77
+ this._endTime = performance.now();
78
+ this._onEnd(this);
79
+ }
80
+ error(err) {
81
+ if (this._ended)
82
+ return;
83
+ this._status = "error";
84
+ if (err instanceof Error) {
85
+ this._attributes["error.message"] = err.message;
86
+ this._attributes["error.name"] = err.name;
87
+ if (err.stack) {
88
+ this._attributes["error.stack"] = err.stack.slice(0, 500);
89
+ }
90
+ } else {
91
+ this._attributes["error.message"] = String(err);
92
+ }
93
+ }
94
+ child(name, layer) {
95
+ const childSpan = this._childFactory(name, layer, this.id);
96
+ this._children.push(childSpan.id);
97
+ return childSpan;
98
+ }
99
+ get ended() {
100
+ return this._ended;
101
+ }
102
+ toData() {
103
+ return {
104
+ id: this.id,
105
+ traceId: this.traceId,
106
+ parentId: this._parentId,
107
+ name: this._name,
108
+ layer: this._layer,
109
+ status: this._status,
110
+ startTime: this._startTime,
111
+ endTime: this._endTime || performance.now(),
112
+ duration: (this._endTime || performance.now()) - this._startTime,
113
+ attributes: { ...this._attributes },
114
+ events: [...this._events],
115
+ children: [...this._children]
116
+ };
117
+ }
118
+ }
119
+ var idCounter = 0;
120
+
121
+ // ../trace/src/ring-buffer.ts
122
+ class RingBuffer {
123
+ capacity;
124
+ buffer;
125
+ index = 0;
126
+ count = 0;
127
+ lookup = new Map;
128
+ constructor(capacity) {
129
+ this.capacity = capacity;
130
+ this.buffer = new Array(capacity);
131
+ }
132
+ push(item) {
133
+ const evicted = this.buffer[this.index];
134
+ if (evicted) {
135
+ this.lookup.delete(evicted.id);
136
+ }
137
+ this.buffer[this.index] = item;
138
+ this.lookup.set(item.id, item);
139
+ this.index = (this.index + 1) % this.capacity;
140
+ if (this.count < this.capacity)
141
+ this.count++;
142
+ }
143
+ get(id) {
144
+ return this.lookup.get(id);
145
+ }
146
+ toArray() {
147
+ const result = [];
148
+ if (this.count < this.capacity) {
149
+ for (let i = 0;i < this.count; i++) {
150
+ const item = this.buffer[i];
151
+ if (item)
152
+ result.push(item);
153
+ }
154
+ } else {
155
+ for (let i = 0;i < this.capacity; i++) {
156
+ const item = this.buffer[(this.index + i) % this.capacity];
157
+ if (item)
158
+ result.push(item);
159
+ }
160
+ }
161
+ return result;
162
+ }
163
+ get size() {
164
+ return this.count;
165
+ }
166
+ clear() {
167
+ this.buffer = new Array(this.capacity);
168
+ this.index = 0;
169
+ this.count = 0;
170
+ this.lookup.clear();
171
+ }
172
+ }
173
+
174
+ // ../trace/src/tracer.ts
175
+ class TracerImpl {
176
+ traces;
177
+ activeTraces = new Map;
178
+ spanStacks = new Map;
179
+ subscribers = new Set;
180
+ config;
181
+ constructor(config = {}) {
182
+ this.config = {
183
+ maxTraces: config.maxTraces ?? 200,
184
+ maxSpansPerTrace: config.maxSpansPerTrace ?? 500,
185
+ layers: config.layers ?? [],
186
+ minDuration: config.minDuration ?? 0
187
+ };
188
+ this.traces = new RingBuffer(this.config.maxTraces);
189
+ }
190
+ startTrace(name, layer, metadata) {
191
+ const traceId = generateTraceId();
192
+ const span = this.createSpan(traceId, null, name, layer);
193
+ const activeTrace = {
194
+ id: traceId,
195
+ rootSpanId: span.id,
196
+ startTime: performance.now(),
197
+ spans: new Map,
198
+ metadata: {
199
+ origin: "server",
200
+ ...metadata
201
+ },
202
+ activeSpanCount: 1
203
+ };
204
+ this.activeTraces.set(traceId, activeTrace);
205
+ this.spanStacks.set(traceId, [span]);
206
+ const traceData = this.buildTraceData(activeTrace);
207
+ this.emit({ type: "trace:start", trace: traceData });
208
+ return span;
209
+ }
210
+ startSpan(name, layer) {
211
+ const parentSpan = this.findActiveSpan();
212
+ if (!parentSpan) {
213
+ return this.startTrace(name, layer);
214
+ }
215
+ const traceId = parentSpan.traceId;
216
+ const activeTrace = this.activeTraces.get(traceId);
217
+ if (activeTrace)
218
+ activeTrace.activeSpanCount++;
219
+ const span = this.createSpan(traceId, parentSpan.id, name, layer);
220
+ const stack = this.spanStacks.get(traceId);
221
+ if (stack)
222
+ stack.push(span);
223
+ return span;
224
+ }
225
+ activeSpan() {
226
+ return this.findActiveSpan();
227
+ }
228
+ withSpan(name, layer, fn) {
229
+ const span = this.startSpan(name, layer);
230
+ try {
231
+ const result = fn(span);
232
+ if (result instanceof Promise) {
233
+ return result.then((v) => {
234
+ span.end();
235
+ return v;
236
+ }).catch((err) => {
237
+ span.error(err);
238
+ span.end();
239
+ throw err;
240
+ });
241
+ }
242
+ span.end();
243
+ return result;
244
+ } catch (err) {
245
+ span.error(err);
246
+ span.end();
247
+ throw err;
248
+ }
249
+ }
250
+ getTraces() {
251
+ return this.traces.toArray();
252
+ }
253
+ getTrace(id) {
254
+ return this.traces.get(id);
255
+ }
256
+ subscribe(cb) {
257
+ this.subscribers.add(cb);
258
+ return () => {
259
+ this.subscribers.delete(cb);
260
+ };
261
+ }
262
+ mergeClientSpans(traceId, spans) {
263
+ const trace = this.traces.get(traceId);
264
+ if (!trace)
265
+ return;
266
+ for (const span of spans) {
267
+ if (trace.spans.size >= this.config.maxSpansPerTrace)
268
+ break;
269
+ trace.spans.set(span.id, span);
270
+ }
271
+ for (const span of spans) {
272
+ if (span.endTime > trace.endTime) {
273
+ trace.endTime = span.endTime;
274
+ trace.duration = trace.endTime - trace.startTime;
275
+ }
276
+ }
277
+ }
278
+ findActiveSpan() {
279
+ let latest = null;
280
+ for (const stack of this.spanStacks.values()) {
281
+ const top = stack[stack.length - 1];
282
+ if (top)
283
+ latest = top;
284
+ }
285
+ return latest;
286
+ }
287
+ createSpan(traceId, parentId, name, layer) {
288
+ const childFactory = (childName, childLayer, pId) => {
289
+ const activeTrace = this.activeTraces.get(traceId);
290
+ if (activeTrace)
291
+ activeTrace.activeSpanCount++;
292
+ const child = this.createSpan(traceId, pId, childName, childLayer);
293
+ const stack = this.spanStacks.get(traceId);
294
+ if (stack)
295
+ stack.push(child);
296
+ return child;
297
+ };
298
+ const onEnd = (span2) => {
299
+ const stack = this.spanStacks.get(traceId);
300
+ if (stack) {
301
+ const idx = stack.indexOf(span2);
302
+ if (idx !== -1)
303
+ stack.splice(idx, 1);
304
+ }
305
+ const spanData = span2.toData();
306
+ const activeTrace = this.activeTraces.get(traceId);
307
+ if (activeTrace) {
308
+ if (activeTrace.spans.size < this.config.maxSpansPerTrace) {
309
+ activeTrace.spans.set(spanData.id, spanData);
310
+ }
311
+ activeTrace.activeSpanCount--;
312
+ this.emit({ type: "span:end", span: spanData });
313
+ if (activeTrace.activeSpanCount <= 0) {
314
+ this.finalizeTrace(activeTrace);
315
+ }
316
+ }
317
+ };
318
+ const onEvent = (spanId, event) => {
319
+ this.emit({ type: "span:event", spanId, event });
320
+ };
321
+ const span = new SpanImpl(traceId, parentId, name, layer, onEnd, childFactory, onEvent);
322
+ this.emit({ type: "span:start", span: span.toData() });
323
+ return span;
324
+ }
325
+ finalizeTrace(active) {
326
+ this.activeTraces.delete(active.id);
327
+ this.spanStacks.delete(active.id);
328
+ let endTime = active.startTime;
329
+ for (const span of active.spans.values()) {
330
+ if (span.endTime > endTime)
331
+ endTime = span.endTime;
332
+ }
333
+ const duration = endTime - active.startTime;
334
+ if (this.config.minDuration > 0 && duration < this.config.minDuration) {
335
+ return;
336
+ }
337
+ const traceData = {
338
+ id: active.id,
339
+ rootSpanId: active.rootSpanId,
340
+ startTime: active.startTime,
341
+ endTime,
342
+ duration,
343
+ spans: active.spans,
344
+ metadata: active.metadata
345
+ };
346
+ this.traces.push(traceData);
347
+ this.emit({ type: "trace:end", trace: traceData });
348
+ }
349
+ buildTraceData(active) {
350
+ return {
351
+ id: active.id,
352
+ rootSpanId: active.rootSpanId,
353
+ startTime: active.startTime,
354
+ endTime: 0,
355
+ duration: 0,
356
+ spans: active.spans,
357
+ metadata: active.metadata
358
+ };
359
+ }
360
+ emit(event) {
361
+ for (const cb of this.subscribers) {
362
+ try {
363
+ cb(event);
364
+ } catch {}
365
+ }
366
+ }
367
+ }
368
+ function createTracer(config) {
369
+ return new TracerImpl(config);
370
+ }
371
+ var init_tracer = () => {};
372
+
373
+ // ../trace/src/context.ts
374
+ function setTracer(context, tracer) {
375
+ context.set(TRACER_KEY, tracer);
376
+ }
377
+ function getTracer(context) {
378
+ return context.get(TRACER_KEY);
379
+ }
380
+ function setActiveSpan(context, span) {
381
+ context.set(ACTIVE_SPAN_KEY, span);
382
+ }
383
+ function getActiveSpan(context) {
384
+ return context.get(ACTIVE_SPAN_KEY);
385
+ }
386
+ var TRACER_KEY = "__ereo_tracer", ACTIVE_SPAN_KEY = "__ereo_active_span";
387
+
388
+ // ../trace/src/noop.ts
389
+ class NoopTracer {
390
+ startTrace() {
391
+ return NOOP_SPAN;
392
+ }
393
+ startSpan() {
394
+ return NOOP_SPAN;
395
+ }
396
+ activeSpan() {
397
+ return null;
398
+ }
399
+ withSpan(_name, _layer, fn) {
400
+ return fn(NOOP_SPAN);
401
+ }
402
+ getTraces() {
403
+ return [];
404
+ }
405
+ getTrace() {
406
+ return;
407
+ }
408
+ subscribe() {
409
+ return () => {};
410
+ }
411
+ mergeClientSpans() {}
412
+ }
413
+ var NOOP_SPAN, noopTracer, noopSpan;
414
+ var init_noop = __esm(() => {
415
+ NOOP_SPAN = {
416
+ id: "",
417
+ traceId: "",
418
+ setAttribute() {},
419
+ event() {},
420
+ end() {},
421
+ error() {},
422
+ child() {
423
+ return NOOP_SPAN;
424
+ }
425
+ };
426
+ noopTracer = new NoopTracer;
427
+ noopSpan = NOOP_SPAN;
428
+ });
429
+
430
+ // ../trace/src/cli-reporter.ts
431
+ function durationColor(ms) {
432
+ if (ms < 50)
433
+ return GREEN;
434
+ if (ms < 200)
435
+ return YELLOW;
436
+ return RED;
437
+ }
438
+ function formatDuration(ms) {
439
+ if (ms < 1)
440
+ return `${(ms * 1000).toFixed(0)}us`;
441
+ if (ms < 1000)
442
+ return `${ms.toFixed(1)}ms`;
443
+ return `${(ms / 1000).toFixed(2)}s`;
444
+ }
445
+ function statusColor(code) {
446
+ if (code < 300)
447
+ return GREEN;
448
+ if (code < 400)
449
+ return CYAN;
450
+ if (code < 500)
451
+ return YELLOW;
452
+ return RED;
453
+ }
454
+ function layerColor(layer) {
455
+ switch (layer) {
456
+ case "request":
457
+ return WHITE;
458
+ case "routing":
459
+ return CYAN;
460
+ case "data":
461
+ return BLUE;
462
+ case "database":
463
+ return MAGENTA;
464
+ case "auth":
465
+ return YELLOW;
466
+ case "rpc":
467
+ return CYAN;
468
+ case "forms":
469
+ return GREEN;
470
+ case "islands":
471
+ return MAGENTA;
472
+ case "build":
473
+ return BLUE;
474
+ case "errors":
475
+ return RED;
476
+ case "signals":
477
+ return GREEN;
478
+ case "custom":
479
+ return GRAY;
480
+ }
481
+ }
482
+ function spanSummary(span) {
483
+ const parts = [];
484
+ if (span.attributes["route.pattern"]) {
485
+ parts.push(`matched ${span.attributes["route.pattern"]}`);
486
+ }
487
+ if (span.attributes["cache.hit"] === true) {
488
+ parts.push("cache hit");
489
+ } else if (span.attributes["db.system"]) {
490
+ parts.push(`${span.attributes["db.system"]}`);
491
+ }
492
+ if (span.attributes["auth.result"]) {
493
+ parts.push(`${span.attributes["auth.provider"] || "auth"} -> ${span.attributes["auth.result"]}`);
494
+ }
495
+ if (span.attributes["rpc.procedure"]) {
496
+ parts.push(`${span.attributes["rpc.type"] || "query"}`);
497
+ }
498
+ if (span.status === "error" && span.attributes["error.message"]) {
499
+ parts.push(`${span.attributes["error.message"]}`);
500
+ }
501
+ if (span.attributes["db.statement"]) {
502
+ const stmt = String(span.attributes["db.statement"]);
503
+ parts.push(stmt.length > 50 ? stmt.slice(0, 50) + "..." : stmt);
504
+ }
505
+ return parts.join(" ");
506
+ }
507
+ function printTrace(trace, options) {
508
+ const { verbose, layers, minDuration, colors } = options;
509
+ const c = (color, text) => colors ? `${color}${text}${RESET}` : text;
510
+ const rootSpan = trace.spans.get(trace.rootSpanId);
511
+ if (!rootSpan)
512
+ return;
513
+ const method = String(trace.metadata.method || "GET");
514
+ const pathname = String(trace.metadata.pathname || "/");
515
+ const statusCode = Number(trace.metadata.statusCode || rootSpan.attributes["http.status_code"] || 200);
516
+ const duration = trace.duration;
517
+ const methodStr = c(BOLD, method.padEnd(7));
518
+ const pathStr = pathname;
519
+ const statusStr = c(statusColor(statusCode), String(statusCode));
520
+ const durationStr = c(durationColor(duration), formatDuration(duration));
521
+ console.log(` ${methodStr}${pathStr} ${statusStr} ${durationStr}`);
522
+ const children = getChildSpans(trace, rootSpan.id);
523
+ printChildren(trace, children, " ", options);
524
+ console.log();
525
+ }
526
+ function getChildSpans(trace, parentId) {
527
+ const children = [];
528
+ for (const span of trace.spans.values()) {
529
+ if (span.parentId === parentId && span.id !== parentId) {
530
+ children.push(span);
531
+ }
532
+ }
533
+ return children.sort((a, b) => a.startTime - b.startTime);
534
+ }
535
+ function printChildren(trace, children, prefix, options) {
536
+ const { layers, minDuration, colors } = options;
537
+ const c = (color, text) => colors ? `${color}${text}${RESET}` : text;
538
+ const filtered = children.filter((span) => {
539
+ if (layers.length > 0 && !layers.includes(span.layer))
540
+ return false;
541
+ if (span.duration < minDuration)
542
+ return false;
543
+ return true;
544
+ });
545
+ for (let i = 0;i < filtered.length; i++) {
546
+ const span = filtered[i];
547
+ const isLast = i === filtered.length - 1;
548
+ const connector = isLast ? "`-- " : "|-- ";
549
+ const childPrefix = isLast ? " " : "| ";
550
+ const name = c(layerColor(span.layer), span.name.padEnd(16));
551
+ const dur = c(durationColor(span.duration), formatDuration(span.duration).padStart(8));
552
+ const summary = c(DIM, spanSummary(span));
553
+ console.log(`${prefix}${c(GRAY, connector)}${name}${dur} ${summary}`);
554
+ const grandchildren = getChildSpans(trace, span.id);
555
+ if (grandchildren.length > 0) {
556
+ printChildren(trace, grandchildren, prefix + childPrefix, options);
557
+ }
558
+ }
559
+ }
560
+ function createCLIReporter(tracer, options = {}) {
561
+ const resolved = {
562
+ verbose: options.verbose ?? false,
563
+ layers: options.layers ?? [],
564
+ minDuration: options.minDuration ?? 0,
565
+ colors: options.colors ?? true
566
+ };
567
+ return tracer.subscribe((event) => {
568
+ if (event.type === "trace:end") {
569
+ const rootSpan = event.trace.spans.get(event.trace.rootSpanId);
570
+ if (rootSpan && rootSpan.attributes["http.status_code"]) {
571
+ event.trace.metadata.statusCode = Number(rootSpan.attributes["http.status_code"]);
572
+ }
573
+ printTrace(event.trace, resolved);
574
+ }
575
+ });
576
+ }
577
+ var RESET = "\x1B[0m", BOLD = "\x1B[1m", DIM = "\x1B[2m", GREEN = "\x1B[32m", YELLOW = "\x1B[33m", RED = "\x1B[31m", CYAN = "\x1B[36m", MAGENTA = "\x1B[35m", BLUE = "\x1B[34m", GRAY = "\x1B[90m", WHITE = "\x1B[37m";
578
+
579
+ // ../trace/src/transport.ts
580
+ function serializeTrace(trace) {
581
+ const spans = {};
582
+ for (const [id, span] of trace.spans) {
583
+ spans[id] = span;
584
+ }
585
+ return {
586
+ id: trace.id,
587
+ rootSpanId: trace.rootSpanId,
588
+ startTime: trace.startTime,
589
+ endTime: trace.endTime,
590
+ duration: trace.duration,
591
+ spans,
592
+ metadata: trace.metadata
593
+ };
594
+ }
595
+ function deserializeTrace(data) {
596
+ const spans = new Map;
597
+ for (const [id, span] of Object.entries(data.spans)) {
598
+ spans.set(id, span);
599
+ }
600
+ return {
601
+ id: data.id,
602
+ rootSpanId: data.rootSpanId,
603
+ startTime: data.startTime,
604
+ endTime: data.endTime,
605
+ duration: data.duration,
606
+ spans,
607
+ metadata: data.metadata
608
+ };
609
+ }
610
+ function serializeEvent(event) {
611
+ if (event.type === "trace:start" || event.type === "trace:end") {
612
+ return { type: event.type, trace: serializeTrace(event.trace) };
613
+ }
614
+ return event;
615
+ }
616
+ function createTraceWebSocket(tracer) {
617
+ const clients = new Set;
618
+ let unsubscribe = null;
619
+ const startBroadcast = () => {
620
+ if (unsubscribe)
621
+ return;
622
+ unsubscribe = tracer.subscribe((event) => {
623
+ const serialized = JSON.stringify(serializeEvent(event));
624
+ for (const client of clients) {
625
+ try {
626
+ client.send(serialized);
627
+ } catch {
628
+ clients.delete(client);
629
+ }
630
+ }
631
+ });
632
+ };
633
+ return {
634
+ upgrade(request) {
635
+ const url = new URL(request.url);
636
+ return url.pathname === "/__ereo/trace-ws";
637
+ },
638
+ websocket: {
639
+ open(ws) {
640
+ clients.add(ws);
641
+ if (clients.size === 1)
642
+ startBroadcast();
643
+ const traces = tracer.getTraces();
644
+ const initial = JSON.stringify({
645
+ type: "initial",
646
+ traces: traces.map(serializeTrace)
647
+ });
648
+ ws.send(initial);
649
+ },
650
+ close(ws) {
651
+ clients.delete(ws);
652
+ if (clients.size === 0 && unsubscribe) {
653
+ unsubscribe();
654
+ unsubscribe = null;
655
+ }
656
+ },
657
+ message(_ws, message) {
658
+ try {
659
+ const data = JSON.parse(typeof message === "string" ? message : message.toString());
660
+ if (data.type === "client:spans" && data.traceId && Array.isArray(data.spans)) {
661
+ tracer.mergeClientSpans(data.traceId, data.spans);
662
+ }
663
+ } catch {}
664
+ }
665
+ },
666
+ close() {
667
+ if (unsubscribe) {
668
+ unsubscribe();
669
+ unsubscribe = null;
670
+ }
671
+ clients.clear();
672
+ }
673
+ };
674
+ }
675
+ function createTracesAPIHandler(tracer) {
676
+ return (request) => {
677
+ const url = new URL(request.url);
678
+ const traceId = url.searchParams.get("id");
679
+ if (traceId) {
680
+ const trace = tracer.getTrace(traceId);
681
+ if (!trace) {
682
+ return new Response(JSON.stringify({ error: "Trace not found" }), {
683
+ status: 404,
684
+ headers: { "Content-Type": "application/json" }
685
+ });
686
+ }
687
+ return new Response(JSON.stringify(serializeTrace(trace)), {
688
+ headers: { "Content-Type": "application/json" }
689
+ });
690
+ }
691
+ const traces = tracer.getTraces().map(serializeTrace);
692
+ return new Response(JSON.stringify({ traces }), {
693
+ headers: { "Content-Type": "application/json" }
694
+ });
695
+ };
696
+ }
697
+
698
+ // ../trace/src/collector.ts
699
+ class TraceCollector {
700
+ tracer;
701
+ constructor(tracer) {
702
+ this.tracer = tracer;
703
+ }
704
+ mergeClientSpans(traceId, clientSpans) {
705
+ this.tracer.mergeClientSpans(traceId, clientSpans);
706
+ }
707
+ getUnifiedTrace(traceId) {
708
+ return this.tracer.getTrace(traceId);
709
+ }
710
+ }
711
+ function createCollector(tracer) {
712
+ return new TraceCollector(tracer);
713
+ }
714
+
715
+ // ../trace/src/viewer.ts
716
+ function generateViewerHTML(traces) {
717
+ const serialized = traces.map(serializeTrace);
718
+ return `<!DOCTYPE html>
719
+ <html lang="en">
720
+ <head>
721
+ <meta charset="UTF-8">
722
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
723
+ <title>Ereo Trace Viewer</title>
724
+ <style>
725
+ * { margin: 0; padding: 0; box-sizing: border-box; }
726
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace; background: #0d1117; color: #c9d1d9; }
727
+ .header { padding: 16px 24px; border-bottom: 1px solid #21262d; display: flex; align-items: center; gap: 12px; }
728
+ .header h1 { font-size: 16px; font-weight: 600; }
729
+ .header .badge { font-size: 12px; background: #1f6feb; color: white; padding: 2px 8px; border-radius: 10px; }
730
+ .container { display: flex; height: calc(100vh - 53px); }
731
+ .trace-list { width: 380px; border-right: 1px solid #21262d; overflow-y: auto; }
732
+ .trace-item { padding: 10px 16px; border-bottom: 1px solid #21262d; cursor: pointer; font-size: 13px; }
733
+ .trace-item:hover { background: #161b22; }
734
+ .trace-item.active { background: #1c2128; border-left: 3px solid #1f6feb; }
735
+ .trace-item .method { font-weight: 600; width: 50px; display: inline-block; }
736
+ .trace-item .path { color: #8b949e; }
737
+ .trace-item .status { float: right; font-size: 12px; }
738
+ .trace-item .duration { float: right; margin-right: 12px; font-size: 12px; }
739
+ .status-2xx { color: #3fb950; }
740
+ .status-3xx { color: #58a6ff; }
741
+ .status-4xx { color: #d29922; }
742
+ .status-5xx { color: #f85149; }
743
+ .dur-fast { color: #3fb950; }
744
+ .dur-medium { color: #d29922; }
745
+ .dur-slow { color: #f85149; }
746
+ .detail-panel { flex: 1; overflow-y: auto; padding: 16px; }
747
+ .waterfall { padding: 8px 0; }
748
+ .span-row { display: flex; align-items: center; padding: 4px 8px; font-size: 12px; border-radius: 4px; }
749
+ .span-row:hover { background: #161b22; }
750
+ .span-name { width: 200px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
751
+ .span-bar-container { flex: 1; height: 20px; position: relative; margin: 0 12px; }
752
+ .span-bar { position: absolute; height: 16px; border-radius: 3px; top: 2px; min-width: 2px; }
753
+ .span-duration { width: 70px; text-align: right; flex-shrink: 0; font-size: 11px; color: #8b949e; }
754
+ .layer-request { background: #8b949e; }
755
+ .layer-routing { background: #58a6ff; }
756
+ .layer-data { background: #1f6feb; }
757
+ .layer-database { background: #bc8cff; }
758
+ .layer-auth { background: #d29922; }
759
+ .layer-rpc { background: #58a6ff; }
760
+ .layer-forms { background: #3fb950; }
761
+ .layer-islands { background: #bc8cff; }
762
+ .layer-build { background: #1f6feb; }
763
+ .layer-errors { background: #f85149; }
764
+ .layer-signals { background: #3fb950; }
765
+ .layer-custom { background: #8b949e; }
766
+ .span-detail { margin-top: 16px; padding: 16px; background: #161b22; border-radius: 8px; font-size: 12px; }
767
+ .span-detail h3 { font-size: 14px; margin-bottom: 8px; }
768
+ .span-detail table { width: 100%; border-collapse: collapse; }
769
+ .span-detail td { padding: 4px 8px; border-bottom: 1px solid #21262d; }
770
+ .span-detail td:first-child { color: #8b949e; width: 150px; }
771
+ .empty { text-align: center; padding: 48px; color: #8b949e; }
772
+ .filters { padding: 8px 16px; border-bottom: 1px solid #21262d; display: flex; gap: 8px; font-size: 12px; }
773
+ .filters select, .filters input { background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; padding: 4px 8px; border-radius: 4px; font-size: 12px; }
774
+ </style>
775
+ </head>
776
+ <body>
777
+ <div class="header">
778
+ <h1>Ereo Trace Viewer</h1>
779
+ <span class="badge">${serialized.length} traces</span>
780
+ </div>
781
+ <div class="filters">
782
+ <select id="filter-method"><option value="">All Methods</option><option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option></select>
783
+ <input id="filter-path" type="text" placeholder="Filter by path..." />
784
+ <select id="filter-status"><option value="">All Status</option><option value="2xx">2xx</option><option value="4xx">4xx</option><option value="5xx">5xx</option></select>
785
+ </div>
786
+ <div class="container">
787
+ <div class="trace-list" id="trace-list"></div>
788
+ <div class="detail-panel" id="detail-panel"><div class="empty">Select a trace to view details</div></div>
789
+ </div>
790
+ <script>
791
+ const TRACES = ${JSON.stringify(serialized).replace(/<\//g, "<\\/")};
792
+ let selectedTraceId = null;
793
+ let selectedSpanId = null;
794
+
795
+ function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
796
+ function durClass(ms) { return ms < 50 ? 'dur-fast' : ms < 200 ? 'dur-medium' : 'dur-slow'; }
797
+ function statusClass(code) {
798
+ if (code < 300) return 'status-2xx';
799
+ if (code < 400) return 'status-3xx';
800
+ if (code < 500) return 'status-4xx';
801
+ return 'status-5xx';
802
+ }
803
+ function fmtDur(ms) { return ms < 1 ? (ms*1000).toFixed(0)+'us' : ms < 1000 ? ms.toFixed(1)+'ms' : (ms/1000).toFixed(2)+'s'; }
804
+
805
+ function renderTraceList(traces) {
806
+ const el = document.getElementById('trace-list');
807
+ if (!traces.length) { el.innerHTML = '<div class="empty">No traces recorded</div>'; return; }
808
+ el.innerHTML = traces.map(t => {
809
+ const s = t.metadata.statusCode || 200;
810
+ const d = t.duration;
811
+ return '<div class="trace-item'+(t.id===selectedTraceId?' active':'')+'" data-id="'+esc(t.id)+'">' +
812
+ '<span class="method">'+esc(t.metadata.method||'GET')+'</span>' +
813
+ '<span class="path">'+esc(t.metadata.pathname||'/')+'</span>' +
814
+ '<span class="status '+statusClass(s)+'">'+s+'</span>' +
815
+ '<span class="duration '+durClass(d)+'">'+fmtDur(d)+'</span>' +
816
+ '</div>';
817
+ }).join('');
818
+ el.querySelectorAll('.trace-item').forEach(item => {
819
+ item.onclick = () => { selectedTraceId = item.dataset.id; selectedSpanId = null; render(); };
820
+ });
821
+ }
822
+
823
+ function flattenSpans(trace) {
824
+ const spans = Object.values(trace.spans);
825
+ const root = spans.find(s => s.id === trace.rootSpanId);
826
+ if (!root) return spans;
827
+ const result = [];
828
+ function walk(span, depth) {
829
+ result.push({ ...span, depth });
830
+ const children = spans.filter(s => s.parentId === span.id).sort((a,b) => a.startTime - b.startTime);
831
+ children.forEach(c => walk(c, depth+1));
832
+ }
833
+ walk(root, 0);
834
+ return result;
835
+ }
836
+
837
+ function renderDetail(trace) {
838
+ const panel = document.getElementById('detail-panel');
839
+ if (!trace) { panel.innerHTML = '<div class="empty">Select a trace to view details</div>'; return; }
840
+ const spans = flattenSpans(trace);
841
+ const minTime = trace.startTime;
842
+ const totalDur = trace.duration || 1;
843
+ let html = '<div class="waterfall">';
844
+ spans.forEach(s => {
845
+ const left = ((s.startTime - minTime) / totalDur * 100).toFixed(2);
846
+ const width = Math.max(0.5, (s.duration / totalDur * 100));
847
+ const indent = s.depth * 16;
848
+ html += '<div class="span-row'+(s.id===selectedSpanId?' active':'')+'" data-span="'+esc(s.id)+'">' +
849
+ '<div class="span-name" style="padding-left:'+indent+'px">'+esc(s.name)+'</div>' +
850
+ '<div class="span-bar-container"><div class="span-bar layer-'+esc(s.layer)+'" style="left:'+left+'%;width:'+width.toFixed(2)+'%"></div></div>' +
851
+ '<div class="span-duration '+durClass(s.duration)+'">'+fmtDur(s.duration)+'</div>' +
852
+ '</div>';
853
+ });
854
+ html += '</div>';
855
+ if (selectedSpanId) {
856
+ const span = trace.spans[selectedSpanId];
857
+ if (span) {
858
+ html += '<div class="span-detail"><h3>'+esc(span.name)+' ('+esc(span.layer)+')</h3><table>';
859
+ html += '<tr><td>Status</td><td>'+esc(span.status)+'</td></tr>';
860
+ html += '<tr><td>Duration</td><td>'+fmtDur(span.duration)+'</td></tr>';
861
+ Object.entries(span.attributes||{}).forEach(([k,v]) => { html += '<tr><td>'+esc(k)+'</td><td>'+esc(v)+'</td></tr>'; });
862
+ if (span.events && span.events.length) {
863
+ html += '<tr><td>Events</td><td>'+span.events.map(e=>esc(e.name)).join(', ')+'</td></tr>';
864
+ }
865
+ html += '</table></div>';
866
+ }
867
+ }
868
+ panel.innerHTML = html;
869
+ panel.querySelectorAll('.span-row').forEach(row => {
870
+ row.onclick = () => { selectedSpanId = row.dataset.span; renderDetail(trace); };
871
+ });
872
+ }
873
+
874
+ function getFiltered() {
875
+ let t = TRACES;
876
+ const method = document.getElementById('filter-method').value;
877
+ const path = document.getElementById('filter-path').value;
878
+ const status = document.getElementById('filter-status').value;
879
+ if (method) t = t.filter(x => x.metadata.method === method);
880
+ if (path) t = t.filter(x => (x.metadata.pathname||'').includes(path));
881
+ if (status) {
882
+ const base = parseInt(status);
883
+ t = t.filter(x => { const s = x.metadata.statusCode||200; return s >= base && s < base+100; });
884
+ }
885
+ return t.reverse();
886
+ }
887
+
888
+ function render() {
889
+ const filtered = getFiltered();
890
+ renderTraceList(filtered);
891
+ const trace = selectedTraceId ? TRACES.find(t => t.id === selectedTraceId) : null;
892
+ renderDetail(trace);
893
+ }
894
+
895
+ document.getElementById('filter-method').onchange = render;
896
+ document.getElementById('filter-path').oninput = render;
897
+ document.getElementById('filter-status').onchange = render;
898
+ render();
899
+
900
+ // Live updates via WebSocket
901
+ if (typeof WebSocket !== 'undefined') {
902
+ try {
903
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
904
+ const ws = new WebSocket(protocol + '//' + location.host + '/__ereo/trace-ws');
905
+ ws.onmessage = (e) => {
906
+ try {
907
+ const data = JSON.parse(e.data);
908
+ if (data.type === 'trace:end') {
909
+ TRACES.push(data.trace);
910
+ render();
911
+ }
912
+ } catch {}
913
+ };
914
+ } catch {}
915
+ }
916
+ </script>
917
+ </body>
918
+ </html>`;
919
+ }
920
+ function createViewerHandler(tracer) {
921
+ return () => {
922
+ const traces = tracer.getTraces();
923
+ const html = generateViewerHTML(traces);
924
+ return new Response(html, {
925
+ headers: { "Content-Type": "text/html; charset=utf-8" }
926
+ });
927
+ };
928
+ }
929
+ function exportTracesHTML(tracer) {
930
+ return generateViewerHTML(tracer.getTraces());
931
+ }
932
+ var init_viewer = () => {};
933
+
934
+ // ../trace/src/instrumentors/request.ts
935
+ function traceMiddleware(tracer, options = {}) {
936
+ const { exclude = ["/_ereo/", "/__ereo/", "/favicon.ico"], recordHeaders = false } = options;
937
+ return async (request, context, next) => {
938
+ const url = new URL(request.url);
939
+ for (const pattern of exclude) {
940
+ if (url.pathname.startsWith(pattern)) {
941
+ return next();
942
+ }
943
+ }
944
+ const rootSpan = tracer.startTrace(`${request.method} ${url.pathname}`, "request", {
945
+ origin: "server",
946
+ method: request.method,
947
+ pathname: url.pathname
948
+ });
949
+ rootSpan.setAttribute("http.method", request.method);
950
+ rootSpan.setAttribute("http.pathname", url.pathname);
951
+ rootSpan.setAttribute("http.search", url.search);
952
+ if (recordHeaders) {
953
+ const headers2 = [];
954
+ request.headers.forEach((value, key) => {
955
+ if (key.toLowerCase() !== "cookie" && key.toLowerCase() !== "authorization") {
956
+ headers2.push(`${key}: ${value}`);
957
+ }
958
+ });
959
+ rootSpan.setAttribute("http.request_headers", headers2.join("; "));
960
+ }
961
+ setTracer(context, tracer);
962
+ setActiveSpan(context, rootSpan);
963
+ const clientTraceId = request.headers.get("X-Ereo-Trace-Id");
964
+ if (clientTraceId) {
965
+ rootSpan.setAttribute("trace.client_id", clientTraceId);
966
+ }
967
+ let response;
968
+ try {
969
+ response = await next();
970
+ } catch (err) {
971
+ rootSpan.error(err);
972
+ rootSpan.setAttribute("http.status_code", 500);
973
+ rootSpan.end();
974
+ throw err;
975
+ }
976
+ rootSpan.setAttribute("http.status_code", response.status);
977
+ if (response.status >= 400) {
978
+ rootSpan.setAttribute("http.error", true);
979
+ }
980
+ rootSpan.end();
981
+ const headers = new Headers(response.headers);
982
+ headers.set("X-Ereo-Trace-Id", rootSpan.traceId);
983
+ return new Response(response.body, {
984
+ status: response.status,
985
+ statusText: response.statusText,
986
+ headers
987
+ });
988
+ };
989
+ }
990
+ var init_request = () => {};
991
+
992
+ // ../trace/src/instrumentors/routing.ts
993
+ function traceRouteMatch(parentSpan, matchFn) {
994
+ const span = parentSpan.child("route.match", "routing");
995
+ try {
996
+ const result = matchFn();
997
+ if (result && typeof result === "object" && "route" in result) {
998
+ const match = result;
999
+ span.setAttribute("route.pattern", match.route.path);
1000
+ span.setAttribute("route.id", match.route.id);
1001
+ span.setAttribute("route.params", JSON.stringify(match.params));
1002
+ if (match.layouts) {
1003
+ span.setAttribute("route.layouts", match.layouts.map((l) => l.id).join(" > "));
1004
+ }
1005
+ } else {
1006
+ span.setAttribute("route.matched", false);
1007
+ span.event("404", { pathname: "unknown" });
1008
+ }
1009
+ span.end();
1010
+ return result;
1011
+ } catch (err) {
1012
+ span.error(err);
1013
+ span.end();
1014
+ throw err;
1015
+ }
1016
+ }
1017
+ function recordRouteMatch(parentSpan, match) {
1018
+ if (match) {
1019
+ parentSpan.setAttribute("route.pattern", match.route.path);
1020
+ parentSpan.setAttribute("route.id", match.route.id);
1021
+ parentSpan.event("route.matched", { pattern: match.route.path });
1022
+ } else {
1023
+ parentSpan.event("route.miss");
1024
+ }
1025
+ }
1026
+
1027
+ // ../trace/src/instrumentors/data.ts
1028
+ function traceLoader(parentSpan, loaderKey, loaderFn) {
1029
+ const span = parentSpan.child(`loader:${loaderKey}`, "data");
1030
+ span.setAttribute("loader.key", loaderKey);
1031
+ try {
1032
+ const result = loaderFn();
1033
+ if (result instanceof Promise) {
1034
+ return result.then((v) => {
1035
+ span.end();
1036
+ return v;
1037
+ }).catch((err) => {
1038
+ span.error(err);
1039
+ span.end();
1040
+ throw err;
1041
+ });
1042
+ }
1043
+ span.end();
1044
+ return result;
1045
+ } catch (err) {
1046
+ span.error(err);
1047
+ span.end();
1048
+ throw err;
1049
+ }
1050
+ }
1051
+ function recordLoaderMetrics(parentSpan, metrics) {
1052
+ for (const metric of metrics) {
1053
+ const span = parentSpan.child(`loader:${metric.key}`, "data");
1054
+ span.setAttribute("loader.key", metric.key);
1055
+ span.setAttribute("loader.duration_ms", metric.duration);
1056
+ if (metric.cacheHit !== undefined) {
1057
+ span.setAttribute("cache.hit", metric.cacheHit);
1058
+ }
1059
+ if (metric.source) {
1060
+ span.setAttribute("loader.source", metric.source);
1061
+ }
1062
+ if (metric.waitingFor && metric.waitingFor.length > 0) {
1063
+ span.setAttribute("loader.waiting_for", metric.waitingFor.join(", "));
1064
+ }
1065
+ if (metric.error) {
1066
+ span.setAttribute("error.message", metric.error);
1067
+ }
1068
+ span.end();
1069
+ }
1070
+ }
1071
+ function traceCacheOperation(parentSpan, operation, key, hit) {
1072
+ parentSpan.event(`cache.${operation}`, {
1073
+ key: key.length > 100 ? key.slice(0, 100) + "..." : key,
1074
+ ...hit !== undefined ? { hit } : {}
1075
+ });
1076
+ }
1077
+
1078
+ // ../trace/src/instrumentors/forms.ts
1079
+ function traceFormSubmit(parentSpan, formName, submitFn, attrs) {
1080
+ const span = parentSpan.child(`form:${formName}`, "forms");
1081
+ span.setAttribute("form.name", formName);
1082
+ if (attrs?.fieldCount)
1083
+ span.setAttribute("form.field_count", attrs.fieldCount);
1084
+ try {
1085
+ const result = submitFn();
1086
+ if (result instanceof Promise) {
1087
+ return result.then((v) => {
1088
+ span.end();
1089
+ return v;
1090
+ }).catch((err) => {
1091
+ span.error(err);
1092
+ span.end();
1093
+ throw err;
1094
+ });
1095
+ }
1096
+ span.end();
1097
+ return result;
1098
+ } catch (err) {
1099
+ span.error(err);
1100
+ span.end();
1101
+ throw err;
1102
+ }
1103
+ }
1104
+ function recordFormValidation(parentSpan, formName, attrs) {
1105
+ parentSpan.event("form.validation", {
1106
+ form: formName,
1107
+ error_count: attrs.errorCount,
1108
+ duration_ms: attrs.validationMs,
1109
+ ...attrs.errorSources ? { sources: attrs.errorSources.join(", ") } : {}
1110
+ });
1111
+ }
1112
+
1113
+ // ../trace/src/instrumentors/signals.ts
1114
+ function recordSignalUpdate(span, name, attrs) {
1115
+ span.event("signal.update", {
1116
+ name,
1117
+ ...attrs?.subscriberCount !== undefined ? { subscribers: attrs.subscriberCount } : {},
1118
+ ...attrs?.batched !== undefined ? { batched: attrs.batched } : {}
1119
+ });
1120
+ }
1121
+ function recordSignalBatch(span, signalNames, attrs) {
1122
+ span.event("signal.batch", {
1123
+ count: signalNames.length,
1124
+ signals: signalNames.join(", "),
1125
+ ...attrs?.subscriberCount !== undefined ? { total_subscribers: attrs.subscriberCount } : {}
1126
+ });
1127
+ }
1128
+
1129
+ // ../trace/src/instrumentors/rpc.ts
1130
+ function traceRPCCall(parentSpan, procedure, type, callFn) {
1131
+ const span = parentSpan.child(`rpc:${procedure}`, "rpc");
1132
+ span.setAttribute("rpc.procedure", procedure);
1133
+ span.setAttribute("rpc.type", type);
1134
+ try {
1135
+ const result = callFn();
1136
+ if (result instanceof Promise) {
1137
+ return result.then((v) => {
1138
+ span.end();
1139
+ return v;
1140
+ }).catch((err) => {
1141
+ span.error(err);
1142
+ span.end();
1143
+ throw err;
1144
+ });
1145
+ }
1146
+ span.end();
1147
+ return result;
1148
+ } catch (err) {
1149
+ span.error(err);
1150
+ span.end();
1151
+ throw err;
1152
+ }
1153
+ }
1154
+ function recordRPCValidation(parentSpan, procedure, validationMs, valid) {
1155
+ parentSpan.event("rpc.validation", {
1156
+ procedure,
1157
+ duration_ms: validationMs,
1158
+ valid
1159
+ });
1160
+ }
1161
+
1162
+ // ../trace/src/instrumentors/database.ts
1163
+ function tracedAdapter(adapter, getSpan) {
1164
+ const methodsToTrace = ["query", "execute", "get", "all", "run"];
1165
+ return new Proxy(adapter, {
1166
+ get(target, prop, receiver) {
1167
+ const value = Reflect.get(target, prop, receiver);
1168
+ if (typeof prop !== "string" || typeof value !== "function" || !methodsToTrace.includes(prop)) {
1169
+ return value;
1170
+ }
1171
+ return async function tracedMethod(sql, params) {
1172
+ const parentSpan = getSpan();
1173
+ if (!parentSpan) {
1174
+ return value.call(target, sql, params);
1175
+ }
1176
+ const span = parentSpan.child(`db.${prop}`, "database");
1177
+ span.setAttribute("db.operation", prop);
1178
+ span.setAttribute("db.statement", typeof sql === "string" ? sql.slice(0, 200) : "");
1179
+ if (params) {
1180
+ span.setAttribute("db.param_count", params.length);
1181
+ }
1182
+ try {
1183
+ const result = await value.call(target, sql, params);
1184
+ if (Array.isArray(result)) {
1185
+ span.setAttribute("db.row_count", result.length);
1186
+ }
1187
+ span.end();
1188
+ return result;
1189
+ } catch (err) {
1190
+ span.error(err);
1191
+ span.end();
1192
+ throw err;
1193
+ }
1194
+ };
1195
+ }
1196
+ });
1197
+ }
1198
+ function traceQuery(parentSpan, operation, sql, queryFn) {
1199
+ const span = parentSpan.child(`db.${operation}`, "database");
1200
+ span.setAttribute("db.operation", operation);
1201
+ span.setAttribute("db.statement", sql.slice(0, 200));
1202
+ return queryFn().then((result) => {
1203
+ if (Array.isArray(result)) {
1204
+ span.setAttribute("db.row_count", result.length);
1205
+ }
1206
+ span.end();
1207
+ return result;
1208
+ }).catch((err) => {
1209
+ span.error(err);
1210
+ span.end();
1211
+ throw err;
1212
+ });
1213
+ }
1214
+
1215
+ // ../trace/src/instrumentors/auth.ts
1216
+ function traceAuthCheck(parentSpan, operation, checkFn, attrs) {
1217
+ const span = parentSpan.child(`auth:${operation}`, "auth");
1218
+ span.setAttribute("auth.operation", operation);
1219
+ if (attrs?.provider)
1220
+ span.setAttribute("auth.provider", attrs.provider);
1221
+ if (attrs?.roles)
1222
+ span.setAttribute("auth.roles", attrs.roles.join(", "));
1223
+ try {
1224
+ const result = checkFn();
1225
+ if (result instanceof Promise) {
1226
+ return result.then((v) => {
1227
+ span.setAttribute("auth.result", "ok");
1228
+ span.end();
1229
+ return v;
1230
+ }).catch((err) => {
1231
+ span.setAttribute("auth.result", "denied");
1232
+ if (err instanceof Response) {
1233
+ const location = err.headers.get("Location");
1234
+ if (location)
1235
+ span.setAttribute("auth.redirect", location);
1236
+ }
1237
+ span.error(err);
1238
+ span.end();
1239
+ throw err;
1240
+ });
1241
+ }
1242
+ span.setAttribute("auth.result", "ok");
1243
+ span.end();
1244
+ return result;
1245
+ } catch (err) {
1246
+ span.setAttribute("auth.result", "denied");
1247
+ span.error(err);
1248
+ span.end();
1249
+ throw err;
1250
+ }
1251
+ }
1252
+
1253
+ // ../trace/src/instrumentors/islands.ts
1254
+ function traceHydration(parentSpan, componentName, strategy, hydrateFn, attrs) {
1255
+ const span = parentSpan.child(`hydrate:${componentName}`, "islands");
1256
+ span.setAttribute("island.component", componentName);
1257
+ span.setAttribute("island.strategy", strategy);
1258
+ if (attrs?.propsSize)
1259
+ span.setAttribute("island.props_size", attrs.propsSize);
1260
+ try {
1261
+ const result = hydrateFn();
1262
+ if (result instanceof Promise) {
1263
+ return result.then((v) => {
1264
+ span.end();
1265
+ return v;
1266
+ }).catch((err) => {
1267
+ span.error(err);
1268
+ span.end();
1269
+ throw err;
1270
+ });
1271
+ }
1272
+ span.end();
1273
+ return result;
1274
+ } catch (err) {
1275
+ span.error(err);
1276
+ span.end();
1277
+ throw err;
1278
+ }
1279
+ }
1280
+ function recordHydration(parentSpan, componentName, strategy, durationMs) {
1281
+ parentSpan.event("island.hydrated", {
1282
+ component: componentName,
1283
+ strategy,
1284
+ duration_ms: durationMs
1285
+ });
1286
+ }
1287
+
1288
+ // ../trace/src/instrumentors/build.ts
1289
+ function traceBuildStage(parentSpan, stageName, stageFn, attrs) {
1290
+ const span = parentSpan.child(`build:${stageName}`, "build");
1291
+ span.setAttribute("build.stage", stageName);
1292
+ if (attrs?.filesCount)
1293
+ span.setAttribute("build.files_count", attrs.filesCount);
1294
+ try {
1295
+ const result = stageFn();
1296
+ if (result instanceof Promise) {
1297
+ return result.then((v) => {
1298
+ span.end();
1299
+ return v;
1300
+ }).catch((err) => {
1301
+ span.error(err);
1302
+ span.end();
1303
+ throw err;
1304
+ });
1305
+ }
1306
+ span.end();
1307
+ return result;
1308
+ } catch (err) {
1309
+ span.error(err);
1310
+ span.end();
1311
+ throw err;
1312
+ }
1313
+ }
1314
+ function traceBuild(tracer, buildName = "production build") {
1315
+ return tracer.startTrace(buildName, "build", { origin: "server" });
1316
+ }
1317
+
1318
+ // ../trace/src/instrumentors/errors.ts
1319
+ function traceError(span, err, phase = "unknown") {
1320
+ span.error(err);
1321
+ span.setAttribute("error.phase", phase);
1322
+ if (err instanceof Error) {
1323
+ span.setAttribute("error.class", err.constructor.name);
1324
+ span.event("error", {
1325
+ message: err.message,
1326
+ class: err.constructor.name,
1327
+ phase
1328
+ });
1329
+ }
1330
+ }
1331
+ function withErrorCapture(span, phase, fn) {
1332
+ try {
1333
+ const result = fn();
1334
+ if (result instanceof Promise) {
1335
+ return result.catch((err) => {
1336
+ traceError(span, err, phase);
1337
+ throw err;
1338
+ });
1339
+ }
1340
+ return result;
1341
+ } catch (err) {
1342
+ traceError(span, err, phase);
1343
+ throw err;
1344
+ }
1345
+ }
1346
+
1347
+ // ../trace/src/instrumentors/index.ts
1348
+ var init_instrumentors = __esm(() => {
1349
+ init_request();
1350
+ });
1351
+
1352
+ // ../trace/src/index.ts
1353
+ var exports_src = {};
1354
+ __export(exports_src, {
1355
+ withErrorCapture: () => withErrorCapture,
1356
+ tracedAdapter: () => tracedAdapter,
1357
+ traceRouteMatch: () => traceRouteMatch,
1358
+ traceRPCCall: () => traceRPCCall,
1359
+ traceQuery: () => traceQuery,
1360
+ traceMiddleware: () => traceMiddleware,
1361
+ traceLoader: () => traceLoader,
1362
+ traceHydration: () => traceHydration,
1363
+ traceFormSubmit: () => traceFormSubmit,
1364
+ traceError: () => traceError,
1365
+ traceCacheOperation: () => traceCacheOperation,
1366
+ traceBuildStage: () => traceBuildStage,
1367
+ traceBuild: () => traceBuild,
1368
+ traceAuthCheck: () => traceAuthCheck,
1369
+ setTracer: () => setTracer,
1370
+ setActiveSpan: () => setActiveSpan,
1371
+ serializeTrace: () => serializeTrace,
1372
+ recordSignalUpdate: () => recordSignalUpdate,
1373
+ recordSignalBatch: () => recordSignalBatch,
1374
+ recordRouteMatch: () => recordRouteMatch,
1375
+ recordRPCValidation: () => recordRPCValidation,
1376
+ recordLoaderMetrics: () => recordLoaderMetrics,
1377
+ recordHydration: () => recordHydration,
1378
+ recordFormValidation: () => recordFormValidation,
1379
+ noopTracer: () => noopTracer,
1380
+ noopSpan: () => noopSpan,
1381
+ getTracer: () => getTracer,
1382
+ getActiveSpan: () => getActiveSpan,
1383
+ generateViewerHTML: () => generateViewerHTML,
1384
+ generateTraceId: () => generateTraceId,
1385
+ generateSpanId: () => generateSpanId,
1386
+ exportTracesHTML: () => exportTracesHTML,
1387
+ deserializeTrace: () => deserializeTrace,
1388
+ createViewerHandler: () => createViewerHandler,
1389
+ createTracesAPIHandler: () => createTracesAPIHandler,
1390
+ createTracer: () => createTracer,
1391
+ createTraceWebSocket: () => createTraceWebSocket,
1392
+ createCollector: () => createCollector,
1393
+ createCLIReporter: () => createCLIReporter,
1394
+ TracerImpl: () => TracerImpl,
1395
+ TraceCollector: () => TraceCollector,
1396
+ SpanImpl: () => SpanImpl,
1397
+ RingBuffer: () => RingBuffer,
1398
+ NoopTracer: () => NoopTracer
1399
+ });
1400
+ var init_src = __esm(() => {
1401
+ init_tracer();
1402
+ init_noop();
1403
+ init_viewer();
1404
+ init_instrumentors();
1405
+ });
1406
+
15
1407
  // src/commands/build.ts
16
1408
  var exports_build = {};
17
1409
  __export(exports_build, {
@@ -138,12 +1530,30 @@ async function dev(options = {}) {
138
1530
  console.log(`\x1B[33m\u27F3\x1B[0m ${route.path} changed`);
139
1531
  hmr.jsUpdate(route.file);
140
1532
  });
1533
+ let traceConfig = undefined;
1534
+ if (options.trace) {
1535
+ try {
1536
+ const { createTracer: createTracer2, traceMiddleware: traceMiddleware2, createCLIReporter: createCLIReporter2, createViewerHandler: createViewerHandler2, createTracesAPIHandler: createTracesAPIHandler2 } = await Promise.resolve().then(() => (init_src(), exports_src));
1537
+ const tracer = createTracer2();
1538
+ const middleware = traceMiddleware2(tracer);
1539
+ const viewerHandler = createViewerHandler2(tracer);
1540
+ const tracesAPIHandler = createTracesAPIHandler2(tracer);
1541
+ createCLIReporter2(tracer);
1542
+ traceConfig = { tracer, middleware, viewerHandler, tracesAPIHandler };
1543
+ console.log(` \x1B[35m\u2B21\x1B[0m Tracing enabled \u2014 view at /__ereo/traces
1544
+ `);
1545
+ } catch {
1546
+ console.warn(` \x1B[33m\u26A0\x1B[0m @ereo/trace not installed, tracing disabled
1547
+ `);
1548
+ }
1549
+ }
141
1550
  const server = createServer({
142
1551
  port,
143
1552
  hostname,
144
1553
  development: true,
145
- logging: true,
146
- websocket: createHMRWebSocket(hmr)
1554
+ logging: !options.trace,
1555
+ websocket: createHMRWebSocket(hmr),
1556
+ trace: traceConfig
147
1557
  });
148
1558
  server.setApp(app);
149
1559
  server.setRouter(router);
@@ -2106,6 +3516,7 @@ function printHelp() {
2106
3516
  --port, -p Port number (default: 3000)
2107
3517
  --host, -h Host name (default: localhost)
2108
3518
  --open, -o Open browser
3519
+ --trace Enable request tracing
2109
3520
 
2110
3521
  \x1B[1mBuild Options:\x1B[0m
2111
3522
  --outDir Output directory (default: .ereo)
@@ -2192,7 +3603,8 @@ async function main() {
2192
3603
  const devOptions = {
2193
3604
  port: options.port ? parseInt(options.port, 10) : undefined,
2194
3605
  host: options.host || options.h,
2195
- open: !!(options.open || options.o)
3606
+ open: !!(options.open || options.o),
3607
+ trace: !!options.trace
2196
3608
  };
2197
3609
  await dev(devOptions);
2198
3610
  break;