@vitest-agent/ui 3.0.0 → 4.0.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.
package/README.md CHANGED
@@ -6,14 +6,13 @@
6
6
 
7
7
  > **Part of the [vitest-agent](https://vitest-agent.dev) ecosystem.** Most users want **[@vitest-agent/plugin](https://www.npmjs.com/package/@vitest-agent/plugin)**, which pulls this package in automatically. Install `@vitest-agent/ui` directly only if you build custom rendering on the primitives.
8
8
 
9
- Pure rendering primitives for vitest-agent. Owns the `RunEvent` reducer, the shape-tailored 12-cell dispatcher matrix, two render paths (agent string and React Ink tree), a PubSub channel for live event transport, and the synthesizers that bridge live Vitest data and stored reports into the event taxonomy. React and Ink are required peer dependencies.
9
+ Pure rendering primitives for vitest-agent. Owns the `RunEvent` reducer, the shape-tailored 12-cell dispatcher matrix, two render paths (agent string and React Ink tree), and the synthesizers that bridge live Vitest data and stored reports into the event taxonomy. React and Ink are required peer dependencies.
10
10
 
11
11
  ## Features
12
12
 
13
13
  - **Reducer** — pure `(state, event) => state` fold over the `RunEvent` discriminated union; `reduceRenderStateAll` for one-shot replay
14
14
  - **Dispatcher matrix** — `dispatch` and `dispatchInk` route a `DispatchInputs` to the matching `(RunShape, RunOutcome)` cell; `classifyRunShape` and `classifyOutcome` derive the coordinates
15
15
  - **Ink components** — `StreamApp` and the supporting primitives (`ModuleHeader`, `TestRow`, `CoverageBlock`, `TrendLine`, `FailureSection`, etc.) for live `stream`-mode frames
16
- - **PubSub channel** — `RunEventChannel` Effect tag and helpers (`accumulateUntilFinished`, `forEachRenderState`, `renderStateStream`) for live event transport
17
16
  - **Synthesizers** — `synthesizeRunEvents` from live Vitest modules; `synthesizeFromAgentReport` from a stored `AgentReport`
18
17
  - **Footer builder** — `buildFooter` assembles the L1 MCP-tool-pointer footer; `dominantClassification` picks the most actionable failure class
19
18
 
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Context, Effect, Layer, PubSub, Schema, Scope, Stream } from "effect";
1
+ import { Schema } from "effect";
2
2
  import { FC, ReactElement } from "react";
3
3
  import { CoreStatusName, GlyphSet, Status, Style, TokenName } from "@effected/cli";
4
4
  //#region ../sdk/dist/dev/pkg/index.d.ts
@@ -1193,311 +1193,6 @@ export declare const buildFooter: (inputs: DispatchInputs) => string;
1193
1193
  */
1194
1194
  export declare const formatDisplayDuration: (ms: number) => string;
1195
1195
  //#endregion
