effective-progress 0.5.3 → 0.6.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
@@ -11,7 +11,7 @@
11
11
 
12
12
  <img alt="Showcase output" src="docs/images/showcase.gif" width="600" />
13
13
 
14
- `effective-progress` is an [Effect](https://effect.website/)-first terminal progress-bar library with:
14
+ `effective-progress` is an [Effect](https://effect.website/)-native CLI progress bar library with:
15
15
 
16
16
  - multiple nested tree-like progress bars
17
17
  - spinner support for “we have no idea how long this takes” work
@@ -79,10 +79,17 @@ Effect.runPromise(program);
79
79
 
80
80
  <img alt="Nested example output" src="docs/images/nesting.gif" width="600" />
81
81
 
82
+ ### Effect.all modes
83
+
84
+ We support the `either`/`validate` modes of `Effect.all` and render the amount of sucesses/failures.
85
+
86
+ <img alt="Mixed outcomes modes output" src="docs/images/mixedOutcomes.gif" width="600" />
87
+
82
88
  ### Other examples
83
89
 
84
90
  - `examples/simpleExample.ts` - low-boilerplate real-world flow
85
91
  - `examples/advancedExample.ts` - full API usage and manual task control
92
+ - `examples/mixedOutcomes.ts` - fail-fast vs `either`/`validate` with mixed success/failure counters
86
93
  - `examples/showcase.ts` - nested concurrent tasks, spinner workloads, and mixed Effect/Console logging
87
94
  - `examples/performance.ts` - stress-style run with high log volume and deeply nested progress updates
88
95
 
@@ -98,11 +105,20 @@ Effect.runPromise(program);
98
105
 
99
106
  - Rendering is powered by [Ink](https://github.com/vadimdemedes/ink).
100
107
  - Built-in columns are: description, bar, amount/spinner, elapsed, and ETA.
108
+ - Determinate bars are segmented by outcome: succeeded (green), failed (red), and remaining (neutral).
109
+ - Determinate amount text shows counters without prefixes: `<succeeded> <failed> <processed>/<total>`.
101
110
  - Column widths are shared per frame (widest visible cell wins), so rows stay aligned.
102
111
  - Elapsed and ETA reserve stable widths to reduce jitter while tasks transition states.
103
112
  - Layout uses a 100-column baseline and grows when content requires more space.
104
113
  - On narrow terminals, layout compacts to fit available width and tree prefixes are suppressed when description space is too tight.
105
114
 
115
+ ### Mixed outcomes and `mode`
116
+
117
+ - `Progress.all` in default mode (`mode: "default"`) remains fail-fast.
118
+ - In fail-fast runs, unresolved units remain unprocessed.
119
+ - `mode: "either"` and `mode: "validate"` run all effects and keep mixed outcomes in the task counters.
120
+ - Mixed outcomes can still finalize as `done` when all units are accounted for.
121
+
106
122
  ## Manual task control
107
123
 
108
124
  For manual usage, `task` still provides the current `Task` context, while logs continue through your outer `Console`:
@@ -110,11 +126,16 @@ For manual usage, `task` still provides the current `Task` context, while logs c
110
126
  ```ts
111
127
  const program = Progress.task(
112
128
  Effect.gen(function* () {
129
+ const progress = yield* Progress.Progress;
113
130
  const currentTask = yield* Progress.Task;
114
131
  yield* Console.log("This log is handled by the outer Console", { taskId: currentTask });
132
+
133
+ // Manual determinate updates:
134
+ yield* progress.advanceTask(currentTask, 3);
135
+ yield* progress.advanceTaskFailed(currentTask, 1);
115
136
  yield* Effect.sleep("1 second");
116
137
  }),
117
- { description: "Manual task" },
138
+ { description: "Manual task", total: 10 },
118
139
  );
119
140
  ```
120
141
 
@@ -122,35 +143,6 @@ const program = Progress.task(
122
143
 
123
144
  Custom column APIs are not part of the first Ink release. The renderer ships with built-in columns only, and old renderer config APIs (`RendererConfig`, `ProgressBarConfig`, custom column definitions) are intentionally removed in this iteration.
124
145
 
125
- ## Terminal service and mocking
126
-
127
- `effective-progress` now exposes a `ProgressTerminal` service that controls terminal detection and I/O:
128
-
129
- - `isTTY`
130
- - `stderrRows`
131
- - `stderrColumns`
132
- - `writeStderr(text)`
133
- - `withRawInputCapture(effect)`
134
-
135
- You can provide a mock if you want to alter the behavior of terminal detection or if you want to capture the output for testing:
136
-
137
- ```ts
138
- import { Effect } from "effect";
139
- import * as Progress from "effective-progress";
140
-
141
- const mockTerminal: Progress.ProgressTerminalService = {
142
- isTTY: Effect.succeed(true),
143
- stderrRows: Effect.succeed(40),
144
- stderrColumns: Effect.succeed(120),
145
- writeStderr: (_text) => Effect.void,
146
- withRawInputCapture: (effect) => effect,
147
- };
148
-
149
- const program = Progress.task(Effect.sleep("100 millis"), { description: "work" }).pipe(
150
- Effect.provideService(Progress.ProgressTerminal, mockTerminal),
151
- );
152
- ```
153
-
154
146
  ## Notes
155
147
 
156
148
  - As Effect 4.0 is around the corner with some changes to logging, there may be some adjustments needed to align with the new Effect APIs.
package/dist/index.d.mts CHANGED
@@ -8,30 +8,35 @@ type TaskId = typeof TaskIdSchema.Type;
8
8
  declare const TaskId: Brand.Brand.Constructor<number & Brand.Brand<"TaskId">>;
9
9
  declare const TaskStatusSchema: Schema.Literal<["running", "done", "failed"]>;
10
10
  type TaskStatus = typeof TaskStatusSchema.Type;
11
+ declare const TaskCountDisplaySchema: Schema.Literal<["processedOnly", "detailed"]>;
12
+ type TaskCountDisplay = typeof TaskCountDisplaySchema.Type;
11
13
  interface AddTaskOptions {
12
14
  readonly description: string;
13
15
  readonly total?: number;
14
16
  readonly transient?: boolean;
15
17
  readonly parentId?: TaskId;
18
+ readonly countDisplay?: TaskCountDisplay;
16
19
  }
17
20
  interface UpdateTaskOptions {
18
21
  readonly description?: string;
19
- readonly completed?: number;
22
+ readonly succeeded?: number;
23
+ readonly failed?: number;
20
24
  readonly total?: number;
21
25
  readonly transient?: boolean;
26
+ readonly countDisplay?: TaskCountDisplay;
22
27
  }
23
28
  type TrackOptions = Exclude<AddTaskOptions, "parentId">;
24
29
  declare const DeterminateTaskUnits_base: Schema.TaggedClass<DeterminateTaskUnits, "DeterminateTaskUnits", {
25
30
  readonly _tag: Schema.tag<"DeterminateTaskUnits">;
26
31
  } & {
27
- completed: typeof Schema.Number;
32
+ succeeded: typeof Schema.Number;
33
+ failed: typeof Schema.Number;
34
+ processed: typeof Schema.Number;
28
35
  total: typeof Schema.Number;
29
36
  }>;
30
37
  declare class DeterminateTaskUnits extends DeterminateTaskUnits_base {}
31
38
  declare const IndeterminateTaskUnits_base: Schema.TaggedClass<IndeterminateTaskUnits, "IndeterminateTaskUnits", {
32
39
  readonly _tag: Schema.tag<"IndeterminateTaskUnits">;
33
- } & {
34
- spinnerFrame: typeof Schema.Number;
35
40
  }>;
36
41
  declare class IndeterminateTaskUnits extends IndeterminateTaskUnits_base {}
37
42
  declare const TaskUnitsSchema: Schema.Union<[typeof DeterminateTaskUnits, typeof IndeterminateTaskUnits]>;
@@ -43,6 +48,7 @@ declare const TaskSnapshot_base: Schema.TaggedClass<TaskSnapshot, "TaskSnapshot"
43
48
  parentId: Schema.NullOr<Schema.brand<typeof Schema.Number, "TaskId">>;
44
49
  description: typeof Schema.String;
45
50
  status: Schema.Literal<["running", "done", "failed"]>;
51
+ countDisplay: Schema.Literal<["processedOnly", "detailed"]>;
46
52
  transient: typeof Schema.Boolean;
47
53
  units: Schema.Union<[typeof DeterminateTaskUnits, typeof IndeterminateTaskUnits]>;
48
54
  startedAt: typeof Schema.Number;
@@ -61,6 +67,7 @@ interface ProgressService {
61
67
  readonly addTask: (options: AddTaskOptions) => Effect.Effect<TaskId>;
62
68
  readonly updateTask: (taskId: TaskId, options: UpdateTaskOptions) => Effect.Effect<void>;
63
69
  readonly advanceTask: (taskId: TaskId, amount?: number) => Effect.Effect<void>;
70
+ readonly advanceTaskFailed: (taskId: TaskId, amount?: number) => Effect.Effect<void>;
64
71
  readonly completeTask: (taskId: TaskId) => Effect.Effect<void>;
65
72
  readonly failTask: (taskId: TaskId) => Effect.Effect<void>;
66
73
  readonly log: (...args: ReadonlyArray<unknown>) => Effect.Effect<void>;
@@ -85,6 +92,7 @@ declare const TaskAddedEvent_base: Schema.TaggedClass<TaskAddedEvent, "TaskAdded
85
92
  description: typeof Schema.String;
86
93
  total: Schema.optional<typeof Schema.Number>;
87
94
  transient: typeof Schema.Boolean;
95
+ countDisplay: Schema.Literal<["processedOnly", "detailed"]>;
88
96
  }>;
89
97
  declare class TaskAddedEvent extends TaskAddedEvent_base {}
90
98
  declare const TaskUpdatedEvent_base: Schema.TaggedClass<TaskUpdatedEvent, "TaskUpdated", {
@@ -92,9 +100,12 @@ declare const TaskUpdatedEvent_base: Schema.TaggedClass<TaskUpdatedEvent, "TaskU
92
100
  } & {
93
101
  taskId: Schema.brand<typeof Schema.Number, "TaskId">;
94
102
  description: Schema.optional<typeof Schema.String>;
95
- completed: Schema.optional<typeof Schema.Number>;
103
+ succeeded: Schema.optional<typeof Schema.Number>;
104
+ failed: Schema.optional<typeof Schema.Number>;
105
+ processed: Schema.optional<typeof Schema.Number>;
96
106
  total: Schema.optional<typeof Schema.Number>;
97
107
  transient: Schema.optional<typeof Schema.Boolean>;
108
+ countDisplay: Schema.optional<Schema.Literal<["processedOnly", "detailed"]>>;
98
109
  }>;
99
110
  declare class TaskUpdatedEvent extends TaskUpdatedEvent_base {}
100
111
  declare const TaskAdvancedEvent_base: Schema.TaggedClass<TaskAdvancedEvent, "TaskAdvanced", {
@@ -102,6 +113,7 @@ declare const TaskAdvancedEvent_base: Schema.TaggedClass<TaskAdvancedEvent, "Tas
102
113
  } & {
103
114
  taskId: Schema.brand<typeof Schema.Number, "TaskId">;
104
115
  amount: typeof Schema.Number;
116
+ kind: Schema.Literal<["succeeded", "failed"]>;
105
117
  }>;
106
118
  declare class TaskAdvancedEvent extends TaskAdvancedEvent_base {}
107
119
  declare const TaskCompletedEvent_base: Schema.TaggedClass<TaskCompletedEvent, "TaskCompleted", {
@@ -126,7 +138,7 @@ declare const ProgressTaskEventSchema: Schema.Union<[typeof TaskAddedEvent, type
126
138
  type ProgressTaskEvent = typeof ProgressTaskEventSchema.Type;
127
139
  declare const decodeProgressTaskEvent: (u: unknown, overrideOptions?: effect_SchemaAST0.ParseOptions) => TaskAddedEvent | TaskUpdatedEvent | TaskAdvancedEvent | TaskCompletedEvent | TaskFailedEvent | TaskRemovedEvent;
128
140
  //#endregion
129
- //#region src/runtime.d.ts
141
+ //#region src/services/progress.d.ts
130
142
  declare const Progress_base: Context.TagClass<Progress, "stromseng.dev/effective-progress/Progress", ProgressService>;
131
143
  declare class Progress extends Progress_base {
132
144
  static readonly Default: Layer.Layer<Progress, never, never>;
@@ -142,12 +154,12 @@ interface EffectAllExecutionOptions extends EffectExecutionOptions {
142
154
  readonly discard?: boolean;
143
155
  readonly mode?: "default" | "validate" | "either";
144
156
  }
145
- type AllOptions = Omit<TrackOptions, "total"> & EffectAllExecutionOptions;
157
+ type AllOptions = Omit<TrackOptions, "total" | "countDisplay"> & EffectAllExecutionOptions;
146
158
  type AllReturn<Arg extends ReadonlyArray<Effect.Effect<any, any, any>> | Record<string, Effect.Effect<any, any, any>>, O extends EffectAllExecutionOptions> = [[Arg] extends [ReadonlyArray<Effect.Effect<any, any, any>>] ? Effect.All.ReturnTuple<Arg, Effect.All.IsDiscard<O>, Effect.All.ExtractMode<O>> : [Arg] extends [Record<string, Effect.Effect<any, any, any>>] ? Effect.All.ReturnObject<Arg, Effect.All.IsDiscard<O>, Effect.All.ExtractMode<O>> : never] extends [Effect.Effect<infer A, infer E, infer R>] ? Effect.Effect<A, E, Exclude<R, Progress | Task>> : never;
147
159
  interface ForEachExecutionOptions extends EffectExecutionOptions {
148
160
  readonly discard?: false | undefined;
149
161
  }
150
- type ForEachOptions = TrackOptions & ForEachExecutionOptions;
162
+ type ForEachOptions = Omit<TrackOptions, "countDisplay"> & ForEachExecutionOptions;
151
163
  type TaskOptions = AddTaskOptions;
152
164
  declare const task: {
153
165
  <A, E, R>(effect: Effect.Effect<A, E, R>, options: TaskOptions): Effect.Effect<A, E, Exclude<R, Progress | Task>>;
@@ -155,25 +167,22 @@ declare const task: {
155
167
  };
156
168
  type AllArg = ReadonlyArray<Effect.Effect<any, any, any>> | Record<string, Effect.Effect<any, any, any>>;
157
169
  declare const all: {
158
- <const Arg extends AllArg, O extends EffectAllExecutionOptions>(effects: Arg, options: Omit<TrackOptions, "total"> & O): AllReturn<Arg, O>;
159
- <O extends EffectAllExecutionOptions>(options: Omit<TrackOptions, "total"> & O): <const Arg extends AllArg>(effects: Arg) => AllReturn<Arg, O>;
170
+ <const Arg extends AllArg, O extends EffectAllExecutionOptions>(effects: Arg, options: Omit<TrackOptions, "total" | "countDisplay"> & O): AllReturn<Arg, O>;
171
+ <O extends EffectAllExecutionOptions>(options: Omit<TrackOptions, "total" | "countDisplay"> & O): <const Arg extends AllArg>(effects: Arg) => AllReturn<Arg, O>;
160
172
  };
161
173
  declare const forEach: {
162
174
  <A, B, E, R>(iterable: Iterable<A>, f: (item: A, index: number) => Effect.Effect<B, E, R>, options: ForEachOptions): Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Progress | Task>>;
163
175
  <A, B, E, R>(f: (item: A, index: number) => Effect.Effect<B, E, R>, options: ForEachOptions): (iterable: Iterable<A>) => Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Progress | Task>>;
164
176
  };
165
177
  //#endregion
166
- //#region src/terminal.d.ts
167
- interface ProgressTerminalService {
168
- readonly isTTY: Effect.Effect<boolean>;
169
- readonly stderrRows: Effect.Effect<number | undefined>;
170
- readonly stderrColumns: Effect.Effect<number | undefined>;
171
- readonly writeStderr: (text: string) => Effect.Effect<void>;
172
- readonly withRawInputCapture: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
178
+ //#region src/services/stdio.d.ts
179
+ interface ProgressStdioService {
180
+ readonly stdout: NodeJS.WriteStream;
181
+ readonly stderr: NodeJS.WriteStream;
173
182
  }
174
- declare const ProgressTerminal_base: Context.TagClass<ProgressTerminal, "stromseng.dev/ProgressTerminal", ProgressTerminalService>;
175
- declare class ProgressTerminal extends ProgressTerminal_base {
176
- static readonly Default: Layer.Layer<ProgressTerminal, never, never>;
183
+ declare const ProgressStdio_base: Context.TagClass<ProgressStdio, "stromseng.dev/effective-progress/ProgressStdio", ProgressStdioService>;
184
+ declare class ProgressStdio extends ProgressStdio_base {
185
+ static readonly Default: Layer.Layer<ProgressStdio, never, never>;
177
186
  }
178
187
  //#endregion
179
- export { AddTaskOptions, AllOptions, AllReturn, DeterminateTaskUnits, EffectAllExecutionOptions, EffectExecutionOptions, ForEachExecutionOptions, ForEachOptions, IndeterminateTaskUnits, Progress, ProgressService, ProgressTaskEvent, ProgressTaskEventSchema, ProgressTerminal, ProgressTerminalService, RenderRow, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskFailedEvent, TaskId, TaskOptions, TaskRemovedEvent, TaskSnapshot, TaskStatus, TaskStatusSchema, TaskStore, TaskUnits, TaskUnitsSchema, TaskUpdatedEvent, TrackOptions, UpdateTaskOptions, all, decodeProgressTaskEvent, forEach, task };
188
+ export { AddTaskOptions, AllOptions, AllReturn, DeterminateTaskUnits, EffectAllExecutionOptions, EffectExecutionOptions, ForEachExecutionOptions, ForEachOptions, IndeterminateTaskUnits, Progress, ProgressService, ProgressStdio, ProgressStdioService, ProgressTaskEvent, ProgressTaskEventSchema, RenderRow, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplay, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskOptions, TaskRemovedEvent, TaskSnapshot, TaskStatus, TaskStatusSchema, TaskStore, TaskUnits, TaskUnitsSchema, TaskUpdatedEvent, TrackOptions, UpdateTaskOptions, all, decodeProgressTaskEvent, forEach, task };