effect-start 0.30.1 → 0.32.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 (122) hide show
  1. package/dist/Fetch.d.ts +1 -1
  2. package/dist/Fetch.d.ts.map +1 -1
  3. package/dist/Fetch.js +1 -1
  4. package/dist/Fetch.js.map +1 -1
  5. package/dist/GlobalLayer.d.ts.map +1 -1
  6. package/dist/GlobalLayer.js.map +1 -1
  7. package/dist/Html.js +3 -3
  8. package/dist/Html.js.map +1 -1
  9. package/dist/Password.d.ts +26 -0
  10. package/dist/Password.d.ts.map +1 -0
  11. package/dist/Password.js +83 -0
  12. package/dist/Password.js.map +1 -0
  13. package/dist/Route.d.ts.map +1 -1
  14. package/dist/Route.js +1 -1
  15. package/dist/Route.js.map +1 -1
  16. package/dist/RouteBody.d.ts +10 -5
  17. package/dist/RouteBody.d.ts.map +1 -1
  18. package/dist/RouteBody.js +6 -2
  19. package/dist/RouteBody.js.map +1 -1
  20. package/dist/RouteHttp.d.ts.map +1 -1
  21. package/dist/RouteHttp.js +10 -3
  22. package/dist/RouteHttp.js.map +1 -1
  23. package/dist/RouteSse.d.ts +3 -4
  24. package/dist/RouteSse.d.ts.map +1 -1
  25. package/dist/RouteSse.js +3 -3
  26. package/dist/RouteSse.js.map +1 -1
  27. package/dist/RouteTree.d.ts +20 -5
  28. package/dist/RouteTree.d.ts.map +1 -1
  29. package/dist/RouteTree.js +172 -40
  30. package/dist/RouteTree.js.map +1 -1
  31. package/dist/StaticFiles.d.ts +23 -0
  32. package/dist/StaticFiles.d.ts.map +1 -0
  33. package/dist/StaticFiles.js +74 -0
  34. package/dist/StaticFiles.js.map +1 -0
  35. package/dist/_Mime.d.ts +2 -0
  36. package/dist/_Mime.d.ts.map +1 -0
  37. package/dist/_Mime.js +33 -0
  38. package/dist/_Mime.js.map +1 -0
  39. package/dist/_PathPattern.js +4 -4
  40. package/dist/_PathPattern.js.map +1 -1
  41. package/dist/_StreamExtra.d.ts.map +1 -1
  42. package/dist/_StreamExtra.js +0 -1
  43. package/dist/_StreamExtra.js.map +1 -1
  44. package/dist/bun/BunRoute.d.ts.map +1 -1
  45. package/dist/bun/BunRoute.js +6 -0
  46. package/dist/bun/BunRoute.js.map +1 -1
  47. package/dist/bun/BunServer.d.ts +2 -0
  48. package/dist/bun/BunServer.d.ts.map +1 -1
  49. package/dist/bun/BunServer.js +18 -7
  50. package/dist/bun/BunServer.js.map +1 -1
  51. package/dist/studio/Studio.d.ts +1 -1
  52. package/dist/studio/Studio.d.ts.map +1 -1
  53. package/dist/studio/Studio.js +5 -4
  54. package/dist/studio/Studio.js.map +1 -1
  55. package/dist/studio/StudioErrors.d.ts.map +1 -1
  56. package/dist/studio/StudioErrors.js +2 -2
  57. package/dist/studio/StudioErrors.js.map +1 -1
  58. package/dist/studio/StudioLogger.d.ts.map +1 -1
  59. package/dist/studio/StudioLogger.js +1 -3
  60. package/dist/studio/StudioLogger.js.map +1 -1
  61. package/dist/studio/StudioStore.d.ts +23 -17
  62. package/dist/studio/StudioStore.d.ts.map +1 -1
  63. package/dist/studio/StudioStore.js +48 -44
  64. package/dist/studio/StudioStore.js.map +1 -1
  65. package/dist/studio/StudioTracer.d.ts.map +1 -1
  66. package/dist/studio/StudioTracer.js +10 -4
  67. package/dist/studio/StudioTracer.js.map +1 -1
  68. package/dist/studio/routes/errors/route.d.ts +2 -2
  69. package/dist/studio/routes/errors/route.d.ts.map +1 -1
  70. package/dist/studio/routes/errors/route.js +2 -2
  71. package/dist/studio/routes/errors/route.js.map +1 -1
  72. package/dist/studio/routes/fiberDetail.d.ts +1 -1
  73. package/dist/studio/routes/fiberDetail.js +4 -4
  74. package/dist/studio/routes/fiberDetail.js.map +1 -1
  75. package/dist/studio/routes/fibers/route.d.ts +3 -3
  76. package/dist/studio/routes/fibers/route.js +4 -4
  77. package/dist/studio/routes/fibers/route.js.map +1 -1
  78. package/dist/studio/routes/logs/route.d.ts +2 -2
  79. package/dist/studio/routes/logs/route.d.ts.map +1 -1
  80. package/dist/studio/routes/logs/route.js +2 -2
  81. package/dist/studio/routes/logs/route.js.map +1 -1
  82. package/dist/studio/routes/metrics/route.d.ts +1 -1
  83. package/dist/studio/routes/system/route.d.ts +1 -1
  84. package/dist/studio/routes/traceDetail.d.ts +1 -1
  85. package/dist/studio/routes/traceDetail.js +1 -1
  86. package/dist/studio/routes/traceDetail.js.map +1 -1
  87. package/dist/studio/routes/traces/route.d.ts +3 -3
  88. package/dist/studio/routes/traces/route.js +5 -5
  89. package/dist/studio/routes/traces/route.js.map +1 -1
  90. package/dist/studio/routes/tree.d.ts +14 -14
  91. package/dist/studio/ui/Traces.d.ts +1 -0
  92. package/dist/studio/ui/Traces.d.ts.map +1 -1
  93. package/dist/studio/ui/Traces.js +32 -11
  94. package/dist/studio/ui/Traces.js.map +1 -1
  95. package/package.json +2 -2
  96. package/src/Fetch.ts +2 -2
  97. package/src/GlobalLayer.ts +1 -3
  98. package/src/Html.ts +3 -3
  99. package/src/Password.ts +130 -0
  100. package/src/Route.ts +10 -12
  101. package/src/RouteBody.ts +36 -14
  102. package/src/RouteHttp.ts +14 -3
  103. package/src/RouteSse.ts +10 -10
  104. package/src/RouteTree.ts +252 -61
  105. package/src/StaticFiles.ts +112 -0
  106. package/src/_Mime.ts +33 -0
  107. package/src/_PathPattern.ts +4 -4
  108. package/src/_StreamExtra.ts +0 -1
  109. package/src/bun/BunRoute.ts +10 -0
  110. package/src/bun/BunServer.ts +33 -7
  111. package/src/studio/Studio.ts +6 -5
  112. package/src/studio/StudioErrors.ts +2 -3
  113. package/src/studio/StudioLogger.ts +2 -3
  114. package/src/studio/StudioStore.ts +159 -115
  115. package/src/studio/StudioTracer.ts +11 -5
  116. package/src/studio/routes/errors/route.tsx +4 -5
  117. package/src/studio/routes/fiberDetail.tsx +4 -4
  118. package/src/studio/routes/fibers/route.tsx +4 -4
  119. package/src/studio/routes/logs/route.tsx +3 -1
  120. package/src/studio/routes/traceDetail.tsx +1 -1
  121. package/src/studio/routes/traces/route.tsx +8 -8
  122. package/src/studio/ui/Traces.tsx +41 -13