1196
- //#region src/pubsub/Channel.d.ts
1197
- declare const RunEventChannel_base: Context.ServiceClass<RunEventChannel, "vitest-agent-ui/RunEventChannel", PubSub.PubSub<{
1198
- readonly _tag: "RunStarted";
1199
- readonly runId: string;
1200
- readonly startedAt: string;
1201
- readonly configHash: string;
1202
- } | {
1203
- readonly _tag: "ModuleQueued";
1204
- readonly modulePath: string;
1205
- readonly projectName?: string | undefined;
1206
- } | {
1207
- readonly _tag: "ModuleStarted";
1208
- readonly modulePath: string;
1209
- readonly startedAt: string;
1210
- readonly projectName?: string | undefined;
1211
- } | {
1212
- readonly _tag: "TestStarted";
1213
- readonly modulePath: string;
1214
- readonly testName: string;
1215
- readonly suitePath: readonly string[];
1216
- } | {
1217
- readonly _tag: "TestFinished";
1218
- readonly modulePath: string;
1219
- readonly testName: string;
1220
- readonly suitePath: readonly string[];
1221
- readonly status: "failed" | "passed" | "pending" | "skipped";
1222
- readonly durationMs: number;
1223
- readonly error?: {
1224
- readonly message: string;
1225
- readonly stack?: string | undefined;
1226
- readonly diff?: string | undefined;
1227
- readonly expected?: string | undefined;
1228
- readonly received?: string | undefined;
1229
- } | undefined;
1230
- readonly timedOut?: boolean | undefined;
1231
- } | {
1232
- readonly _tag: "ModuleFinished";
1233
- readonly modulePath: string;
1234
- readonly passCount: number;
1235
- readonly failCount: number;
1236
- readonly skipCount: number;
1237
- readonly durationMs: number;
1238
- readonly projectName?: string | undefined;
1239
- readonly timeoutCount?: number | undefined;
1240
- readonly tagCounts?: {
1241
- readonly [x: string]: number;
1242
- } | undefined;
1243
- } | {
1244
- readonly _tag: "ModuleCollected";
1245
- readonly modulePath: string;
1246
- readonly testCount: number;
1247
- readonly suiteCount: number;
1248
- } | {
1249
- readonly _tag: "SuiteStarted";
1250
- readonly modulePath: string;
1251
- readonly suitePath: readonly string[];
1252
- readonly suiteName: string;
1253
- } | {
1254
- readonly _tag: "SuiteFinished";
1255
- readonly modulePath: string;
1256
- readonly suitePath: readonly string[];
1257
- readonly suiteName: string;
1258
- readonly passCount: number;
1259
- readonly failCount: number;
1260
- readonly skipCount: number;
1261
- } | {
1262
- readonly _tag: "HookStarted";
1263
- readonly modulePath: string;
1264
- readonly hookType: "afterAll" | "afterEach" | "beforeAll" | "beforeEach";
1265
- readonly scopeName: string;
1266
- } | {
1267
- readonly _tag: "HookFinished";
1268
- readonly modulePath: string;
1269
- readonly hookType: "afterAll" | "afterEach" | "beforeAll" | "beforeEach";
1270
- readonly scopeName: string;
1271
- readonly durationMs: number;
1272
- readonly status: "failed" | "passed";
1273
- readonly error?: {
1274
- readonly message: string;
1275
- readonly stack?: string | undefined;
1276
- readonly diff?: string | undefined;
1277
- readonly expected?: string | undefined;
1278
- readonly received?: string | undefined;
1279
- } | undefined;
1280
- } | {
1281
- readonly _tag: "ConsoleLog";
1282
- readonly modulePath?: string | undefined;
1283
- readonly testName?: string | undefined;
1284
- readonly level: "stderr" | "stdout";
1285
- readonly content: string;
1286
- readonly time: number;
1287
- } | {
1288
- readonly _tag: "RunTimedOut";
1289
- readonly message: string;
1290
- } | {
1291
- readonly _tag: "TestAnnotated";
1292
- readonly modulePath: string;
1293
- readonly testName: string;
1294
- readonly suitePath: readonly string[];
1295
- readonly annotation: string;
1296
- readonly annotationType: string;
1297
- readonly location?: {
1298
- readonly file: string;
1299
- readonly line: number;
1300
- readonly column: number;
1301
- } | undefined;
1302
- readonly attachments: readonly {
1303
- readonly contentType?: string | undefined;
1304
- readonly path?: string | undefined;
1305
- readonly body?: string | undefined;
1306
- readonly bodyEncoding?: "base64" | "utf-8" | undefined;
1307
- readonly byteSize: number;
1308
- }[];
1309
- } | {
1310
- readonly _tag: "TestArtifactRecorded";
1311
- readonly modulePath: string;
1312
- readonly testName: string;
1313
- readonly suitePath: readonly string[];
1314
- readonly artifact: string;
1315
- readonly location?: {
1316
- readonly file: string;
1317
- readonly line: number;
1318
- readonly column: number;
1319
- } | undefined;
1320
- readonly attachments: readonly {
1321
- readonly contentType?: string | undefined;
1322
- readonly path?: string | undefined;
1323
- readonly body?: string | undefined;
1324
- readonly bodyEncoding?: "base64" | "utf-8" | undefined;
1325
- readonly byteSize: number;
1326
- }[];
1327
- } | {
1328
- readonly _tag: "WatcherReady";
1329
- } | {
1330
- readonly _tag: "WatcherRerun";
1331
- readonly triggerFiles: readonly string[];
1332
- readonly reason?: string | undefined;
1333
- } | {
1334
- readonly _tag: "TrendComputed";
1335
- readonly direction: "improving" | "regressing" | "stable";
1336
- readonly runCount: number;
1337
- } | {
1338
- readonly _tag: "CoverageReady";
1339
- readonly metrics: {
1340
- readonly statements: number;
1341
- readonly branches: number;
1342
- readonly functions: number;
1343
- readonly lines: number;
1344
- };
1345
- readonly thresholds: {
1346
- readonly lines?: number | undefined;
1347
- readonly functions?: number | undefined;
1348
- readonly branches?: number | undefined;
1349
- readonly statements?: number | undefined;
1350
- };
1351
- readonly gaps: readonly {
1352
- readonly file: string;
1353
- readonly missing: {
1354
- readonly statements: number;
1355
- readonly branches: number;
1356
- readonly functions: number;
1357
- readonly lines: number;
1358
- };
1359
- readonly uncoveredLines?: string | undefined;
1360
- }[];
1361
- readonly scoped?: boolean | undefined;
1362
- readonly scopedFiles?: number | undefined;
1363
- readonly totalFiles?: number | undefined;
1364
- } | {
1365
- readonly _tag: "ThresholdViolation";
1366
- readonly metric: "branches" | "functions" | "lines" | "statements";
1367
- readonly expected: number;
1368
- readonly actual: number;
1369
- } | {
1370
- readonly _tag: "FailureClassified";
1371
- readonly modulePath: string;
1372
- readonly testName: string;
1373
- readonly classification: "flaky" | "new-failure" | "persistent" | "recovered" | "stable";
1374
- } | {
1375
- readonly _tag: "SuggestedAction";
1376
- readonly severity: "blocker" | "info" | "warn";
1377
- readonly title: string;
1378
- readonly detail: string;
1379
- readonly targetTool?: string | undefined;
1380
- } | {
1381
- readonly _tag: "RunFinished";
1382
- readonly runId: string;
1383
- readonly finishedAt: string;
1384
- readonly passCount: number;
1385
- readonly failCount: number;
1386
- readonly skipCount: number;
1387
- readonly durationMs: number;
1388
- readonly timeoutCount?: number | undefined;
1389
- readonly collectedModules?: number | undefined;
1390
- readonly unhandledErrors?: readonly {
1391
- readonly message: string;
1392
- readonly stack?: string | undefined;
1393
- readonly diff?: string | undefined;
1394
- readonly expected?: string | undefined;
1395
- readonly received?: string | undefined;
1396
- }[] | undefined;
1397
- }>>;
1398
- /**
1399
- * Service tag for the run-event channel. Consumers depend on this tag;
1400
- * the {@link RunEventChannelLive} layer is the single point at which
1401
- * the underlying Effect `PubSub` is constructed.
1402
- *
1403
- * @public
1404
- */
1405
- export declare class RunEventChannel extends RunEventChannel_base {}
1406
- /**
1407
- * Default live layer providing an unbounded Effect `PubSub` for
1408
- * the run-event channel. The PubSub is scoped — when the surrounding
1409
- * scope closes, the channel shuts down and any pending subscribers
1410
- * receive their dequeue closure.
1411
- *
1412
- * Unbounded is the right default for the plugin and CLI: events arrive
1413
- * faster than a slow renderer can drain (e.g., several `TestFinished`
1414
- * per millisecond during a large suite), and dropping events would
1415
- * leave the rendered state inconsistent with the runner's truth.
1416
- * If memory pressure becomes a concern, swap in `PubSub.sliding`
1417
- * with a tuned capacity at the call site.
1418
- *
1419
- * @public
1420
- */
1421
- export declare const RunEventChannelLive: Layer.Layer<RunEventChannel>;
1422
- //#endregion
1423
- //#region src/pubsub/Publisher.d.ts
1424
- /**
1425
- * Emit one event onto the channel.
1426
- *
1427
- * Resolves when the event is enqueued. Returns the success boolean
1428
- * from `PubSub.publish` so callers can detect a shutdown
1429
- * channel if needed; in normal operation the result is always `true`.
1430
- *
1431
- * @param event - the run event to publish
1432
- * @returns an Effect that resolves to the publish success boolean
1433
- * @public
1434
- */
1435
- export declare const publish: (event: RunEvent) => Effect.Effect<boolean, never, RunEventChannel>;
1436
- /**
1437
- * Emit a batch of events onto the channel in order.
1438
- *
1439
- * Useful for the CLI replay path where the entire event sequence is
1440
- * materialized synchronously from a database query before any
1441
- * subscriber needs to read it.
1442
- *
1443
- * @param events - iterable of run events to publish in order
1444
- * @returns an Effect that resolves when all events are enqueued
1445
- * @public
1446
- */
1447
- export declare const publishAll: (events: Iterable<RunEvent>) => Effect.Effect<void, never, RunEventChannel>;
1448
- //#endregion
1449
- //#region src/pubsub/Subscriber.d.ts
1450
- /**
1451
- * Subscribe, fold the run-event stream into a terminal
1452
- * `RenderState`, and return when `RunFinished` arrives.
1453
- *
1454
- * The subscription is scoped — when this effect completes (or is
1455
- * interrupted), the underlying PubSub releases its slot.
1456
- *
1457
- * @param initial - the initial render state to fold from
1458
- * @returns an Effect that resolves to the terminal render state
1459
- * @public
1460
- */
1461
- export declare const accumulateUntilFinished: (initial?: RenderState) => Effect.Effect<RenderState, never, RunEventChannel>;
1462
- /**
1463
- * Run a callback for every projected `RenderState` as events
1464
- * arrive. The stream terminates when the PubSub shuts down (typically
1465
- * when the surrounding scope closes).
1466
- *
1467
- * The host runs this in a forked fiber so the publisher and the
1468
- * renderer can make progress concurrently. Returning an Effect from
1469
- * the callback lets the host suspend further rendering on backpressure
1470
- * (e.g. throttling redraws to the next animation frame).
1471
- *
1472
- * @param onState - callback invoked with each projected render state
1473
- * @param initial - the initial render state to fold from
1474
- * @returns an Effect that completes when the PubSub shuts down
1475
- * @public
1476
- */
1477
- export declare const forEachRenderState: <R, E>(onState: (state: RenderState) => Effect.Effect<void, E, R>, initial?: RenderState) => Effect.Effect<void, E, R | RunEventChannel>;
1478
- /**
1479
- * Build an Effect `Stream` of accumulated states over the channel.
1480
- *
1481
- * Lower-level than {@link forEachRenderState} — useful when the host
1482
- * wants to compose with other stream operators (debouncing, throttling,
1483
- * tee-to-file) before running the terminal sink.
1484
- *
1485
- * @param initial - the initial render state to fold from
1486
- * @returns an Effect that resolves to a Stream of render states
1487
- * @public
1488
- */
1489
- export declare const renderStateStream: (initial?: RenderState) => Effect.Effect<Stream.Stream<RenderState>, never, RunEventChannel>;
1490
- /**
1491
- * Convenience: subscribe and produce a low-level dequeue of raw
1492
- * `RunEvent` values. Mostly for tests and ad-hoc consumers; most
1493
- * application code should prefer {@link forEachRenderState} or
1494
- * {@link renderStateStream}.
1495
- *
1496
- * @returns an Effect that resolves to a dequeue of raw run events
1497
- * @public
1498
- */
1499
- export declare const subscribeRaw: () => Effect.Effect<PubSub.Subscription<RunEvent>, never, RunEventChannel | Scope.Scope>;
1500
- //#endregion
1501
1196
  //#region src/reducer.d.ts
