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,426 +0,0 @@
1
- import * as ConsoleStore from "../ConsoleStore.ts"
2
-
3
- function formatDuration(ms: number | undefined): string {
4
- if (ms == null) return "..."
5
- if (ms < 1) return `${(ms * 1000).toFixed(0)}µs`
6
- if (ms < 1000) return `${ms.toFixed(1)}ms`
7
- return `${(ms / 1000).toFixed(2)}s`
8
- }
9
-
10
- function statusColor(status: string): string {
11
- if (status === "ok") return "#22c55e"
12
- if (status === "error") return "#ef4444"
13
- return "#eab308"
14
- }
15
-
16
- function KeyValue({ label, value }: { label: string; value: string | undefined | null }) {
17
- if (value == null) return null
18
- return (
19
- <div style="display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b;font-size:12px">
20
- <span style="color:#64748b;min-width:120px">{label}</span>
21
- <span style="color:#e2e8f0;font-family:monospace;word-break:break-all">{value}</span>
22
- </div>
23
- )
24
- }
25
-
26
- function StatusBadge({ status }: { status: string }) {
27
- const bg = status === "ok" ? "#166534" : status === "error" ? "#7f1d1d" : "#713f12"
28
- const fg = status === "ok" ? "#4ade80" : status === "error" ? "#fca5a5" : "#fde047"
29
- return (
30
- <span style={`font-size:11px;padding:2px 8px;border-radius:4px;background:${bg};color:${fg}`}>
31
- {status}
32
- </span>
33
- )
34
- }
35
-
36
- // --- Tree building ---
37
-
38
- interface TreeSpan {
39
- span: ConsoleStore.ConsoleSpan
40
- depth: number
41
- childCount: number
42
- isLastChild: boolean
43
- ancestorHasNextSibling: Array<boolean>
44
- }
45
-
46
- function buildSpanTree(spans: Array<ConsoleStore.ConsoleSpan>): Array<TreeSpan> {
47
- const byId = new Map<string, ConsoleStore.ConsoleSpan>()
48
- const childrenOf = new Map<string, Array<ConsoleStore.ConsoleSpan>>()
49
-
50
- for (const s of spans) {
51
- byId.set(s.spanId, s)
52
- }
53
-
54
- const roots: Array<ConsoleStore.ConsoleSpan> = []
55
- for (const s of spans) {
56
- if (s.parentSpanId && byId.has(s.parentSpanId)) {
57
- let children = childrenOf.get(s.parentSpanId)
58
- if (!children) {
59
- children = []
60
- childrenOf.set(s.parentSpanId, children)
61
- }
62
- children.push(s)
63
- } else {
64
- roots.push(s)
65
- }
66
- }
67
-
68
- const sortByStart = (a: ConsoleStore.ConsoleSpan, b: ConsoleStore.ConsoleSpan) =>
69
- Number(a.startTime - b.startTime)
70
-
71
- roots.sort(sortByStart)
72
- for (const children of childrenOf.values()) {
73
- children.sort(sortByStart)
74
- }
75
-
76
- const result: Array<TreeSpan> = []
77
-
78
- function walk(
79
- span: ConsoleStore.ConsoleSpan,
80
- depth: number,
81
- isLast: boolean,
82
- ancestors: Array<boolean>,
83
- ) {
84
- const children = childrenOf.get(span.spanId) ?? []
85
- result.push({
86
- span,
87
- depth,
88
- childCount: children.length,
89
- isLastChild: isLast,
90
- ancestorHasNextSibling: [...ancestors],
91
- })
92
- for (let i = 0; i < children.length; i++) {
93
- walk(children[i], depth + 1, i === children.length - 1, [...ancestors, !isLast])
94
- }
95
- }
96
-
97
- for (let i = 0; i < roots.length; i++) {
98
- walk(roots[i], 0, i === roots.length - 1, [])
99
- }
100
-
101
- return result
102
- }
103
-
104
- // --- Components ---
105
-
106
- function TreeConnectors({ tree }: { tree: TreeSpan }) {
107
- if (tree.depth === 0) return null
108
-
109
- const indent = tree.depth * 20
110
- const elements: Array<any> = []
111
-
112
- for (let i = 0; i < tree.ancestorHasNextSibling.length; i++) {
113
- if (tree.ancestorHasNextSibling[i]) {
114
- elements.push(<div class="wf-vline" style={`left:${i * 20 + 6}px`} />)
115
- }
116
- }
117
-
118
- if (tree.isLastChild) {
119
- elements.push(<div class="wf-elbow" style={`left:${(tree.depth - 1) * 20 + 6}px`} />)
120
- } else {
121
- elements.push(<div class="wf-vline" style={`left:${(tree.depth - 1) * 20 + 6}px`} />)
122
- }
123
-
124
- elements.push(<div class="wf-hline" style={`left:${(tree.depth - 1) * 20 + 6}px;top:50%`} />)
125
-
126
- return (
127
- <div class="wf-tree" style={`width:${indent}px;position:relative`}>
128
- {elements}
129
- </div>
130
- )
131
- }
132
-
133
- function TimeAxis({ totalMs }: { totalMs: number }) {
134
- const ticks = 5
135
- const labels: Array<string> = []
136
- for (let i = 0; i <= ticks; i++) {
137
- labels.push(formatDuration((totalMs / ticks) * i))
138
- }
139
- return (
140
- <div class="wf-axis">
141
- <div style="padding:4px 8px;color:#64748b;font-size:11px">Span</div>
142
- <div class="wf-axis-ticks">
143
- {labels.map((l) => (
144
- <span>{l}</span>
145
- ))}
146
- </div>
147
- </div>
148
- )
149
- }
150
-
151
- function WaterfallRow({
152
- tree,
153
- totalMs,
154
- rootStart,
155
- }: {
156
- tree: TreeSpan
157
- totalMs: number
158
- rootStart: bigint
159
- }) {
160
- const s = tree.span
161
- const offsetMs = Number(s.startTime - rootStart) / 1_000_000
162
- const durMs = s.durationMs ?? 0
163
- const leftPct = totalMs > 0 ? Math.min(100, (offsetMs / totalMs) * 100) : 0
164
- const widthPct =
165
- totalMs > 0 ? Math.max(0.5, Math.min(100 - leftPct, (durMs / totalMs) * 100)) : 100
166
- const color = statusColor(s.status)
167
-
168
- const durLabelLeft = leftPct + widthPct + 0.5
169
-
170
- return (
171
- <div class="wf-row">
172
- <div class="wf-name">
173
- <TreeConnectors tree={tree} />
174
- <span style="overflow:hidden;text-overflow:ellipsis">{s.name}</span>
175
- {tree.childCount > 0 && <span class="wf-badge">{tree.childCount}</span>}
176
- </div>
177
- <div class="wf-bar-cell">
178
- <div class="wf-bar" style={`left:${leftPct}%;width:${widthPct}%;background:${color}`} />
179
- <div class="wf-dur" style={`left:${durLabelLeft}%`}>
180
- {formatDuration(s.durationMs)}
181
- </div>
182
- </div>
183
- </div>
184
- )
185
- }
186
-
187
- function MiniWaterfall({
188
- spans,
189
- totalMs,
190
- rootStart,
191
- }: {
192
- spans: Array<ConsoleStore.ConsoleSpan>
193
- totalMs: number
194
- rootStart: bigint
195
- }) {
196
- if (totalMs <= 0) return <div class="mini-wf" />
197
- return (
198
- <div class="mini-wf">
199
- {spans.map((s) => {
200
- const offsetMs = Number(s.startTime - rootStart) / 1_000_000
201
- const durMs = s.durationMs ?? 0
202
- const leftPct = Math.min(100, (offsetMs / totalMs) * 100)
203
- const widthPct = Math.max(0.3, Math.min(100 - leftPct, (durMs / totalMs) * 100))
204
- return (
205
- <div
206
- class="mini-wf-bar"
207
- style={`left:${leftPct}%;width:${widthPct}%;background:${statusColor(s.status)}`}
208
- />
209
- )
210
- })}
211
- </div>
212
- )
213
- }
214
-
215
- // --- Exports ---
216
-
217
- export function groupByTraceId(
218
- spans: Array<ConsoleStore.ConsoleSpan>,
219
- ): Map<string, Array<ConsoleStore.ConsoleSpan>> {
220
- const groups = new Map<string, Array<ConsoleStore.ConsoleSpan>>()
221
- for (const span of spans) {
222
- let group = groups.get(span.traceId)
223
- if (!group) {
224
- group = []
225
- groups.set(span.traceId, group)
226
- }
227
- group.push(span)
228
- }
229
- return groups
230
- }
231
-
232
- export function TraceGroup({ spans }: { spans: Array<ConsoleStore.ConsoleSpan> }) {
233
- if (spans.length === 0) return null
234
- const root = spans.find((s) => !s.parentSpanId) ?? spans[0]
235
- const traceId = root.traceId
236
- const totalMs = root.durationMs ?? 0
237
- const rootStart = root.startTime
238
- const hasError = spans.some((s) => s.status === "error")
239
- const status = hasError ? "error" : root.status
240
-
241
- return (
242
- <details class="tl-row">
243
- <summary class="tl-summary tl-cols">
244
- <span class="tl-cell tl-cell-status">
245
- <span
246
- style={`width:8px;height:8px;border-radius:50%;background:${statusColor(status)};display:block`}
247
- />
248
- </span>
249
- <span class="tl-cell tl-cell-name">{root.name}</span>
250
- <span class="tl-cell tl-cell-spans">{spans.length}</span>
251
- <span class="tl-cell tl-cell-dur">{formatDuration(totalMs)}</span>
252
- <span class="tl-cell tl-cell-id">{traceId.slice(0, 12)}</span>
253
- </summary>
254
- <div class="tl-body">
255
- <div style="display:flex;gap:12px;align-items:center;margin-bottom:8px">
256
- <a
257
- href={`${ConsoleStore.store.prefix}/traces/${traceId}`}
258
- style="color:#38bdf8;font-size:12px;text-decoration:none"
259
- >
260
- Full trace view
261
- </a>
262
- <StatusBadge status={status} />
263
- <span style="color:#64748b;font-size:11px">
264
- {spans.length} span{spans.length !== 1 ? "s" : ""}
265
- </span>
266
- <span style="color:#64748b;font-size:11px;font-family:monospace">
267
- {formatDuration(totalMs)}
268
- </span>
269
- <span style="color:#475569;font-size:10px;font-family:monospace">{traceId}</span>
270
- </div>
271
- {spans.map((s) => {
272
- const offsetMs = Number(s.startTime - rootStart) / 1_000_000
273
- const leftPct = totalMs > 0 ? Math.min(100, (offsetMs / totalMs) * 100) : 0
274
- const widthPct =
275
- totalMs > 0
276
- ? Math.max(0.5, Math.min(100 - leftPct, ((s.durationMs ?? 0) / totalMs) * 100))
277
- : 100
278
- return (
279
- <div style="display:flex;align-items:center;gap:8px;padding:2px 0;font-size:11px;font-family:monospace">
280
- <span style={`color:${statusColor(s.status)};min-width:10px`}>
281
- {s.parentSpanId ? " " : ""}
282
- </span>
283
- <span style="color:#d1d5db;min-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
284
- {s.name}
285
- </span>
286
- <div style="flex:1;height:10px;background:#1f2937;border-radius:2px;position:relative;overflow:hidden">
287
- <div
288
- style={`position:absolute;height:100%;border-radius:2px;background:${statusColor(s.status)};left:${leftPct}%;width:${widthPct}%`}
289
- />
290
- </div>
291
- <span style="color:#9ca3af;min-width:60px;text-align:right">
292
- {formatDuration(s.durationMs)}
293
- </span>
294
- </div>
295
- )
296
- })}
297
- </div>
298
- </details>
299
- )
300
- }
301
-
302
- export function TraceGroups({ spans }: { spans: Array<ConsoleStore.ConsoleSpan> }) {
303
- const groups = groupByTraceId(spans)
304
- const sorted = Array.from(groups.values())
305
- .sort((a, b) => Number(b[0].startTime) - Number(a[0].startTime))
306
- .slice(0, 50)
307
-
308
- if (sorted.length === 0) {
309
- return <div class="empty">Waiting for traces...</div>
310
- }
311
- return (
312
- <div class="tl-grid">
313
- <div class="tl-header tl-cols">
314
- <span class="tl-cell tl-cell-status" />
315
- <span class="tl-cell tl-cell-name">Name</span>
316
- <span class="tl-cell tl-cell-spans">Spans</span>
317
- <span class="tl-cell tl-cell-dur">Duration</span>
318
- <span class="tl-cell tl-cell-id">Trace</span>
319
- </div>
320
- {sorted.map((group) => (
321
- <TraceGroup spans={group} />
322
- ))}
323
- </div>
324
- )
325
- }
326
-
327
- export function TraceDetail({
328
- prefix,
329
- spans,
330
- }: {
331
- prefix: string
332
- spans: Array<ConsoleStore.ConsoleSpan>
333
- }) {
334
- if (spans.length === 0) {
335
- return <div class="empty">Trace not found</div>
336
- }
337
- const root = spans.find((s) => !s.parentSpanId) ?? spans[0]
338
- const traceId = root.traceId
339
- const totalMs = root.durationMs ?? 0
340
- const rootStart = root.startTime
341
- const startDate = new Date(Number(rootStart) / 1_000_000)
342
- const tree = buildSpanTree(spans)
343
-
344
- return (
345
- <>
346
- <div style="padding:12px 16px;border-bottom:1px solid #1e293b">
347
- <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
348
- <a href={`${prefix}/traces`} style="color:#64748b;text-decoration:none;font-size:12px">
349
- Traces
350
- </a>
351
- <span style="color:#475569">/</span>
352
- <span style="color:#e2e8f0;font-size:13px;font-family:monospace">{root.name}</span>
353
- </div>
354
- <div style="display:flex;gap:16px;font-size:12px;color:#94a3b8;align-items:center">
355
- <StatusBadge status={root.status} />
356
- <span>
357
- {spans.length} span{spans.length !== 1 ? "s" : ""}
358
- </span>
359
- <span>{formatDuration(totalMs)}</span>
360
- <span>{startDate.toLocaleTimeString("en", { hour12: false })}</span>
361
- <span style="color:#475569;font-family:monospace;font-size:10px">{traceId}</span>
362
- </div>
363
- </div>
364
-
365
- <div style="padding:8px 16px">
366
- <MiniWaterfall spans={spans} totalMs={totalMs} rootStart={rootStart} />
367
- </div>
368
-
369
- <div style="padding:0 8px">
370
- <TimeAxis totalMs={totalMs} />
371
- <div class="wf-grid">
372
- {tree.map((t) => (
373
- <WaterfallRow tree={t} totalMs={totalMs} rootStart={rootStart} />
374
- ))}
375
- </div>
376
- </div>
377
-
378
- <div style="padding:8px">
379
- {tree.map((t) => {
380
- const s = t.span
381
- const stacktrace = s.attributes["code.stacktrace"] as string | undefined
382
- const customAttrs = Object.entries(s.attributes).filter(([k]) => k !== "code.stacktrace")
383
-
384
- return (
385
- <details class="span-panel" style="margin-bottom:4px">
386
- <summary class="span-panel-header">
387
- <span
388
- style={`width:8px;height:8px;border-radius:50%;background:${statusColor(s.status)};flex-shrink:0`}
389
- />
390
- <span style="color:#e2e8f0;font-family:monospace;font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1">
391
- {s.name}
392
- </span>
393
- <StatusBadge status={s.status} />
394
- <span style="color:#64748b;font-size:11px;font-family:monospace;margin-left:auto">
395
- {formatDuration(s.durationMs)}
396
- </span>
397
- </summary>
398
- <div class="span-panel-body">
399
- <KeyValue label="Span ID" value={s.spanId} />
400
- <KeyValue label="Kind" value={s.kind} />
401
- {s.parentSpanId && <KeyValue label="Parent" value={s.parentSpanId} />}
402
- {stacktrace && <KeyValue label="Source" value={stacktrace} />}
403
- {customAttrs.map(([k, v]) => (
404
- <KeyValue label={k} value={String(v)} />
405
- ))}
406
- {s.events.length > 0 && (
407
- <div style="margin-top:4px">
408
- <span style="color:#64748b;font-size:11px">Events:</span>
409
- {s.events.map((ev) => (
410
- <div style="padding:2px 0;font-size:11px;color:#94a3b8;font-family:monospace">
411
- {ev.name}
412
- {ev.attributes && (
413
- <span style="color:#64748b"> {JSON.stringify(ev.attributes)}</span>
414
- )}
415
- </div>
416
- ))}
417
- </div>
418
- )}
419
- </div>
420
- </details>
421
- )
422
- })}
423
- </div>
424
- </>
425
- )
426
- }
package/src/sql/Sql.ts DELETED
@@ -1,51 +0,0 @@
1
- import * as Context from "effect/Context"
2
- import * as Data from "effect/Data"
3
- import type * as Effect from "effect/Effect"
4
- import type * as Scope from "effect/Scope"
5
-
6
- export class SqlError extends Data.TaggedError("SqlError")<{
7
- readonly code: string
8
- readonly message: string
9
- readonly cause?: unknown
10
- }> {}
11
-
12
- export interface SqlQuery {
13
- <T = any>(
14
- strings: TemplateStringsArray,
15
- ...values: Array<unknown>
16
- ): Effect.Effect<ReadonlyArray<T>, SqlError>
17
-
18
- readonly unsafe: <T = any>(
19
- query: string,
20
- values?: Array<unknown>,
21
- ) => Effect.Effect<ReadonlyArray<T>, SqlError>
22
-
23
- readonly values: {
24
- <T extends Record<string, unknown>>(obj: T | ReadonlyArray<T>): SqlHelper<T>
25
- <T extends Record<string, unknown>, K extends keyof T>(
26
- obj: T | ReadonlyArray<T>,
27
- ...columns: [K, ...Array<K>]
28
- ): SqlHelper<Pick<T, K>>
29
- }
30
- }
31
-
32
- export interface SqlClient extends SqlQuery {
33
- readonly withTransaction: <A, E, R>(
34
- self: Effect.Effect<A, E, R>,
35
- ) => Effect.Effect<A, SqlError | E, R>
36
-
37
- readonly reserve: Effect.Effect<SqlQuery, SqlError, Scope.Scope>
38
-
39
- readonly close: (options?: { readonly timeout?: number }) => Effect.Effect<void, SqlError>
40
-
41
- readonly use: <T>(fn: (driver: any) => Promise<T> | T) => Effect.Effect<T, SqlError>
42
- }
43
-
44
- export interface SqlHelper<T> {
45
- readonly value: ReadonlyArray<T>
46
- readonly columns: ReadonlyArray<keyof T>
47
- }
48
-
49
- export const SqlClient: Context.Tag<SqlClient, SqlClient> = Context.GenericTag<SqlClient>(
50
- "effect-start/Sql/SqlClient",
51
- )