effect-start 0.26.0 → 0.27.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 (58) hide show
  1. package/package.json +4 -2
  2. package/src/Entity.ts +6 -6
  3. package/src/FileRouterCodegen.ts +4 -4
  4. package/src/FileSystem.ts +4 -8
  5. package/src/RouteHook.ts +1 -1
  6. package/src/RouteSse.ts +3 -3
  7. package/src/SqlIntrospect.ts +2 -2
  8. package/src/Start.ts +102 -2
  9. package/src/Values.ts +11 -0
  10. package/src/bun/BunRoute.ts +1 -1
  11. package/src/bun/BunRuntime.ts +5 -5
  12. package/src/hyper/HyperHtml.ts +11 -7
  13. package/src/hyper/jsx.d.ts +1 -1
  14. package/src/lint/plugin.js +174 -4
  15. package/src/sql/SqlClient.ts +355 -0
  16. package/src/sql/bun/index.ts +117 -50
  17. package/src/sql/index.ts +1 -1
  18. package/src/sql/libsql/index.ts +91 -77
  19. package/src/sql/libsql/libsql.d.ts +4 -1
  20. package/src/sql/mssql/index.ts +141 -108
  21. package/src/sql/mssql/mssql.d.ts +1 -0
  22. package/src/testing/TestLogger.ts +4 -4
  23. package/src/x/tailwind/compile.ts +6 -14
  24. package/src/console/Console.ts +0 -42
  25. package/src/console/ConsoleErrors.ts +0 -213
  26. package/src/console/ConsoleLogger.ts +0 -56
  27. package/src/console/ConsoleMetrics.ts +0 -72
  28. package/src/console/ConsoleProcess.ts +0 -59
  29. package/src/console/ConsoleStore.ts +0 -187
  30. package/src/console/ConsoleTracer.ts +0 -107
  31. package/src/console/Simulation.ts +0 -814
  32. package/src/console/console.html +0 -340
  33. package/src/console/index.ts +0 -3
  34. package/src/console/routes/errors/route.tsx +0 -97
  35. package/src/console/routes/fiberDetail.tsx +0 -54
  36. package/src/console/routes/fibers/route.tsx +0 -45
  37. package/src/console/routes/git/route.tsx +0 -64
  38. package/src/console/routes/layout.tsx +0 -4
  39. package/src/console/routes/logs/route.tsx +0 -77
  40. package/src/console/routes/metrics/route.tsx +0 -36
  41. package/src/console/routes/route.tsx +0 -8
  42. package/src/console/routes/routes/route.tsx +0 -30
  43. package/src/console/routes/services/route.tsx +0 -21
  44. package/src/console/routes/system/route.tsx +0 -43
  45. package/src/console/routes/traceDetail.tsx +0 -22
  46. package/src/console/routes/traces/route.tsx +0 -81
  47. package/src/console/routes/tree.ts +0 -30
  48. package/src/console/ui/Errors.tsx +0 -76
  49. package/src/console/ui/Fibers.tsx +0 -321
  50. package/src/console/ui/Git.tsx +0 -182
  51. package/src/console/ui/Logs.tsx +0 -46
  52. package/src/console/ui/Metrics.tsx +0 -78
  53. package/src/console/ui/Routes.tsx +0 -125
  54. package/src/console/ui/Services.tsx +0 -273
  55. package/src/console/ui/Shell.tsx +0 -62
  56. package/src/console/ui/System.tsx +0 -131
  57. package/src/console/ui/Traces.tsx +0 -426
  58. package/src/sql/Sql.ts +0 -51