1502
1197
  /**
1503
1198
  * Apply a single event to the previous state and return the next.
package/index.js CHANGED
@@ -3,11 +3,7 @@ import { buildFooter, dominantClassification } from "./dispatcher/footer.js";
3
3
  import { VitestAgentStatus, VitestAgentTokens, inkStyle, statusGlyph, statusInkStyle } from "./theme.js";
4
4
  import { formatDisplayDuration } from "./format-duration.js";
5
5
  import { dispatch, dispatchInk, dispatcherTable } from "./dispatcher/dispatch.js";
6
- import { RunEventChannel, RunEventChannelLive } from "./pubsub/Channel.js";
7
- import { publish, publishAll } from "./pubsub/Publisher.js";
8
6
  import { reduceRenderState, reduceRenderStateAll } from "./reducer.js";
9
- import { accumulateUntilFinished, forEachRenderState, renderStateStream, subscribeRaw } from "./pubsub/Subscriber.js";
10
- import "./pubsub/index.js";
11
7
  import { renderAgent } from "./render-agent.js";
12
8
  import { CountColumns, DURATION_CELL_WIDTH } from "./render-ink/CountColumns.js";
13
9
  import { CoverageBlock } from "./render-ink/CoverageBlock.js";
@@ -34,7 +30,7 @@ import { ActionSeverity, CoverageFile, CoverageGap, CoverageMetric, CoverageRend
34
30
  *
35
31
  * @public
36
32
  */