@@ -50,6 +50,7 @@ export type BunServer = {
50
50
  readonly server: Bun.Server<WebSocketContext>
51
51
  readonly pushHandler: (fetch: FetchHandler) => void
52
52
  readonly popHandler: () => void
53
+ readonly setRoutes: (tree: RouteTree.RouteTree) => Effect.Effect<void>
53
54
  }
54
55
 
55
56
  export const BunServer = Context.GenericTag<BunServer>("effect-start/BunServer")
@@ -77,6 +78,9 @@ export const make = (
77
78
  },
78
79
  ]
79
80
 
81
+ const setRoutesDeferred =
82
+ yield* Deferred.make<(tree: RouteTree.RouteTree) => Effect.Effect<void>>()
83
+
80
84
  const service = BunServer.of({
81
85
  // During the construction we need to create a service imlpementation
82
86
  // first so we can provide it in the runtime that will be used in web
@@ -93,6 +97,11 @@ export const make = (
93
97
  handlerStack.pop()
94
98
  reload()
95
99
  },
100
+ setRoutes(tree) {
101
+ return Deferred.await(setRoutesDeferred).pipe(
102
+ Effect.flatMap((applyRoutes) => applyRoutes(tree)),
103
+ )
104
+ },
96
105
  })
97
106
 
98
107
  const runtime = yield* Effect.runtime().pipe(
@@ -150,6 +159,18 @@ export const make = (
150
159
  })
151
160
  }
152
161
 