@@ -1,36 +0,0 @@
1
- import * as Stream from "effect/Stream"
2
- import * as Route from "../../../Route.ts"
3
- import * as HyperHtml from "../../../hyper/HyperHtml.ts"
4
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
5
- import * as ConsoleStore from "../../ConsoleStore.ts"
6
- import * as Metrics from "../../ui/Metrics.tsx"
7
- import * as Shell from "../../ui/Shell.tsx"
8
-
9
- export default Route.get(
10
- HyperRoute.html(function* () {
11
- return (
12
- <Shell.Shell prefix={ConsoleStore.store.prefix} active="metrics">
13
- <div class="tab-header">Metrics</div>
14
- <div id="metrics-container" class="tab-body metrics-grid">
15
- <Metrics.MetricsGrid metrics={ConsoleStore.store.metrics} />
16
- </div>
17
- <div data-init={`@get('${ConsoleStore.store.prefix}/metrics')`} />
18
- </Shell.Shell>
19
- )
20
- }),
21
- Route.sse(
22
- Stream.fromPubSub(ConsoleStore.store.events).pipe(
23
- Stream.filter((e) => e._tag === "MetricsSnapshot"),
24
- Stream.map((e) => {
25
- const html = HyperHtml.renderToString(<Metrics.MetricsGrid metrics={e.metrics} />).replace(
26
- /\n/g,
27
- "",
28
- )
29
- return {
30
- event: "datastar-patch-elements",
31
- data: `selector #metrics-container\nmode inner\nelements ${html}`,
32
- }
33
- }),
34
- ),
35
- ),
36
- )
@@ -1,8 +0,0 @@
1
- import * as Route from "../../Route.ts"
2
- import * as ConsoleStore from "../ConsoleStore.ts"
3
-
4
- export default Route.get(
5
- Route.render(function* () {
6
- return Route.redirect(`${ConsoleStore.store.prefix}/traces`)
7
- }),
8
- )
@@ -1,30 +0,0 @@
1
- import * as Route from "../../../Route.ts"
2
- import * as RouteTree from "../../../RouteTree.ts"
3
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
4
- import * as ConsoleStore from "../../ConsoleStore.ts"
5
- import * as Routes from "../../ui/Routes.tsx"
6
- import * as Shell from "../../ui/Shell.tsx"
7
-
8
- export default Route.get(
9
- HyperRoute.html(function* () {
10
- const tree = yield* Route.Routes
11
- const routes: Array<Routes.RouteInfo> = []
12
- for (const route of RouteTree.walk(tree)) {
13
- const desc = Route.descriptor(route)
14
- routes.push({
15
- method: desc.method ?? "*",
16
- path: desc.path ?? "/",
17
- format: desc.format,
18
- })
19
- }
20
-
21
- return (
22
- <Shell.Shell prefix={ConsoleStore.store.prefix} active="routes">
23
- <div class="tab-header">Routes</div>
24
- <div class="tab-body">
25
- <Routes.RouteList routes={routes} />
26
- </div>
27
- </Shell.Shell>
28
- )
29
- }),
30
- )
@@ -1,21 +0,0 @@
1
- import * as Effect from "effect/Effect"
2
- import * as Route from "../../../Route.ts"
3
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
4
- import * as ConsoleStore from "../../ConsoleStore.ts"
5
- import * as Services from "../../ui/Services.tsx"
6
- import * as Shell from "../../ui/Shell.tsx"
7
-
8
- export default Route.get(
9
- HyperRoute.html(function* () {
10
- const ctx = yield* Effect.context<never>()
11
- const services = Services.collectServices(ctx.unsafeMap)
12
- return (
13
- <Shell.Shell prefix={ConsoleStore.store.prefix} active="services">
14
- <div class="tab-header">Services ({services.length})</div>
15
- <div class="tab-body">
16
- <Services.ServiceList services={services} />
17
- </div>
18
- </Shell.Shell>
19
- )
20
- }),
21
- )
@@ -1,43 +0,0 @@
1
- import * as Stream from "effect/Stream"
2
- import * as Route from "../../../Route.ts"
3
- import * as HyperHtml from "../../../hyper/HyperHtml.ts"
4
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
5
- import * as ConsoleStore from "../../ConsoleStore.ts"
6
- import * as System from "../../ui/System.tsx"
7
- import * as Shell from "../../ui/Shell.tsx"
8
-
9
- export default Route.get(
10
- HyperRoute.html(function* () {
11
- const stats = ConsoleStore.store.process
12
- return (
13
- <Shell.Shell prefix={ConsoleStore.store.prefix} active="system">
14
- <div style="display:flex;flex-direction:column;flex:1;overflow:hidden">
15
- <div class="tab-header">System</div>
16
- <div id="system-container" class="tab-body">
17
- {stats ? (
18
- <System.SystemStatsView stats={stats} />
19
- ) : (
20
- <div class="empty">Waiting for system data...</div>
21
- )}
22
- </div>
23
- <div data-init={`@get('${ConsoleStore.store.prefix}/system')`} />
24
- </div>
25
- </Shell.Shell>
26
- )
27
- }),
28
- Route.sse(
29
- Stream.fromPubSub(ConsoleStore.store.events).pipe(
30
- Stream.filter((e) => e._tag === "ProcessSnapshot"),
31
- Stream.map((e) => {
32
- const html = HyperHtml.renderToString(<System.SystemStatsView stats={e.stats} />).replace(
33
- /\n/g,
34
- "",
35
- )
36
- return {
37
- event: "datastar-patch-elements",
38
- data: `selector #system-container\nmode inner\nelements ${html}`,
39
- }
40
- }),
41
- ),
42
- ),
43
- )
@@ -1,22 +0,0 @@
1
- import * as Schema from "effect/Schema"
2
- import * as Route from "../../Route.ts"
3
- import * as RouteSchema from "../../RouteSchema.ts"
4
- import * as HyperRoute from "../../hyper/HyperRoute.ts"
5
- import * as ConsoleStore from "../ConsoleStore.ts"
6
- import * as Shell from "../ui/Shell.tsx"
7
- import * as Traces from "../ui/Traces.tsx"
8
-
9
- export default Route.get(
10
- RouteSchema.schemaPathParams(Schema.Struct({ id: Schema.String })),
11
- HyperRoute.html(function* (ctx) {
12
- const allSpans = ConsoleStore.store.spans.toArray()
13
- const groups = Traces.groupByTraceId(allSpans)
14
- const spans = groups.get(ctx.pathParams.id) ?? []
15
-
16
- return (
17
- <Shell.Shell prefix={ConsoleStore.store.prefix} active="traces">
18
- <Traces.TraceDetail prefix={ConsoleStore.store.prefix} spans={spans} />
19
- </Shell.Shell>
20
- )
21
- }),
22
- )
@@ -1,81 +0,0 @@
1
- import * as Stream from "effect/Stream"
2
- import * as Route from "../../../Route.ts"
3
- import * as HyperHtml from "../../../hyper/HyperHtml.ts"
4
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
5
- import * as ConsoleStore from "../../ConsoleStore.ts"
6
- import * as Shell from "../../ui/Shell.tsx"
7
- import * as Traces from "../../ui/Traces.tsx"
8
-
9
- const prefix = ConsoleStore.store.prefix
10
-
11
- function collectNames(): Array<string> {
12
- const names = new Set<string>()
13
- for (const span of ConsoleStore.store.spans.toArray()) {
14
- if (span.name) names.add(span.name)
15
- }
16
- return Array.from(names).sort()
17
- }
18
-
19
- function filterSpans(url: URL): Array<ConsoleStore.ConsoleSpan> {
20
- const search = url.searchParams.get("traceSearch") || ""
21
- let spans = ConsoleStore.store.spans.toArray()
22
- if (search) {
23
- const lower = search.toLowerCase()
24
- spans = spans.filter((s) => s.name.toLowerCase().startsWith(lower))
25
- }
26
- return spans
27
- }
28
-
29
- export default Route.get(
30
- HyperRoute.html(function* (ctx) {
31
- const url = new URL(ctx.request.url)
32
- const spans = filterSpans(url)
33
- const names = collectNames()
34
-
35
- return (
36
- <Shell.Shell prefix={prefix} active="traces">
37
- <form
38
- data-signals={{ traceSearch: "" }}
39
- style="display:flex;flex-direction:column;flex:1;overflow:hidden"
40
- >
41
- <div class="tab-header">Traces</div>
42
- <div class="filter-bar">
43
- <input
44
- type="text"
45
- name="traceSearch"
46
- placeholder="Search trace name..."
47
- list="trace-names"
48
- data-bind:traceSearch
49
- data-on:input={(c) => c.actions.get(location.href, { contentType: "form" })}
50
- />
51
- <datalist id="trace-names">
52
- {names.map((n) => (
53
- <option value={n} />
54
- ))}
55
- </datalist>
56
- </div>
57
- <div id="traces-container" class="tab-body">
58
- <Traces.TraceGroups spans={spans} />
59
- </div>
60
- </form>
61
- <div data-init={`@get('${prefix}/traces')`} />
62
- </Shell.Shell>
63
- )
64
- }),
65
- Route.sse(
66
- Stream.fromPubSub(ConsoleStore.store.events).pipe(
67
- Stream.filter((e) => e._tag === "SpanStart" || e._tag === "SpanEnd"),
68
- Stream.map(() => {
69
- const spans = ConsoleStore.store.spans.toArray()
70
- const html = HyperHtml.renderToString(<Traces.TraceGroups spans={spans} />).replace(
71
- /\n/g,
72
- "",
73
- )
74
- return {
75
- event: "datastar-patch-elements",
76
- data: `selector #traces-container\nmode inner\nelements ${html}`,
77
- }
78
- }),
79
- ),
80
- ),
81
- )
@@ -1,30 +0,0 @@
1
- import * as Route from "../../Route.ts"
2
- import errorsRoute from "./errors/route.tsx"
3
- import fiberDetailRoute from "./fiberDetail.tsx"
4
- import fibersRoute from "./fibers/route.tsx"
5
- import gitRoute from "./git/route.tsx"
6
- import layout from "./layout.tsx"
7
- import logsRoute from "./logs/route.tsx"
8
- import metricsRoute from "./metrics/route.tsx"
9
- import systemRoute from "./system/route.tsx"
10
- import rootRoute from "./route.tsx"
11
- import routesRoute from "./routes/route.tsx"
12
- import servicesRoute from "./services/route.tsx"
13
- import traceDetailRoute from "./traceDetail.tsx"
14
- import tracesRoute from "./traces/route.tsx"
15
-
16
- export default Route.tree({
17
- "*": layout,
18
- "/": rootRoute,
19
- "/traces": tracesRoute,
20
- "/traces/:id": traceDetailRoute,
21
- "/metrics": metricsRoute,
22
- "/logs": logsRoute,
23
- "/errors": errorsRoute,
24
- "/fibers": fibersRoute,
25
- "/fibers/:id": fiberDetailRoute,
26
- "/routes": routesRoute,
27
- "/system": systemRoute,
28
- "/services": servicesRoute,
29
- "/git": gitRoute,
30
- })
@@ -1,76 +0,0 @@
1
- import * as ConsoleStore from "../ConsoleStore.ts"
2
-
3
- export function ErrorLine({ error }: { error: ConsoleStore.ConsoleError }) {
4
- const time = error.date.toLocaleTimeString("en", {
5
- hour12: false,
6
- hour: "2-digit",
7
- minute: "2-digit",
8
- second: "2-digit",
9
- })
10
- const firstLine = error.prettyPrint.split("\n")[0] ?? ""
11
- const tags = error.details.map((d) => d.tag).filter(Boolean)
12
-
13
- const allSpans = error.details.map((d) => d.span).filter(Boolean)
14
- const allProps = error.details.flatMap((d) => Object.entries(d.properties))
15
-
16
- return (
17
- <details style="border-bottom:1px solid #1e293b">
18
- <summary style="display:flex;align-items:center;gap:8px;padding:6px 12px;cursor:pointer;font-size:12px;font-family:monospace">
19
- <span style="color:#6b7280;flex-shrink:0">{time}</span>
20
- <span style="color:#fca5a5">{firstLine}</span>
21
- </summary>
22
- <div style="padding:4px 12px 10px;font-size:12px;font-family:monospace">
23
- {tags.length > 0 && (
24
- <div style="display:flex;flex-wrap:wrap;gap:4px 12px;margin-bottom:6px">
25
- {tags.map((t) => (
26
- <div>
27
- <span style="color:#64748b">tag </span>
28
- <span
29
- style="color:#fca5a5;text-decoration:underline;cursor:copy"
30
- data-on:click={`(e) => { e.signals.errorTag.value = '${t}'; e.actions.get(location.href, { contentType: 'form' }) }`}
31
- >
32
- {t}
33
- </span>
34
- </div>
35
- ))}
36
- </div>
37
- )}
38
- {allSpans.length > 0 && (
39
- <div style="display:flex;flex-wrap:wrap;gap:4px 12px;margin-bottom:6px">
40
- {allSpans.map((s) => (
41
- <div>
42
- <span style="color:#64748b">span </span>
43
- <span style="color:#818cf8">{s}</span>
44
- </div>
45
- ))}
46
- </div>
47
- )}
48
- {allProps.length > 0 && (
49
- <div style="display:flex;flex-wrap:wrap;gap:4px 12px;margin-bottom:6px">
50
- {allProps.map(([k, v]) => (
51
- <div>
52
- <span style="color:#64748b">{k}</span>
53
- <span style="color:#4b5563">=</span>
54
- <span style="color:#e2e8f0">
55
- {typeof v === "object" ? JSON.stringify(v) : String(v)}
56
- </span>
57
- </div>
58
- ))}
59
- </div>
60
- )}
61
- <div style="margin-bottom:6px">
62
- <span style="color:#64748b">fiber </span>
63
- <a
64
- href={`${ConsoleStore.store.prefix}/fibers/${error.fiberId.replace("#", "")}`}
65
- style="color:#9ca3af;text-decoration:none"
66
- >
67
- {error.fiberId}
68
- </a>
69
- </div>
70
- <pre style="color:#9ca3af;font-size:11px;padding:0;margin:0;white-space:pre-wrap;word-break:break-all">
71
- {error.prettyPrint}
72
- </pre>
73
- </div>
74
- </details>
75
- )
76
- }
@@ -1,321 +0,0 @@
1
- import * as ConsoleStore from "../ConsoleStore.ts"
2
- import * as Logs from "./Logs.tsx"
3
-
4
- function formatDuration(ms: number | undefined): string {
5
- if (ms == null) return "..."
6
- if (ms < 1) return `${(ms * 1000).toFixed(0)}µs`
7
- if (ms < 1000) return `${ms.toFixed(1)}ms`
8
- return `${(ms / 1000).toFixed(2)}s`
9
- }
10
-
11
- function KeyValue({ label, value }: { label: string; value: string | undefined | null }) {
12
- if (value == null) return null
13
- return (
14
- <div style="display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b;font-size:12px">
15
- <span style="color:#64748b;min-width:120px">{label}</span>
16
- <span style="color:#e2e8f0;font-family:monospace;word-break:break-all">{value}</span>
17
- </div>
18
- )
19
- }
20
-
21
- function StatusBadge({ status }: { status: string }) {
22
- const bg = status === "ok" ? "#166534" : status === "error" ? "#7f1d1d" : "#713f12"
23
- const fg = status === "ok" ? "#4ade80" : status === "error" ? "#fca5a5" : "#fde047"
24
- return (
25
- <span style={`font-size:11px;padding:2px 8px;border-radius:4px;background:${bg};color:${fg}`}>
26
- {status}
27
- </span>
28
- )
29
- }
30
-
31
- export interface FiberSummary {
32
- readonly id: string
33
- logCount: number
34
- spanCount: number
35
- lastSeen: Date | undefined
36
- alive: "alive" | "dead" | "unknown"
37
- readonly levels: Set<string>
38
- }
39
-
40
- export function getParentChain(fiberId: string, fiberParents: Map<string, string>): Array<string> {
41
- const chain: Array<string> = []
42
- const visited = new Set<string>()
43
- let current = fiberParents.get(fiberId)
44
- while (current && !visited.has(current)) {
45
- chain.push(current)
46
- visited.add(current)
47
- current = fiberParents.get(current)
48
- }
49
- return chain.reverse()
50
- }
51
-
52
- export function collectFibers(
53
- logs: Array<ConsoleStore.ConsoleLog>,
54
- spans: Array<ConsoleStore.ConsoleSpan>,
55
- ): Array<FiberSummary> {
56
- const map = new Map<string, FiberSummary>()
57
- const counter = ConsoleStore.fiberIdCounter()
58
- const now = Date.now()
59
-
60
- for (const log of logs) {
61
- let fiber = map.get(log.fiberId)
62
- if (!fiber) {
63
- fiber = {
64
- id: log.fiberId,
65
- logCount: 0,
66
- spanCount: 0,
67
- lastSeen: undefined,
68
- alive: "unknown",
69
- levels: new Set(),
70
- }
71
- map.set(log.fiberId, fiber)
72
- }
73
- fiber.logCount++
74
- fiber.levels.add(log.level)
75
- if (!fiber.lastSeen || log.date > fiber.lastSeen) {
76
- fiber.lastSeen = log.date
77
- }
78
- }
79
-
80
- for (const span of spans) {
81
- const fiberId = span.attributes["fiber.id"] as string | undefined
82
- if (!fiberId) continue
83
- let fiber = map.get(fiberId)
84
- if (!fiber) {
85
- fiber = {
86
- id: fiberId,
87
- logCount: 0,
88
- spanCount: 0,
89
- lastSeen: undefined,
90
- alive: "unknown",
91
- levels: new Set(),
92
- }
93
- map.set(fiberId, fiber)
94
- }
95
- fiber.spanCount++
96
- }
97
-
98
- for (const fiber of map.values()) {
99
- const num = parseInt(fiber.id.replace("#", ""), 10)
100
- if (!isNaN(num)) {
101
- if (fiber.lastSeen && now - fiber.lastSeen.getTime() < 5000) {
102
- fiber.alive = "alive"
103
- } else if (num < counter) {
104
- fiber.alive = "dead"
105
- }
106
- }
107
- }
108
-
109
- return Array.from(map.values()).sort((a, b) => {
110
- const na = parseInt(a.id.replace("#", ""), 10)
111
- const nb = parseInt(b.id.replace("#", ""), 10)
112
- return nb - na
113
- })
114
- }
115
-
116
- function FiberRow({ fiber, prefix }: { fiber: FiberSummary; prefix: string }) {
117
- const aliveColor =
118
- fiber.alive === "alive" ? "#4ade80" : fiber.alive === "dead" ? "#ef4444" : "#94a3b8"
119
- const aliveBg =
120
- fiber.alive === "alive" ? "#166534" : fiber.alive === "dead" ? "#7f1d1d" : "#334155"
121
- const lastSeen = fiber.lastSeen
122
- ? fiber.lastSeen.toLocaleTimeString("en", {
123
- hour12: false,
124
- hour: "2-digit",
125
- minute: "2-digit",
126
- second: "2-digit",
127
- })
128
- : "—"
129
-
130
- return (
131
- <a
132
- href={`${prefix}/fibers/${fiber.id.replace("#", "")}`}
133
- style="display:flex;align-items:center;gap:12px;padding:8px 12px;border-bottom:1px solid #1e293b;text-decoration:none;transition:background .1s"
134
- onmouseover="this.style.background='#1e293b'"
135
- onmouseout="this.style.background='transparent'"
136
- >
137
- <span style="color:#e2e8f0;font-family:monospace;font-size:13px;font-weight:600;min-width:60px">
138
- {fiber.id}
139
- </span>
140
- <span
141
- style={`font-size:10px;padding:2px 8px;border-radius:4px;background:${aliveBg};color:${aliveColor}`}
142
- >
143
- {fiber.alive}
144
- </span>
145
- <span style="color:#94a3b8;font-size:12px">
146
- {fiber.spanCount} span{fiber.spanCount !== 1 ? "s" : ""}
147
- </span>
148
- <span style="color:#94a3b8;font-size:12px">
149
- {fiber.logCount} log{fiber.logCount !== 1 ? "s" : ""}
150
- </span>
151
- {fiber.levels.has("ERROR") && (
152
- <span style="font-size:10px;padding:1px 6px;border-radius:4px;background:#7f1d1d;color:#fca5a5">
153
- ERROR
154
- </span>
155
- )}
156
- {fiber.levels.has("WARNING") && (
157
- <span style="font-size:10px;padding:1px 6px;border-radius:4px;background:#713f12;color:#fde047">
158
- WARN
159
- </span>
160
- )}
161
- <span style="color:#6b7280;font-size:11px;margin-left:auto;font-family:monospace">
162
- {lastSeen}
163
- </span>
164
- </a>
165
- )
166
- }
167
-
168
- export function FiberList({ fibers, prefix }: { fibers: Array<FiberSummary>; prefix: string }) {
169
- if (fibers.length === 0) {
170
- return <div class="empty">Waiting for fibers...</div>
171
- }
172
- return (
173
- <>
174
- {fibers.map((f) => (
175
- <FiberRow fiber={f} prefix={prefix} />
176
- ))}
177
- </>
178
- )
179
- }
180
-
181
- export function FiberDetail({
182
- prefix,
183
- fiberId,
184
- logs,
185
- spans,
186
- alive,
187
- parents,
188
- context,
189
- }: {
190
- prefix: string
191
- fiberId: string
192
- logs: Array<ConsoleStore.ConsoleLog>
193
- spans: Array<ConsoleStore.ConsoleSpan>
194
- alive: "alive" | "dead" | "unknown"
195
- parents: Array<string>
196
- context: ConsoleStore.FiberContext | undefined
197
- }) {
198
- const aliveColor = alive === "alive" ? "#4ade80" : alive === "dead" ? "#ef4444" : "#94a3b8"
199
- const aliveBg = alive === "alive" ? "#166534" : alive === "dead" ? "#7f1d1d" : "#334155"
200
-
201
- return (
202
- <>
203
- <div class="tab-header">
204
- <div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
205
- <a href={`${prefix}/fibers`} style="color:#64748b;text-decoration:none;font-size:12px">
206
- Fibers
207
- </a>
208
- <span style="color:#475569">/</span>
209
- <span style="color:#e2e8f0;font-size:13px;font-family:monospace">{fiberId}</span>
210
- <span
211
- style={`font-size:11px;padding:2px 8px;border-radius:4px;background:${aliveBg};color:${aliveColor}`}
212
- >
213
- {alive}
214
- </span>
215
- </div>
216
- <div style="display:flex;gap:16px;font-size:12px;color:#94a3b8">
217
- <span>
218
- {logs.length} log{logs.length !== 1 ? "s" : ""}
219
- </span>
220
- <span>
221
- {spans.length} span{spans.length !== 1 ? "s" : ""}
222
- </span>
223
- </div>
224
- </div>
225
- <div class="tab-body">
226
- {parents.length > 0 && (
227
- <div style="padding:8px 16px">
228
- <div style="color:#94a3b8;font-size:12px;font-weight:600;margin-bottom:8px">
229
- Parents
230
- </div>
231
- <div style="display:flex;gap:6px;flex-wrap:wrap">
232
- {parents.map((id) => (
233
- <a
234
- href={`${prefix}/fibers/${id.replace("#", "")}`}
235
- style="color:#38bdf8;font-family:monospace;font-size:13px;text-decoration:none;padding:4px 10px;background:#111827;border:1px solid #1e293b;border-radius:6px"
236
- >
237
- {id}
238
- </a>
239
- ))}
240
- </div>
241
- </div>
242
- )}
243
-
244
- {context &&
245
- (context.spanName || context.traceId || Object.keys(context.annotations).length > 0) && (
246
- <div style="padding:8px 16px">
247
- <div style="color:#94a3b8;font-size:12px;font-weight:600;margin-bottom:8px">
248
- Context
249
- </div>
250
- <div style="background:#111827;border:1px solid #1e293b;border-radius:6px;padding:8px 12px">
251
- {context.spanName && <KeyValue label="Span" value={context.spanName} />}
252
- {context.traceId && (
253
- <div style="display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b;font-size:12px">
254
- <span style="color:#64748b;min-width:120px">Trace</span>
255
- <a
256
- href={`${prefix}/traces/${context.traceId}`}
257
- style="color:#38bdf8;font-family:monospace;word-break:break-all;text-decoration:none"
258
- >
259
- {context.traceId}
260
- </a>
261
- </div>
262
- )}
263
- {Object.entries(context.annotations).map(([k, v]) => (
264
- <KeyValue
265
- label={k}
266
- value={typeof v === "object" ? JSON.stringify(v) : String(v)}
267
- />
268
- ))}
269
- </div>
270
- </div>
271
- )}
272
-
273
- {spans.length > 0 && (
274
- <div style="padding:8px 16px">
275
- <div style="color:#94a3b8;font-size:12px;font-weight:600;margin-bottom:8px">Spans</div>
276
- {spans.map((s) => {
277
- const stacktrace = s.attributes["code.stacktrace"] as string | undefined
278
- return (
279
- <div style="margin-bottom:6px;background:#111827;border:1px solid #1e293b;border-radius:6px;padding:8px 12px">
280
- <div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
281
- <a
282
- href={`${prefix}/traces/${s.traceId}`}
283
- style="color:#38bdf8;font-family:monospace;font-size:13px;text-decoration:none"
284
- >
285
- {s.name}
286
- </a>
287
- <StatusBadge status={s.status} />
288
- <span style="color:#64748b;font-size:11px;margin-left:auto;font-family:monospace">
289
- {formatDuration(s.durationMs)}
290
- </span>
291
- </div>
292
- <KeyValue label="Trace" value={s.traceId} />
293
- <KeyValue label="Kind" value={s.kind} />
294
- {stacktrace && <KeyValue label="Source" value={stacktrace} />}
295
- {Object.entries(s.attributes)
296
- .filter(([k]) => k !== "code.stacktrace")
297
- .map(([k, v]) => (
298
- <KeyValue label={k} value={String(v)} />
299
- ))}
300
- </div>
301
- )
302
- })}
303
- </div>
304
- )}
305
-
306
- {logs.length > 0 && (
307
- <div style="padding:8px 16px">
308
- <div style="color:#94a3b8;font-size:12px;font-weight:600;margin-bottom:8px">Logs</div>
309
- {logs.map((l) => (
310
- <Logs.LogLine log={l} />
311
- ))}
312
- </div>
313
- )}
314
-
315
- {logs.length === 0 && spans.length === 0 && (
316
- <div class="empty">No data found for fiber {fiberId}</div>
317
- )}
318
- </div>
319
- </>
320
- )
321
- }