37
- const CURRENT_UI_VERSION = "3.0.0";
33
+ const CURRENT_UI_VERSION = "4.0.0";
38
34
 
39
35
  //#endregion
40
- export { ActionSeverity, CURRENT_UI_VERSION, CountColumns, CoverageBlock, CoverageFile, CoverageGap, CoverageMetric, CoverageRenderState, DURATION_CELL_WIDTH, FailureRecord, FailureSection, FailuresSection, ModuleHeader, ModuleRecord, ProjectRow, RenderState, RenderTotals, RunEvent, RunEventChannel, RunEventChannelLive, SPINNER_FRAMES, SPINNER_FRAME_MS, SUITE_LOAD_FAILURE_LABEL, StatusIcon, StreamApp, SuggestedActionRecord, SuggestedActions, TagColumns, TestRecord, TestRow, TrendLine, VitestAgentStatus, VitestAgentTokens, accumulateUntilFinished, buildFooter, classifyOutcome, classifyRunShape, dispatch, dispatchInk, dispatcherTable, dominantClassification, forEachRenderState, formatDisplayDuration, initialRenderState, inkStyle, publish, publishAll, reduceRenderState, reduceRenderStateAll, renderAgent, renderStateStream, spinnerFrame, statusGlyph, statusInkStyle, subscribeRaw, synthesizeFromAgentReport, synthesizeRunEvents, tagUnion };
36
+ export { ActionSeverity, CURRENT_UI_VERSION, CountColumns, CoverageBlock, CoverageFile, CoverageGap, CoverageMetric, CoverageRenderState, DURATION_CELL_WIDTH, FailureRecord, FailureSection, FailuresSection, ModuleHeader, ModuleRecord, ProjectRow, RenderState, RenderTotals, RunEvent, SPINNER_FRAMES, SPINNER_FRAME_MS, SUITE_LOAD_FAILURE_LABEL, StatusIcon, StreamApp, SuggestedActionRecord, SuggestedActions, TagColumns, TestRecord, TestRow, TrendLine, VitestAgentStatus, VitestAgentTokens, buildFooter, classifyOutcome, classifyRunShape, dispatch, dispatchInk, dispatcherTable, dominantClassification, formatDisplayDuration, initialRenderState, inkStyle, reduceRenderState, reduceRenderStateAll, renderAgent, spinnerFrame, statusGlyph, statusInkStyle, synthesizeFromAgentReport, synthesizeRunEvents, tagUnion };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest-agent/ui",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "private": false,
5
5
  "description": "Shared event-sourced renderer for vitest-agent. React Ink view for humans, plain-string view for agents, both fed by the same reducer over a RunEvent stream.",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "./package.json": "./package.json"