162
+ yield* Deferred.succeed(setRoutesDeferred, (tree) =>
163
+ walkBunRoutes(runtime, tree).pipe(
164
+ Effect.tap((bunRoutes) =>
165
+ Effect.sync(() => {
166
+ currentRoutes = bunRoutes
167
+ reload()
168
+ }),
169
+ ),
170
+ Effect.asVoid,
171
+ ),
172
+ )
173
+
153
174
  const bunServer = BunServer.of({
154
175
  server,
155
176
  pushHandler(fetch) {
@@ -160,6 +181,11 @@ export const make = (
160
181
  handlerStack.pop()
161
182
  reload()
162
183
  },
184
+ setRoutes(tree) {
185
+ return Deferred.await(setRoutesDeferred).pipe(
186
+ Effect.flatMap((applyRoutes) => applyRoutes(tree)),
187
+ )
188
+ },
163
189
  })
164
190
 
165
191
  return bunServer
@@ -185,6 +211,7 @@ export const layerRoutes = (
185
211
  /**
186
212
  * Resolves the Bun server in one place for Start.serve so routes are available:
187
213
  * 1) Reuse a user-provided BunServer when one already exists in context.
214
+ * If Route.Routes are available, upgrade the existing server with them.
188
215
  * 2) Otherwise create the server from Route.Routes when routes are available.
189
216
  * 3) Otherwise create a fallback server with the default 404 handler.
190
217
  */
@@ -195,18 +222,17 @@ export const layerStart = (
195
222
  BunServer,
196
223
  Effect.gen(function* () {
197
224
  const app = yield* StartApp.StartApp
225
+ const routes = yield* Effect.serviceOption(Route.Routes)
226
+ const routeTree = Option.getOrNull(routes)
198
227
  const existing = yield* Effect.serviceOption(BunServer)
199
228
  if (Option.isSome(existing)) {
229
+ if (routeTree !== null) {
230
+ yield* existing.value.setRoutes(routeTree)
231
+ }
200
232
  yield* Deferred.succeed(app.server, existing.value)
201
233
  return existing.value
202
234
  }
203
- const routes = yield* Effect.serviceOption(Route.Routes)
204
- if (Option.isSome(routes)) {
205
- const server = yield* make(options ?? {}, routes.value)
206
- yield* Deferred.succeed(app.server, server)
207
- return server
208
- }
209
- const server = yield* make(options ?? {})
235
+ const server = yield* make(options ?? {}, routeTree ?? undefined)
210
236
  yield* Deferred.succeed(app.server, server)
211
237
  return server
212
238
  }),
@@ -10,16 +10,17 @@ import * as StudioMetrics from "./StudioMetrics.ts"
10
10
  import * as StudioProcess from "./StudioProcess.ts"
11
11
  import * as StudioStore from "./StudioStore.ts"
12
12
  import * as StudioTracer from "./StudioTracer.ts"
13
- import consoleRoutes from "./routes/tree.ts"
13
+ import routes from "./routes/tree.ts"
14
14
 
15
15
  export function layer(
16
16
  options?: StudioStore.StudioStoreOptions & {
17
17
  readonly sqlLayer?: Layer.Layer<SqlClient.SqlClient, SqlClient.SqlError>
18
18
  },
19
- ): Layer.Layer<StudioStore.StudioStore> {
19
+ ): Layer.Layer<StudioStore.StudioStore | SqlClient.SqlClient> {
20
20
  const sqlLayer =
21
21
  options?.sqlLayer ?? sqlBun.layer({ adapter: "sqlite" as const, filename: ":memory:" })
22
- const store = StudioStore.layer(options).pipe(Layer.provide(sqlLayer), Layer.orDie)
22
+ const providedSqlLayer = sqlLayer.pipe(Layer.orDie)
23
+ const store = StudioStore.layer(options).pipe(Layer.provide(providedSqlLayer), Layer.orDie)
23
24
  const features = Layer.mergeAll(
24
25
  StudioTracer.layer,
25
26
  StudioLogger.layer,
@@ -27,7 +28,7 @@ export function layer(
27
28
  StudioErrors.layer,
28
29
  StudioProcess.layer,
29
30
  ).pipe(Layer.provide(store))
30
- return Layer.merge(store, features)
31
+ return Layer.mergeAll(providedSqlLayer, store, features)
31
32
  }
32
33
 
33
34
  export function layerRoutes(options?: { prefix?: string }) {
@@ -39,7 +40,7 @@ export function layerRoutes(options?: { prefix?: string }) {
39
40
  const existing = yield* Route.Routes
40
41
  StudioStore.store.prefix = prefix
41
42
  const tree = Route.tree({
42
- [prefix as "/"]: consoleRoutes,
43
+ [prefix as "/"]: routes,
43
44
  })
44
45
  return RouteTree.merge(existing, tree)
45
46
  }),
@@ -185,7 +185,6 @@ function make(store: StudioStore.StudioStoreShape): Supervisor.Supervisor<void>
185
185
 
186
186
  StudioStore.runWrite(
187
187
  StudioStore.upsertFiber(
188
- store.sql,
189
188
  childId,
190
189
  parentId !== childId ? parentId : undefined,
191
190
  span?._tag === "Span" ? span.name : undefined,
@@ -206,8 +205,8 @@ function make(store: StudioStore.StudioStoreShape): Supervisor.Supervisor<void>
206
205
  }
207
206
  StudioStore.runWrite(
208
207
  Effect.zipRight(
209
- StudioStore.insertError(store.sql, error),
210
- StudioStore.evict(store.sql, "Error", store.errorCapacity),
208
+ StudioStore.insertError(error),
209
+ StudioStore.evict("Error", store.errorCapacity),
211
210
  ),
212
211
  )
213
212
  Effect.runSync(PubSub.publish(store.events, { _tag: "Error", error }))
@@ -9,7 +9,6 @@ import * as StudioStore from "./StudioStore.ts"
9
9
 
10
10
  const studioLogger = Logger.make((options) => {
11
11
  const store = StudioStore.store
12
- if (!store.sql) return
13
12
 
14
13
  try {
15
14
  const levelMap: Record<string, StudioStore.StudioLog["level"]> = {
@@ -41,8 +40,8 @@ const studioLogger = Logger.make((options) => {
41
40
  }
42
41
  StudioStore.runWrite(
43
42
  Effect.zipRight(
44
- StudioStore.insertLog(store.sql, log),
45
- StudioStore.evict(store.sql, "Log", store.logCapacity),
43
+ StudioStore.insertLog(log),
44
+ StudioStore.evict("Log", store.logCapacity),
46
45
  ),
47
46
  )
48
47
  Effect.runSync(PubSub.publish(store.events, { _tag: "Log", log }))
@@ -114,6 +114,8 @@ export interface StudioMetricSnapshot {
114
114
  export type StudioEvent =
115
115
  | { readonly _tag: "SpanStart"; readonly span: StudioSpan }
116
116
  | { readonly _tag: "SpanEnd"; readonly span: StudioSpan }
117
+ | { readonly _tag: "TraceStart"; readonly traceId: bigint }
118
+ | { readonly _tag: "TraceEnd"; readonly traceId: bigint }
117
119
  | { readonly _tag: "Log"; readonly log: StudioLog }
118
120
  | { readonly _tag: "Error"; readonly error: StudioError }
119
121
  | { readonly _tag: "MetricsSnapshot"; readonly metrics: Array<StudioMetricSnapshot> }
@@ -327,179 +329,221 @@ function deserializeError(row: ErrorRow): StudioError {
327
329
  }
328
330
  }
329
331
 
330
- export function insertSpan(sql: SqlClient.SqlClient, span: StudioSpan) {
331
- return sql`INSERT INTO Span ${sql({
332
- spanId: span.spanId,
333
- traceId: span.traceId,
334
- fiberId: span.fiberId ?? null,
335
- name: span.name,
336
- kind: span.kind,
337
- parentSpanId: span.parentSpanId ?? null,
338
- startTime: span.startTime.toString(),
339
- endTime: span.endTime?.toString() ?? null,
340
- durationMs: span.durationMs ?? null,
341
- status: span.status,
342
- attributes: JSON.stringify(span.attributes),
343
- events: JSON.stringify(serializeBigint(span.events)),
344
- })}`
345
- }
346
-
347
- export function updateSpan(sql: SqlClient.SqlClient, span: StudioSpan) {
348
- return sql`UPDATE Span SET
349
- endTime = ${span.endTime?.toString() ?? null},
350
- durationMs = ${span.durationMs ?? null},
351
- status = ${span.status},
352
- attributes = ${JSON.stringify(span.attributes)},
353
- events = ${JSON.stringify(serializeBigint(span.events))}
354
- WHERE spanId = ${span.spanId}`
355
- }
356
-
357
- export function insertLog(sql: SqlClient.SqlClient, log: StudioLog) {
358
- return sql`INSERT INTO Log ${sql({
359
- id: log.id,
360
- level: log.level,
361
- message: log.message,
362
- fiberId: log.fiberId,
363
- cause: log.cause ?? null,
364
- spans: JSON.stringify(log.spans),
365
- annotations: JSON.stringify(log.annotations),
366
- })}`
367
- }
368
-
369
- export function insertError(sql: SqlClient.SqlClient, error: StudioError) {
370
- return sql`INSERT INTO Error ${sql({
371
- id: error.id,
372
- fiberId: error.fiberId,
373
- interrupted: error.interrupted ? 1 : 0,
374
- prettyPrint: error.prettyPrint,
375
- details: JSON.stringify(error.details),
376
- })}`
332
+ const withSql = <A, E>(
333
+ f: (sql: SqlClient.SqlClient) => Effect.Effect<A, E>,
334
+ ): Effect.Effect<A, E, SqlClient.SqlClient> => Effect.flatMap(SqlClient.SqlClient, f)
335
+
336
+ export function insertSpan(span: StudioSpan) {
337
+ return withSql((sql) =>
338
+ sql`INSERT INTO Span ${sql({
339
+ spanId: span.spanId,
340
+ traceId: span.traceId,
341
+ fiberId: span.fiberId ?? null,
342
+ name: span.name,
343
+ kind: span.kind,
344
+ parentSpanId: span.parentSpanId ?? null,
345
+ startTime: span.startTime.toString(),
346
+ endTime: span.endTime?.toString() ?? null,
347
+ durationMs: span.durationMs ?? null,
348
+ status: span.status,
349
+ attributes: JSON.stringify(span.attributes),
350
+ events: JSON.stringify(serializeBigint(span.events)),
351
+ })}`,
352
+ )
353
+ }
354
+
355
+ export function updateSpan(span: StudioSpan) {
356
+ return withSql(
357
+ (sql) => sql`UPDATE Span SET
358
+ endTime = ${span.endTime?.toString() ?? null},
359
+ durationMs = ${span.durationMs ?? null},
360
+ status = ${span.status},
361
+ attributes = ${JSON.stringify(span.attributes)},
362
+ events = ${JSON.stringify(serializeBigint(span.events))}
363
+ WHERE spanId = ${span.spanId}`,
364
+ )
365
+ }
366
+
367
+ export function insertLog(log: StudioLog) {
368
+ return withSql((sql) =>
369
+ sql`INSERT INTO Log ${sql({
370
+ id: log.id,
371
+ level: log.level,
372
+ message: log.message,
373
+ fiberId: log.fiberId,
374
+ cause: log.cause ?? null,
375
+ spans: JSON.stringify(log.spans),
376
+ annotations: JSON.stringify(log.annotations),
377
+ })}`,
378
+ )
379
+ }
380
+
381
+ export function insertError(error: StudioError) {
382
+ return withSql((sql) =>
383
+ sql`INSERT INTO Error ${sql({
384
+ id: error.id,
385
+ fiberId: error.fiberId,
386
+ interrupted: error.interrupted ? 1 : 0,
387
+ prettyPrint: error.prettyPrint,
388
+ details: JSON.stringify(error.details),
389
+ })}`,
390
+ )
377
391
  }
378
392
 
379
393
  export function upsertFiber(
380
- sql: SqlClient.SqlClient,
381
394
  id: string,
382
395
  parentId: string | undefined,
383
396
  spanName: string | undefined,
384
397
  traceId: bigint | undefined,
385
398
  annotations: Record<string, unknown>,
386
399
  ) {
387
- return sql`INSERT OR REPLACE INTO Fiber ${sql({
388
- id,
389
- parentId: parentId ?? null,
390
- spanName: spanName ?? null,
391
- traceId: traceId ?? null,
392
- annotations: JSON.stringify(annotations),
393
- })}`
394
- }
395
-
396
- export function evict(sql: SqlClient.SqlClient, table: string, capacity: number) {
397
- return Effect.gen(function* () {
398
- const [{ cnt }] = yield* sql<{ cnt: number }>`SELECT count(*) as cnt FROM ${sql(table)}`
399
- if (cnt > capacity) {
400
- const excess = cnt - capacity
401
- yield* sql`DELETE FROM ${sql(table)} WHERE rowid IN (SELECT rowid FROM ${sql(table)} ORDER BY rowid LIMIT ${excess})`
402
- }
403
- })
400
+ return withSql((sql) =>
401
+ sql`INSERT OR REPLACE INTO Fiber ${sql({
402
+ id,
403
+ parentId: parentId ?? null,
404
+ spanName: spanName ?? null,
405
+ traceId: traceId ?? null,
406
+ annotations: JSON.stringify(annotations),
407
+ })}`,
408
+ )
404
409
  }
405
410
 
406
- export function runWrite(effect: Effect.Effect<unknown, SqlClient.SqlError>) {
411
+ export function evict(table: string, capacity: number) {
412
+ return withSql((sql) =>
413
+ Effect.gen(function* () {
414
+ const [{ cnt }] = yield* sql<{ cnt: number }>`SELECT count(*) as cnt FROM ${sql(table)}`
415
+ if (cnt > capacity) {
416
+ const excess = cnt - capacity
417
+ yield* sql`DELETE FROM ${sql(table)} WHERE rowid IN (SELECT rowid FROM ${sql(table)} ORDER BY rowid LIMIT ${excess})`
418
+ }
419
+ }),
420
+ )
421
+ }
422
+
423
+ export function runWrite(effect: Effect.Effect<unknown, SqlClient.SqlError, SqlClient.SqlClient>) {
424
+ const sql = store.sql
425
+ if (!sql) return
407
426
  writeQueue = writeQueue
408
- .then(() => Effect.runPromise(Effect.withTracerEnabled(effect, false)).then(() => undefined))
427
+ .then(() =>
428
+ Effect.runPromise(
429
+ Effect.withTracerEnabled(
430
+ Effect.provideService(effect, SqlClient.SqlClient, sql),
431
+ false,
432
+ ),
433
+ ).then(() => undefined),
434
+ )
409
435
  .catch(() => undefined)
410
436
  }
411
437
 
412
438
  let writeQueue = Promise.resolve<void>(undefined)
413
439
 
414
- const noTrace = <A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E> =>
440
+ const noTrace = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
415
441
  Effect.withTracerEnabled(effect, false)
416
442
 
417
- export function allSpans(sql: SqlClient.SqlClient) {
443
+ export function allSpans() {
418
444
  return noTrace(
419
- Effect.map(sql<SpanRow>`SELECT * FROM Span ORDER BY rowid`, (rows) =>
420
- rows.map(deserializeSpan),
445
+ withSql((sql) =>
446
+ Effect.map(sql<SpanRow>`SELECT * FROM Span ORDER BY rowid`, (rows) =>
447
+ rows.map(deserializeSpan),
448
+ ),
421
449
  ),
422
450
  )
423
451
  }
424
452
 
425
- export function allLogs(sql: SqlClient.SqlClient) {
453
+ export function allLogs() {
426
454
  return noTrace(
427
- Effect.map(sql<LogRow>`SELECT * FROM Log ORDER BY rowid`, (rows) => rows.map(deserializeLog)),
455
+ withSql((sql) =>
456
+ Effect.map(sql<LogRow>`SELECT * FROM Log ORDER BY rowid`, (rows) => rows.map(deserializeLog)),
457
+ ),
428
458
  )
429
459
  }
430
460
 
431
- export function allErrors(sql: SqlClient.SqlClient) {
461
+ export function allErrors() {
432
462
  return noTrace(
433
- Effect.map(sql<ErrorRow>`SELECT * FROM Error ORDER BY rowid`, (rows) =>
434
- rows.map(deserializeError),
463
+ withSql((sql) =>
464
+ Effect.map(sql<ErrorRow>`SELECT * FROM Error ORDER BY rowid`, (rows) =>
465
+ rows.map(deserializeError),
466
+ ),
435
467
  ),
436
468
  )
437
469
  }
438
470
 
439
- export function spansByTraceId(sql: SqlClient.SqlClient, traceId: bigint) {
471
+ export function spansByTraceId(traceId: bigint) {
440
472
  return noTrace(
441
- Effect.map(sql<SpanRow>`SELECT * FROM Span WHERE traceId = ${traceId} ORDER BY rowid`, (rows) =>
442
- rows.map(deserializeSpan),
473
+ withSql((sql) =>
474
+ Effect.map(sql<SpanRow>`SELECT * FROM Span WHERE traceId = ${traceId} ORDER BY rowid`, (rows) =>
475
+ rows.map(deserializeSpan),
476
+ ),
443
477
  ),
444
478
  )
445
479
  }
446
480
 
447
- export function spansByFiberId(sql: SqlClient.SqlClient, fiberId: string) {
481
+ export function spansByFiberId(fiberId: string) {
448
482
  return noTrace(
449
- Effect.map(sql<SpanRow>`SELECT * FROM Span WHERE fiberId = ${fiberId} ORDER BY rowid`, (rows) =>
450
- rows.map(deserializeSpan),
483
+ withSql((sql) =>
484
+ Effect.map(sql<SpanRow>`SELECT * FROM Span WHERE fiberId = ${fiberId} ORDER BY rowid`, (rows) =>
485
+ rows.map(deserializeSpan),
486
+ ),
451
487
  ),
452
488
  )
453
489
  }
454
490
 
455
- export function logsByFiberId(sql: SqlClient.SqlClient, fiberId: string) {
491
+ export function logsByFiberId(fiberId: string) {
456
492
  return noTrace(
457
- Effect.map(sql<LogRow>`SELECT * FROM Log WHERE fiberId = ${fiberId} ORDER BY rowid`, (rows) =>
458
- rows.map(deserializeLog),
493
+ withSql((sql) =>
494
+ Effect.map(sql<LogRow>`SELECT * FROM Log WHERE fiberId = ${fiberId} ORDER BY rowid`, (rows) =>
495
+ rows.map(deserializeLog),
496
+ ),
459
497
  ),
460
498
  )
461
499
  }
462
500
 
463
- export function getFiber(sql: SqlClient.SqlClient, fiberId: string) {
501
+ export function getFiber(fiberId: string) {
464
502
  return noTrace(
465
- Effect.map(sql<FiberRow>`SELECT * FROM Fiber WHERE id = ${fiberId}`, (rows) =>
466
- rows.length > 0 ? rows[0] : undefined,
503
+ withSql((sql) =>
504
+ Effect.map(sql<FiberRow>`SELECT * FROM Fiber WHERE id = ${fiberId}`, (rows) =>
505
+ rows.length > 0 ? rows[0] : undefined,
506
+ ),
467
507
  ),
468
508
  )
469
509
  }
470
510
 
471
- export function getParentChain(sql: SqlClient.SqlClient, fiberId: string) {
511
+ export function getParentChain(fiberId: string) {
472
512
  return noTrace(
473
- Effect.gen(function* () {
474
- const chain: Array<string> = []
475
- const visited = new Set<string>()
476
- let current = fiberId
477
- while (true) {
478
- const rows = yield* sql<FiberRow>`SELECT * FROM Fiber WHERE id = ${current}`
479
- if (rows.length === 0 || !rows[0].parentId) break
480
- const parentId = rows[0].parentId
481
- if (visited.has(parentId)) break
482
- chain.push(parentId)
483
- visited.add(parentId)
484
- current = parentId
485
- }
486
- return chain.reverse()
487
- }),
513
+ withSql((sql) =>
514
+ Effect.gen(function* () {
515
+ const chain: Array<string> = []
516
+ const visited = new Set<string>()
517
+ let current = fiberId
518
+ while (true) {
519
+ const rows = yield* sql<FiberRow>`SELECT * FROM Fiber WHERE id = ${current}`
520
+ if (rows.length === 0 || !rows[0].parentId) break
521
+ const parentId = rows[0].parentId
522
+ if (visited.has(parentId)) break
523
+ chain.push(parentId)
524
+ visited.add(parentId)
525
+ current = parentId
526
+ }
527
+ return chain.reverse()
528
+ }),
529
+ ),
488
530
  )
489
531
  }
490
532
 
491
- export function getFiberContext(sql: SqlClient.SqlClient, fiberId: string) {
533
+ export function getFiberContext(fiberId: string) {
492
534
  return noTrace(
493
- Effect.map(
494
- sql<FiberRow>`SELECT * FROM Fiber WHERE id = ${fiberId}`,
495
- (rows): FiberContext | undefined =>
496
- rows.length > 0
497
- ? {
498
- spanName: rows[0].spanName ?? undefined,
499
- traceId: rows[0].traceId != null ? BigInt(rows[0].traceId) : undefined,
500
- annotations: JSON.parse(rows[0].annotations),
501
- }
502
- : undefined,
535
+ withSql((sql) =>
536
+ Effect.map(
537
+ sql<FiberRow>`SELECT * FROM Fiber WHERE id = ${fiberId}`,
538
+ (rows): FiberContext | undefined =>
539
+ rows.length > 0
540
+ ? {
541
+ spanName: rows[0].spanName ?? undefined,
542
+ traceId: rows[0].traceId != null ? BigInt(rows[0].traceId) : undefined,
543
+ annotations: JSON.parse(rows[0].annotations),
544
+ }
545
+ : undefined,
546
+ ),
503
547
  ),
504
548
  )
505
549
  }
@@ -62,11 +62,14 @@ const make = (store: StudioStore.StudioStoreShape): Tracer.Tracer =>
62
62
 
63
63
  StudioStore.runWrite(
64
64
  Effect.zipRight(
65
- StudioStore.insertSpan(store.sql, studioSpan),
66
- StudioStore.evict(store.sql, "Span", store.spanCapacity),
65
+ StudioStore.insertSpan(studioSpan),
66
+ StudioStore.evict("Span", store.spanCapacity),
67
67
  ),
68
68
  )
69
69
  publish(store, { _tag: "SpanStart", span: studioSpan })
70
+ if (parentSpanId === undefined) {
71
+ publish(store, { _tag: "TraceStart", traceId })
72
+ }
70
73
 
71
74
  const attrs = new Map<string, unknown>(Object.entries(attributes))
72
75
  const spanLinks = [...links]
@@ -97,17 +100,20 @@ const make = (store: StudioStore.StudioStoreShape): Tracer.Tracer =>
97
100
  studioSpan.endTime = endTime
98
101
  studioSpan.durationMs = Number(endTime - studioSpan.startTime) / 1_000_000
99
102
  studioSpan.status = Exit.isSuccess(exit) ? "ok" : "error"
100
- StudioStore.runWrite(StudioStore.updateSpan(store.sql, studioSpan))
103
+ StudioStore.runWrite(StudioStore.updateSpan(studioSpan))
101
104
  publish(store, { _tag: "SpanEnd", span: studioSpan })
105
+ if (parentSpanId === undefined) {
106
+ publish(store, { _tag: "TraceEnd", traceId })
107
+ }
102
108
  },
103
109
  attribute(key, value) {
104
110
  attrs.set(key, value)
105
111
  ;(studioSpan.attributes as Record<string, unknown>)[key] = value
106
- StudioStore.runWrite(StudioStore.updateSpan(store.sql, studioSpan))
112
+ StudioStore.runWrite(StudioStore.updateSpan(studioSpan))
107
113
  },
108
114
  event(name, startTime, attributes) {
109
115
  studioSpan.events.push({ name, startTime, attributes })
110
- StudioStore.runWrite(StudioStore.updateSpan(store.sql, studioSpan))
116
+ StudioStore.runWrite(StudioStore.updateSpan(studioSpan))
111
117
  },
112
118
  addLinks(newLinks) {
113
119
  spanLinks.push(...newLinks)
@@ -12,7 +12,7 @@ export default Route.get(
12
12
  const url = new URL(ctx.request.url)
13
13
  const search = url.searchParams.get("errorSearch") || ""
14
14
  const tag = url.searchParams.get("errorTag") || ""
15
- const allErrors = yield* StudioStore.allErrors(StudioStore.store.sql)
15
+ const allErrors = yield* StudioStore.allErrors()
16
16
  const tagSet = new Set<string>()
17
17
  for (const error of allErrors) {
18
18
  for (const d of error.details) {
@@ -67,6 +67,8 @@ export default Route.get(
67
67
  <Errors.ErrorLine error={e} />
68
68
  ))}
69
69
  </div>
70
+
71
+ <div data-init={`@get('${prefix}/errors')`} />
70
72
  </form>
71
73
  </Shell.Shell>
72
74
  )
@@ -75,10 +77,7 @@ export default Route.get(
75
77
  Stream.fromPubSub(StudioStore.store.events).pipe(
76
78
  Stream.filter((e) => e._tag === "Error"),
77
79
  Stream.map((e) => {
78
- const html = Html.renderToString(<Errors.ErrorLine error={e.error} />).replace(
79
- /\n/g,
80
- "",
81
- )
80
+ const html = Html.renderToString(<Errors.ErrorLine error={e.error} />).replace(/\n/g, "")
82
81
  return {
83
82
  event: "datastar-patch-elements",
84
83
  data: `selector #errors-list\nmode prepend\nelements ${html}`,
@@ -12,8 +12,8 @@ export default Route.get(
12
12
  const fiberId = ctx.pathParams.id
13
13
  const fiberName = fiberId.startsWith("#") ? fiberId : `#${fiberId}`
14
14
 
15
- const fiberLogs = yield* StudioStore.logsByFiberId(StudioStore.store.sql, fiberName)
16
- const fiberSpans = yield* StudioStore.spansByFiberId(StudioStore.store.sql, fiberName)
15
+ const fiberLogs = yield* StudioStore.logsByFiberId(fiberName)
16
+ const fiberSpans = yield* StudioStore.spansByFiberId(fiberName)
17
17
 
18
18
  const fiberNum = parseInt(fiberName.slice(1), 10)
19
19
  const counter = StudioStore.fiberIdCounter()
@@ -30,8 +30,8 @@ export default Route.get(
30
30
  else if (fiberNum < counter) alive = "dead"
31
31
  }
32
32
 
33
- const parents = yield* StudioStore.getParentChain(StudioStore.store.sql, fiberName)
34
- const fiberContext = yield* StudioStore.getFiberContext(StudioStore.store.sql, fiberName)
33
+ const parents = yield* StudioStore.getParentChain(fiberName)
34
+ const fiberContext = yield* StudioStore.getFiberContext(fiberName)
35
35
 
36
36
  return (
37
37
  <Shell.Shell prefix={StudioStore.store.prefix} active="fibers">
@@ -8,8 +8,8 @@ import * as Shell from "../../ui/Shell.tsx"
8
8
 
9
9
  export default Route.get(
10
10
  Route.html(function* () {
11
- const logs = yield* StudioStore.allLogs(StudioStore.store.sql)
12
- const spans = yield* StudioStore.allSpans(StudioStore.store.sql)
11
+ const logs = yield* StudioStore.allLogs()
12
+ const spans = yield* StudioStore.allSpans()
13
13
  const fibers = Fibers.collectFibers(logs, spans)
14
14
  return (
15
15
  <Shell.Shell prefix={StudioStore.store.prefix} active="fibers">
@@ -28,8 +28,8 @@ export default Route.get(
28
28
  Stream.filter((e) => e._tag === "SpanStart" || e._tag === "SpanEnd" || e._tag === "Log"),
29
29
  Stream.mapEffect(() =>
30
30
  Effect.gen(function* () {
31
- const logs = yield* StudioStore.allLogs(StudioStore.store.sql)
32
- const spans = yield* StudioStore.allSpans(StudioStore.store.sql)
31
+ const logs = yield* StudioStore.allLogs()
32
+ const spans = yield* StudioStore.allSpans()
33
33
  const fibers = Fibers.collectFibers(logs, spans)
34
34
  const html = Html.renderToString(
35
35
  <Fibers.FiberList fibers={fibers} prefix={StudioStore.store.prefix} />,
@@ -12,7 +12,7 @@ export default Route.get(
12
12
  const url = new URL(ctx.request.url)
13
13
  const level = url.searchParams.get("logLevel") || ""
14
14
  const search = url.searchParams.get("logSearch") || ""
15
- let logs = yield* StudioStore.allLogs(StudioStore.store.sql)
15
+ let logs = yield* StudioStore.allLogs()
16
16
  if (level) logs = logs.filter((l) => l.level === level)
17
17
  if (search) {
18
18
  const lower = search.toLowerCase()
@@ -53,6 +53,8 @@ export default Route.get(
53
53
  <Logs.LogLine log={l} />
54
54
  ))}
55
55
  </div>
56
+
57
+ <div data-init={`@get('${prefix}/logs')`} />
56
58
  </form>
57
59
  </Shell.Shell>
58
60
  )
@@ -18,7 +18,7 @@ export default Route.get(
18
18
  </Shell.Shell>
19
19
  )
20
20
  }
21
- const spans = yield* StudioStore.spansByTraceId(StudioStore.store.sql, traceId)
21
+ const spans = yield* StudioStore.spansByTraceId(traceId)
22
22
 
23
23
  return (
24
24
  <Shell.Shell prefix={StudioStore.store.prefix} active="traces">