effective-progress 0.2.0 → 0.2.1
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 +24 -27
- package/package.json +1 -1
- package/src/api.ts +104 -71
- package/src/runtime.ts +43 -41
- package/src/types.ts +9 -4
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/effective-progress)
|
|
4
4
|
|
|
5
5
|
> [!WARNING]
|
|
6
|
-
> Pre-`1.0.0`, breaking changes may happen in any release. SemVer guarantees will begin at `1.0.0`.
|
|
6
|
+
> Pre-`1.0.0`, breaking changes may happen in any release. SemVer guarantees will begin at `1.0.0`.
|
|
7
7
|
> I recommend using only the `Progress.all` and `Progress.forEach` APIs for now, as they will likely change the least. The lower-level APIs for manual progress bar control are more likely to see breaking changes as I iterate on the design.
|
|
8
8
|
>
|
|
9
9
|
> Please open an issue or reach out if you have any questions or want to contribute!
|
|
@@ -115,6 +115,25 @@ const configured = program.pipe(
|
|
|
115
115
|
Effect.runPromise(configured);
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
Task-level `progressbar` config is optional and inherits from its parent task (or from global `ProgressBarConfig` for root tasks):
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
yield *
|
|
122
|
+
progress.withTask(
|
|
123
|
+
Effect.sleep("1 second"),
|
|
124
|
+
{
|
|
125
|
+
description: "Worker pipeline",
|
|
126
|
+
progressbar: {
|
|
127
|
+
barWidth: 20,
|
|
128
|
+
spinnerFrames: [".", "o", "O", "0"],
|
|
129
|
+
colors: {
|
|
130
|
+
spinner: { kind: "named", value: "magentaBright" },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
```
|
|
136
|
+
|
|
118
137
|
## Terminal service and mocking
|
|
119
138
|
|
|
120
139
|
`effective-progress` now exposes a `ProgressTerminal` service that controls terminal detection and I/O:
|
|
@@ -125,7 +144,7 @@ Effect.runPromise(configured);
|
|
|
125
144
|
- `writeStderr(text)`
|
|
126
145
|
- `withRawInputCapture(effect)`
|
|
127
146
|
|
|
128
|
-
You can provide a mock
|
|
147
|
+
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:
|
|
129
148
|
|
|
130
149
|
```ts
|
|
131
150
|
import { Effect } from "effect";
|
|
@@ -139,45 +158,23 @@ const mockTerminal: Progress.ProgressTerminalService = {
|
|
|
139
158
|
withRawInputCapture: (effect) => effect,
|
|
140
159
|
};
|
|
141
160
|
|
|
142
|
-
const program = Progress.withTask({ description: "work" }
|
|
161
|
+
const program = Progress.withTask(Effect.sleep("100 millis"), { description: "work" }).pipe(
|
|
143
162
|
Effect.provideService(Progress.ProgressTerminal, mockTerminal),
|
|
144
163
|
);
|
|
145
164
|
```
|
|
146
165
|
|
|
147
|
-
##
|
|
148
|
-
|
|
149
|
-
`RendererConfig.isTTY` has been removed.
|
|
150
|
-
TTY mode is now sourced from `ProgressTerminal.isTTY`.
|
|
151
|
-
|
|
152
|
-
Task-level `progressbar` config is optional and inherits from its parent task (or from global `ProgressBarConfig` for root tasks):
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
yield *
|
|
156
|
-
progress.withTask(
|
|
157
|
-
{
|
|
158
|
-
description: "Worker pipeline",
|
|
159
|
-
progressbar: {
|
|
160
|
-
barWidth: 20,
|
|
161
|
-
spinnerFrames: [".", "o", "O", "0"],
|
|
162
|
-
colors: {
|
|
163
|
-
spinner: { kind: "named", value: "magentaBright" },
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
Effect.sleep("1 second"),
|
|
168
|
-
);
|
|
169
|
-
```
|
|
166
|
+
## Manual task control
|
|
170
167
|
|
|
171
168
|
For manual usage, `withTask` captures logs implicitly and provides the current `Task` context:
|
|
172
169
|
|
|
173
170
|
```ts
|
|
174
171
|
const program = Progress.withTask(
|
|
175
|
-
{ description: "Manual task" },
|
|
176
172
|
Effect.gen(function* () {
|
|
177
173
|
const currentTask = yield* Progress.Task;
|
|
178
174
|
yield* Console.log("This log is rendered through progress output", { taskId: currentTask });
|
|
179
175
|
yield* Effect.sleep("1 second");
|
|
180
176
|
}),
|
|
177
|
+
{ description: "Manual task" },
|
|
181
178
|
);
|
|
182
179
|
```
|
|
183
180
|
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
+
import { dual } from "effect/Function";
|
|
2
3
|
import type { Concurrency } from "effect/Types";
|
|
3
4
|
import { Progress, provideProgressService } from "./runtime";
|
|
4
5
|
import { Task } from "./types";
|
|
@@ -31,81 +32,113 @@ export interface ForEachExecutionOptions extends EffectExecutionOptions {
|
|
|
31
32
|
|
|
32
33
|
export type ForEachOptions = TrackOptions & ForEachExecutionOptions;
|
|
33
34
|
|
|
34
|
-
export const withTask
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
export const withTask: {
|
|
36
|
+
<A, E, R>(
|
|
37
|
+
effect: Effect.Effect<A, E, R>,
|
|
38
|
+
options: AddTaskOptions,
|
|
39
|
+
): Effect.Effect<A, E, Exclude<R, Progress | Task>>;
|
|
40
|
+
<A, E, R>(
|
|
41
|
+
options: AddTaskOptions,
|
|
42
|
+
): (effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Progress | Task>>;
|
|
43
|
+
} = dual(2, <A, E, R>(effect: Effect.Effect<A, E, R>, options: AddTaskOptions) =>
|
|
38
44
|
provideProgressService(
|
|
39
45
|
Effect.gen(function* () {
|
|
40
46
|
const progress = yield* Progress;
|
|
41
|
-
return yield* progress.withTask(
|
|
47
|
+
return yield* progress.withTask(effect, options);
|
|
42
48
|
}),
|
|
43
|
-
) as Effect.Effect<A, E, Exclude<R, Progress | Task
|
|
49
|
+
) as Effect.Effect<A, E, Exclude<R, Progress | Task>>,
|
|
50
|
+
);
|
|
44
51
|
|
|
45
|
-
export const all
|
|
46
|
-
const Arg extends ReadonlyArray<Effect.Effect<any, any, any>>,
|
|
47
|
-
|
|
48
|
-
>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
52
|
+
export const all: {
|
|
53
|
+
<const Arg extends ReadonlyArray<Effect.Effect<any, any, any>>, O extends EffectAllExecutionOptions>(
|
|
54
|
+
effects: Arg,
|
|
55
|
+
options: Omit<TrackOptions, "total"> & O,
|
|
56
|
+
): AllReturn<Arg, O>;
|
|
57
|
+
<O extends EffectAllExecutionOptions>(
|
|
58
|
+
options: Omit<TrackOptions, "total"> & O,
|
|
59
|
+
): <const Arg extends ReadonlyArray<Effect.Effect<any, any, any>>>(effects: Arg) => AllReturn<
|
|
60
|
+
Arg,
|
|
61
|
+
O
|
|
62
|
+
>;
|
|
63
|
+
} = dual(
|
|
64
|
+
2,
|
|
65
|
+
<const Arg extends ReadonlyArray<Effect.Effect<any, any, any>>, O extends EffectAllExecutionOptions>(
|
|
66
|
+
effects: Arg,
|
|
67
|
+
options: Omit<TrackOptions, "total"> & O,
|
|
68
|
+
) =>
|
|
69
|
+
provideProgressService(
|
|
70
|
+
Effect.gen(function* () {
|
|
71
|
+
const progress = yield* Progress;
|
|
72
|
+
return yield* progress.withTask(
|
|
73
|
+
Effect.gen(function* () {
|
|
74
|
+
const taskId = yield* Task;
|
|
75
|
+
return yield* Effect.all(
|
|
76
|
+
effects.map((effect) => Effect.tap(effect, () => progress.advanceTask(taskId, 1))),
|
|
77
|
+
{
|
|
78
|
+
concurrency: options.concurrency,
|
|
79
|
+
batching: options.batching,
|
|
80
|
+
discard: options.discard,
|
|
81
|
+
mode: options.mode,
|
|
82
|
+
concurrentFinalizers: options.concurrentFinalizers,
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
}),
|
|
86
|
+
{
|
|
87
|
+
description: options.description,
|
|
88
|
+
total: effects.length,
|
|
89
|
+
transient: options.transient,
|
|
90
|
+
progressbar: options.progressbar,
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
}),
|
|
94
|
+
) as AllReturn<Arg, O>,
|
|
95
|
+
);
|
|
78
96
|
|
|
79
|
-
export const forEach
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
export const forEach: {
|
|
98
|
+
<A, B, E, R>(
|
|
99
|
+
iterable: Iterable<A>,
|
|
100
|
+
f: (item: A, index: number) => Effect.Effect<B, E, R>,
|
|
101
|
+
options: ForEachOptions,
|
|
102
|
+
): Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Progress | Task>>;
|
|
103
|
+
<A, B, E, R>(
|
|
104
|
+
f: (item: A, index: number) => Effect.Effect<B, E, R>,
|
|
105
|
+
options: ForEachOptions,
|
|
106
|
+
): (
|
|
107
|
+
iterable: Iterable<A>,
|
|
108
|
+
) => Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Progress | Task>>;
|
|
109
|
+
} = dual(
|
|
110
|
+
3,
|
|
111
|
+
<A, B, E, R>(
|
|
112
|
+
iterable: Iterable<A>,
|
|
113
|
+
f: (item: A, index: number) => Effect.Effect<B, E, R>,
|
|
114
|
+
options: ForEachOptions,
|
|
115
|
+
) =>
|
|
116
|
+
provideProgressService(
|
|
117
|
+
Effect.gen(function* () {
|
|
118
|
+
const progress = yield* Progress;
|
|
87
119
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
return yield* progress.withTask(
|
|
121
|
+
Effect.gen(function* () {
|
|
122
|
+
const taskId = yield* Task;
|
|
123
|
+
return yield* Effect.forEach(
|
|
124
|
+
iterable,
|
|
125
|
+
(item, index) =>
|
|
126
|
+
Effect.tap(f(item, index), () => progress.advanceTask(taskId, 1)),
|
|
127
|
+
{
|
|
128
|
+
concurrency: options.concurrency,
|
|
129
|
+
batching: options.batching,
|
|
130
|
+
discard: options.discard,
|
|
131
|
+
concurrentFinalizers: options.concurrentFinalizers,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
}),
|
|
135
|
+
{
|
|
136
|
+
description: options.description,
|
|
137
|
+
total: options.total ?? inferTotal(iterable),
|
|
138
|
+
transient: options.transient,
|
|
139
|
+
progressbar: options.progressbar,
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
}),
|
|
143
|
+
) as Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Progress | Task>>,
|
|
144
|
+
);
|
package/src/runtime.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Context, Effect, Exit, FiberRef, Layer, Option, Ref } from "effect";
|
|
2
|
+
import { dual } from "effect/Function";
|
|
2
3
|
import { mergeWith } from "es-toolkit/object";
|
|
3
4
|
import { formatWithOptions } from "node:util";
|
|
4
5
|
import type { PartialDeep } from "type-fest";
|
|
@@ -275,6 +276,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
275
276
|
return;
|
|
276
277
|
}
|
|
277
278
|
|
|
279
|
+
// TODO: Might wanna replace this or make it configurable. Look for other options.
|
|
278
280
|
const message = formatWithOptions(
|
|
279
281
|
{
|
|
280
282
|
colors: isTTY,
|
|
@@ -297,66 +299,66 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
297
299
|
yield* markDirty;
|
|
298
300
|
});
|
|
299
301
|
|
|
300
|
-
const log = (...args: ReadonlyArray<unknown>) =>
|
|
301
|
-
Effect.gen(function* () {
|
|
302
|
-
yield* appendLog(args);
|
|
303
|
-
});
|
|
302
|
+
const log = (...args: ReadonlyArray<unknown>) => appendLog(args);
|
|
304
303
|
|
|
305
304
|
const getTask = (taskId: TaskId) =>
|
|
306
305
|
Ref.get(tasksRef).pipe(Effect.map((tasks) => Option.fromNullable(tasks.get(taskId))));
|
|
307
306
|
|
|
308
307
|
const listTasks = Ref.get(tasksRef).pipe(Effect.map((tasks) => Array.from(tasks.values())));
|
|
309
308
|
|
|
310
|
-
const withTask: ProgressService["withTask"] = (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
309
|
+
const withTask: ProgressService["withTask"] = dual(
|
|
310
|
+
2,
|
|
311
|
+
<A, E, R>(effect: Effect.Effect<A, E, R>, options: AddTaskOptions) =>
|
|
312
|
+
Effect.gen(function* () {
|
|
313
|
+
const outerConsole = yield* Effect.console;
|
|
314
|
+
const inheritedParentId = yield* FiberRef.get(currentParentRef);
|
|
315
|
+
const resolvedParentId =
|
|
316
|
+
options.parentId === undefined ? inheritedParentId : Option.some(options.parentId);
|
|
317
|
+
|
|
318
|
+
const taskId = yield* addTask({
|
|
319
|
+
...options,
|
|
320
|
+
parentId: Option.isSome(resolvedParentId) ? resolvedParentId.value : undefined,
|
|
321
|
+
transient: options.transient ?? Option.isSome(resolvedParentId),
|
|
322
|
+
});
|
|
322
323
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
324
|
+
const exit = yield* Effect.exit(
|
|
325
|
+
Effect.locally(
|
|
326
|
+
Effect.withConsole(
|
|
327
|
+
Effect.provideService(effect, Task, taskId),
|
|
328
|
+
makeProgressConsole(log, outerConsole),
|
|
329
|
+
),
|
|
330
|
+
currentParentRef,
|
|
331
|
+
Option.some(taskId),
|
|
328
332
|
),
|
|
329
|
-
|
|
330
|
-
Option.some(taskId),
|
|
331
|
-
),
|
|
332
|
-
);
|
|
333
|
+
);
|
|
333
334
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
335
|
+
if (Exit.isSuccess(exit)) {
|
|
336
|
+
yield* completeTask(taskId);
|
|
337
|
+
} else {
|
|
338
|
+
yield* failTask(taskId);
|
|
339
|
+
}
|
|
339
340
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
341
|
+
return yield* Exit.match(exit, {
|
|
342
|
+
onFailure: Effect.failCause,
|
|
343
|
+
onSuccess: Effect.succeed,
|
|
344
|
+
});
|
|
345
|
+
}),
|
|
346
|
+
);
|
|
345
347
|
|
|
346
348
|
const trackIterable: ProgressService["trackIterable"] = (iterable, options, f) =>
|
|
347
349
|
withTask(
|
|
348
|
-
{
|
|
349
|
-
description: options.description,
|
|
350
|
-
total: options.total ?? inferTotal(iterable),
|
|
351
|
-
transient: options.transient,
|
|
352
|
-
progressbar: options.progressbar,
|
|
353
|
-
},
|
|
354
350
|
Effect.gen(function* () {
|
|
355
351
|
const taskId = yield* Task;
|
|
356
352
|
return yield* Effect.forEach(iterable, (item, index) =>
|
|
357
353
|
Effect.tap(f(item, index), () => advanceTask(taskId, 1)),
|
|
358
354
|
);
|
|
359
355
|
}),
|
|
356
|
+
{
|
|
357
|
+
description: options.description,
|
|
358
|
+
total: options.total ?? inferTotal(iterable),
|
|
359
|
+
transient: options.transient,
|
|
360
|
+
progressbar: options.progressbar,
|
|
361
|
+
},
|
|
360
362
|
);
|
|
361
363
|
|
|
362
364
|
const service: ProgressService = {
|
package/src/types.ts
CHANGED
|
@@ -114,10 +114,15 @@ export interface ProgressService {
|
|
|
114
114
|
readonly log: (...args: ReadonlyArray<unknown>) => Effect.Effect<void>;
|
|
115
115
|
readonly getTask: (taskId: TaskId) => Effect.Effect<Option.Option<TaskSnapshot>>;
|
|
116
116
|
readonly listTasks: Effect.Effect<ReadonlyArray<TaskSnapshot>>;
|
|
117
|
-
readonly withTask:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
readonly withTask: {
|
|
118
|
+
<A, E, R>(
|
|
119
|
+
effect: Effect.Effect<A, E, R>,
|
|
120
|
+
options: AddTaskOptions,
|
|
121
|
+
): Effect.Effect<A, E, Exclude<R, Task>>;
|
|
122
|
+
<A, E, R>(
|
|
123
|
+
options: AddTaskOptions,
|
|
124
|
+
): (effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Task>>;
|
|
125
|
+
};
|
|
121
126
|
readonly trackIterable: <A, B, E, R>(
|
|
122
127
|
iterable: Iterable<A>,
|
|
123
128
|
options: TrackOptions,
|