40
40
  },
41
41
  "dependencies": {
42
- "@vitest-agent/sdk": "5.2.0",
42
+ "@vitest-agent/sdk": "6.0.0",
43
43
  "effect": "^4.0.0"
44
44
  },
45
45
  "peerDependencies": {
package/render-agent.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { formatTotalsLine } from "./counts.js";
2
+ import { formatFailure as formatFailure$1 } from "./dispatcher/helpers.js";
2
3
  import { Fmt } from "@effected/cli";
3
4
 
4
5
  //#region src/render-agent.ts
@@ -47,14 +48,7 @@ const formatModulesSection = (state) => {
47
48
  };
48
49
  const truncate = (line, max) => Fmt.truncate(line, max);
49
50
  const formatFailure = (f, width, includeStack) => {
50
- const suite = f.suitePath.length > 0 ? `${f.suitePath.join(" > ")} > ` : "";
51
- const classification = f.classification !== null ? ` [${f.classification}]` : "";
52
- const lines = [`- ${f.modulePath} > ${suite}${f.testName}${classification}`];
53
- if (f.error?.message !== void 0) {
54
- const firstLine = f.error.message.split("\n", 1)[0] ?? "";
55
- lines.push(` ${truncate(firstLine, Math.max(20, width - 2))}`);
56
- }
57
- if (f.error?.diff !== void 0) for (const diffLine of f.error.diff.split("\n")) lines.push(` ${truncate(diffLine, Math.max(20, width - 2))}`);
51
+ const lines = [...formatFailure$1(f, width)];
58
52
  if (includeStack && f.error?.stack !== void 0) for (const stackLine of f.error.stack.split("\n")) lines.push(` ${truncate(stackLine, Math.max(20, width - 2))}`);
59
53
  return lines.join("\n");
60
54
  };
package/pubsub/Channel.js DELETED
@@ -1,30 +0,0 @@
1
- import { Context, Layer, PubSub } from "effect";
2
-
3
- //#region src/pubsub/Channel.ts
4
- /**
5
- * Service tag for the run-event channel. Consumers depend on this tag;
6
- * the {@link RunEventChannelLive} layer is the single point at which
7
- * the underlying Effect `PubSub` is constructed.
8
- *
9
- * @public
10
- */
11
- var RunEventChannel = class extends Context.Service()("vitest-agent-ui/RunEventChannel") {};
12
- /**
13
- * Default live layer providing an unbounded Effect `PubSub` for
14
- * the run-event channel. The PubSub is scoped — when the surrounding
15
- * scope closes, the channel shuts down and any pending subscribers
16
- * receive their dequeue closure.
17
- *
18
- * Unbounded is the right default for the plugin and CLI: events arrive
19
- * faster than a slow renderer can drain (e.g., several `TestFinished`
20
- * per millisecond during a large suite), and dropping events would
21
- * leave the rendered state inconsistent with the runner's truth.
22
- * If memory pressure becomes a concern, swap in `PubSub.sliding`
23
- * with a tuned capacity at the call site.
24
- *
25
- * @public
26
- */
27
- const RunEventChannelLive = Layer.effect(RunEventChannel, PubSub.unbounded());
28
-
29
- //#endregion
30
- export { RunEventChannel, RunEventChannelLive };
@@ -1,37 +0,0 @@
1
- import { RunEventChannel } from "./Channel.js";
2
- import { Effect, PubSub } from "effect";
3
-
4
- //#region src/pubsub/Publisher.ts
5
- /**
6
- * Emit one event onto the channel.
7
- *
8
- * Resolves when the event is enqueued. Returns the success boolean
9
- * from `PubSub.publish` so callers can detect a shutdown
10
- * channel if needed; in normal operation the result is always `true`.
11
- *
12
- * @param event - the run event to publish
13
- * @returns an Effect that resolves to the publish success boolean
14
- * @public
15
- */
16
- const publish = (event) => Effect.gen(function* () {
17
- const channel = yield* RunEventChannel;
18
- return yield* PubSub.publish(channel, event);
19
- });
20
- /**
21
- * Emit a batch of events onto the channel in order.
22
- *
23
- * Useful for the CLI replay path where the entire event sequence is
24
- * materialized synchronously from a database query before any
25
- * subscriber needs to read it.
26
- *
27
- * @param events - iterable of run events to publish in order
28
- * @returns an Effect that resolves when all events are enqueued
29
- * @public
30
- */
31
- const publishAll = (events) => Effect.gen(function* () {
32
- const channel = yield* RunEventChannel;
33
- yield* PubSub.publishAll(channel, events);
34
- });
35
-
36
- //#endregion
37
- export { publish, publishAll };
@@ -1,77 +0,0 @@
1
- import { RunEventChannel } from "./Channel.js";
2
- import { reduceRenderState } from "../reducer.js";
3
- import { initialRenderState } from "@vitest-agent/sdk";
4
- import { Effect, PubSub, Stream } from "effect";
5
-
6
- //#region src/pubsub/Subscriber.ts
7
- /**
8
- * Subscribe, fold the run-event stream into a terminal
9
- * `RenderState`, and return when `RunFinished` arrives.
10
- *
11
- * The subscription is scoped — when this effect completes (or is
12
- * interrupted), the underlying PubSub releases its slot.
13
- *
14
- * @param initial - the initial render state to fold from
15
- * @returns an Effect that resolves to the terminal render state
16
- * @public
17
- */
18
- const accumulateUntilFinished = (initial = initialRenderState) => Effect.scoped(Effect.gen(function* () {
19
- const channel = yield* RunEventChannel;
20
- const subscription = yield* PubSub.subscribe(channel);
21
- let state = initial;
22
- while (state.phase !== "finished" && state.phase !== "timed-out") {
23
- const event = yield* PubSub.take(subscription);
24
- state = reduceRenderState(state, event);
25
- }
26
- return state;
27
- }));
28
- /**
29
- * Run a callback for every projected `RenderState` as events
30
- * arrive. The stream terminates when the PubSub shuts down (typically
31
- * when the surrounding scope closes).
32
- *
33
- * The host runs this in a forked fiber so the publisher and the
34
- * renderer can make progress concurrently. Returning an Effect from
35
- * the callback lets the host suspend further rendering on backpressure
36
- * (e.g. throttling redraws to the next animation frame).
37
- *
38
- * @param onState - callback invoked with each projected render state
39
- * @param initial - the initial render state to fold from
40
- * @returns an Effect that completes when the PubSub shuts down
41
- * @public
42
- */
43
- const forEachRenderState = (onState, initial = initialRenderState) => Effect.scoped(Effect.gen(function* () {
44
- const channel = yield* RunEventChannel;
45
- yield* Stream.fromPubSub(channel).pipe(Stream.scan(() => initial, reduceRenderState), Stream.runForEach(onState));
46
- }));
47
- /**
48
- * Build an Effect `Stream` of accumulated states over the channel.
49
- *
50
- * Lower-level than {@link forEachRenderState} — useful when the host
51
- * wants to compose with other stream operators (debouncing, throttling,
52
- * tee-to-file) before running the terminal sink.
53
- *
54
- * @param initial - the initial render state to fold from
55
- * @returns an Effect that resolves to a Stream of render states
56
- * @public
57
- */
58
- const renderStateStream = (initial = initialRenderState) => Effect.gen(function* () {
59
- const channel = yield* RunEventChannel;
60
- return Stream.fromPubSub(channel).pipe(Stream.scan(() => initial, reduceRenderState));
61
- });
62
- /**
63
- * Convenience: subscribe and produce a low-level dequeue of raw
64
- * `RunEvent` values. Mostly for tests and ad-hoc consumers; most
65
- * application code should prefer {@link forEachRenderState} or
66
- * {@link renderStateStream}.
67
- *
68
- * @returns an Effect that resolves to a dequeue of raw run events
69
- * @public
70
- */
71
- const subscribeRaw = () => Effect.gen(function* () {
72
- const channel = yield* RunEventChannel;
73
- return yield* PubSub.subscribe(channel);
74
- });
75
-
76
- //#endregion
77
- export { accumulateUntilFinished, forEachRenderState, renderStateStream, subscribeRaw };
package/pubsub/index.js DELETED
@@ -1,5 +0,0 @@
1
- import { RunEventChannel, RunEventChannelLive } from "./Channel.js";
2
- import { publish, publishAll } from "./Publisher.js";
3
- import { accumulateUntilFinished, forEachRenderState, renderStateStream, subscribeRaw } from "./Subscriber.js";
4
-
5
- export { RunEventChannel, RunEventChannelLive, accumulateUntilFinished, forEachRenderState, publish, publishAll, renderStateStream, subscribeRaw };