effective-progress 0.2.1 → 0.2.2
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 +11 -14
- package/package.json +1 -1
- package/src/api.ts +73 -42
- package/src/console.ts +2 -2
- package/src/renderer.ts +12 -33
- package/src/runtime.ts +121 -93
- package/src/terminal.ts +1 -3
- package/src/types.ts +20 -6
package/README.md
CHANGED
|
@@ -119,19 +119,16 @@ Task-level `progressbar` config is optional and inherits from its parent task (o
|
|
|
119
119
|
|
|
120
120
|
```ts
|
|
121
121
|
yield *
|
|
122
|
-
progress.withTask(
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
colors: {
|
|
130
|
-
spinner: { kind: "named", value: "magentaBright" },
|
|
131
|
-
},
|
|
122
|
+
progress.withTask(Effect.sleep("1 second"), {
|
|
123
|
+
description: "Worker pipeline",
|
|
124
|
+
progressbar: {
|
|
125
|
+
barWidth: 20,
|
|
126
|
+
spinnerFrames: [".", "o", "O", "0"],
|
|
127
|
+
colors: {
|
|
128
|
+
spinner: { kind: "named", value: "magentaBright" },
|
|
132
129
|
},
|
|
133
130
|
},
|
|
134
|
-
);
|
|
131
|
+
});
|
|
135
132
|
```
|
|
136
133
|
|
|
137
134
|
## Terminal service and mocking
|
|
@@ -158,17 +155,17 @@ const mockTerminal: Progress.ProgressTerminalService = {
|
|
|
158
155
|
withRawInputCapture: (effect) => effect,
|
|
159
156
|
};
|
|
160
157
|
|
|
161
|
-
const program = Progress.
|
|
158
|
+
const program = Progress.task(Effect.sleep("100 millis"), { description: "work" }).pipe(
|
|
162
159
|
Effect.provideService(Progress.ProgressTerminal, mockTerminal),
|
|
163
160
|
);
|
|
164
161
|
```
|
|
165
162
|
|
|
166
163
|
## Manual task control
|
|
167
164
|
|
|
168
|
-
For manual usage, `
|
|
165
|
+
For manual usage, `task` captures logs implicitly and provides the current `Task` context:
|
|
169
166
|
|
|
170
167
|
```ts
|
|
171
|
-
const program = Progress.
|
|
168
|
+
const program = Progress.task(
|
|
172
169
|
Effect.gen(function* () {
|
|
173
170
|
const currentTask = yield* Progress.Task;
|
|
174
171
|
yield* Console.log("This log is rendered through progress output", { taskId: currentTask });
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Effect, Exit } from "effect";
|
|
2
2
|
import { dual } from "effect/Function";
|
|
3
3
|
import type { Concurrency } from "effect/Types";
|
|
4
4
|
import { Progress, provideProgressService } from "./runtime";
|
|
@@ -21,9 +21,10 @@ export type AllOptions = Omit<TrackOptions, "total"> & EffectAllExecutionOptions
|
|
|
21
21
|
export type AllReturn<
|
|
22
22
|
Arg extends ReadonlyArray<Effect.Effect<any, any, any>>,
|
|
23
23
|
O extends EffectAllExecutionOptions,
|
|
24
|
-
> =
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
> = [Effect.All.ReturnTuple<Arg, Effect.All.IsDiscard<O>, Effect.All.ExtractMode<O>>] extends [
|
|
25
|
+
Effect.Effect<infer A, infer E, infer R>,
|
|
26
|
+
]
|
|
27
|
+
? Effect.Effect<A, E, Exclude<R, Progress | Task>>
|
|
27
28
|
: never;
|
|
28
29
|
|
|
29
30
|
export interface ForEachExecutionOptions extends EffectExecutionOptions {
|
|
@@ -32,7 +33,7 @@ export interface ForEachExecutionOptions extends EffectExecutionOptions {
|
|
|
32
33
|
|
|
33
34
|
export type ForEachOptions = TrackOptions & ForEachExecutionOptions;
|
|
34
35
|
|
|
35
|
-
export const
|
|
36
|
+
export const task: {
|
|
36
37
|
<A, E, R>(
|
|
37
38
|
effect: Effect.Effect<A, E, R>,
|
|
38
39
|
options: AddTaskOptions,
|
|
@@ -40,48 +41,68 @@ export const withTask: {
|
|
|
40
41
|
<A, E, R>(
|
|
41
42
|
options: AddTaskOptions,
|
|
42
43
|
): (effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Progress | Task>>;
|
|
43
|
-
} = dual(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
} = dual(
|
|
45
|
+
2,
|
|
46
|
+
<A, E, R>(effect: Effect.Effect<A, E, R>, options: AddTaskOptions) =>
|
|
47
|
+
provideProgressService(
|
|
48
|
+
Effect.gen(function* () {
|
|
49
|
+
const progress = yield* Progress;
|
|
50
|
+
return yield* progress.withTask(effect, options);
|
|
51
|
+
}),
|
|
52
|
+
) as Effect.Effect<A, E, Exclude<R, Progress | Task>>,
|
|
50
53
|
);
|
|
51
54
|
|
|
52
55
|
export const all: {
|
|
53
|
-
<
|
|
56
|
+
<
|
|
57
|
+
const Arg extends ReadonlyArray<Effect.Effect<any, any, any>>,
|
|
58
|
+
O extends EffectAllExecutionOptions,
|
|
59
|
+
>(
|
|
54
60
|
effects: Arg,
|
|
55
61
|
options: Omit<TrackOptions, "total"> & O,
|
|
56
62
|
): AllReturn<Arg, O>;
|
|
57
63
|
<O extends EffectAllExecutionOptions>(
|
|
58
64
|
options: Omit<TrackOptions, "total"> & O,
|
|
59
|
-
): <const Arg extends ReadonlyArray<Effect.Effect<any, any, any>>>(
|
|
60
|
-
Arg,
|
|
61
|
-
|
|
62
|
-
>;
|
|
65
|
+
): <const Arg extends ReadonlyArray<Effect.Effect<any, any, any>>>(
|
|
66
|
+
effects: Arg,
|
|
67
|
+
) => AllReturn<Arg, O>;
|
|
63
68
|
} = dual(
|
|
64
69
|
2,
|
|
65
|
-
<
|
|
70
|
+
<
|
|
71
|
+
const Arg extends ReadonlyArray<Effect.Effect<any, any, any>>,
|
|
72
|
+
O extends EffectAllExecutionOptions,
|
|
73
|
+
>(
|
|
66
74
|
effects: Arg,
|
|
67
75
|
options: Omit<TrackOptions, "total"> & O,
|
|
68
76
|
) =>
|
|
69
77
|
provideProgressService(
|
|
70
78
|
Effect.gen(function* () {
|
|
71
79
|
const progress = yield* Progress;
|
|
72
|
-
return yield* progress.
|
|
80
|
+
return yield* progress.runTask(
|
|
73
81
|
Effect.gen(function* () {
|
|
74
82
|
const taskId = yield* Task;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
const exit = yield* Effect.exit(
|
|
84
|
+
Effect.all(
|
|
85
|
+
effects.map((effect) => Effect.tap(effect, () => progress.advanceTask(taskId, 1))),
|
|
86
|
+
{
|
|
87
|
+
concurrency: options.concurrency,
|
|
88
|
+
batching: options.batching,
|
|
89
|
+
discard: options.discard,
|
|
90
|
+
mode: options.mode,
|
|
91
|
+
concurrentFinalizers: options.concurrentFinalizers,
|
|
92
|
+
},
|
|
93
|
+
),
|
|
84
94
|
);
|
|
95
|
+
|
|
96
|
+
if (Exit.isSuccess(exit)) {
|
|
97
|
+
yield* progress.completeTask(taskId);
|
|
98
|
+
} else {
|
|
99
|
+
yield* progress.failTask(taskId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return yield* Exit.match(exit, {
|
|
103
|
+
onFailure: Effect.failCause,
|
|
104
|
+
onSuccess: Effect.succeed,
|
|
105
|
+
});
|
|
85
106
|
}),
|
|
86
107
|
{
|
|
87
108
|
description: options.description,
|
|
@@ -103,9 +124,7 @@ export const forEach: {
|
|
|
103
124
|
<A, B, E, R>(
|
|
104
125
|
f: (item: A, index: number) => Effect.Effect<B, E, R>,
|
|
105
126
|
options: ForEachOptions,
|
|
106
|
-
): (
|
|
107
|
-
iterable: Iterable<A>,
|
|
108
|
-
) => Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Progress | Task>>;
|
|
127
|
+
): (iterable: Iterable<A>) => Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Progress | Task>>;
|
|
109
128
|
} = dual(
|
|
110
129
|
3,
|
|
111
130
|
<A, B, E, R>(
|
|
@@ -117,20 +136,32 @@ export const forEach: {
|
|
|
117
136
|
Effect.gen(function* () {
|
|
118
137
|
const progress = yield* Progress;
|
|
119
138
|
|
|
120
|
-
return yield* progress.
|
|
139
|
+
return yield* progress.runTask(
|
|
121
140
|
Effect.gen(function* () {
|
|
122
141
|
const taskId = yield* Task;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
Effect.tap(f(item, index), () => progress.advanceTask(taskId, 1)),
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
142
|
+
const exit = yield* Effect.exit(
|
|
143
|
+
Effect.forEach(
|
|
144
|
+
iterable,
|
|
145
|
+
(item, index) => Effect.tap(f(item, index), () => progress.advanceTask(taskId, 1)),
|
|
146
|
+
{
|
|
147
|
+
concurrency: options.concurrency,
|
|
148
|
+
batching: options.batching,
|
|
149
|
+
discard: options.discard,
|
|
150
|
+
concurrentFinalizers: options.concurrentFinalizers,
|
|
151
|
+
},
|
|
152
|
+
),
|
|
133
153
|
);
|
|
154
|
+
|
|
155
|
+
if (Exit.isSuccess(exit)) {
|
|
156
|
+
yield* progress.completeTask(taskId);
|
|
157
|
+
} else {
|
|
158
|
+
yield* progress.failTask(taskId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return yield* Exit.match(exit, {
|
|
162
|
+
onFailure: Effect.failCause,
|
|
163
|
+
onSuccess: Effect.succeed,
|
|
164
|
+
});
|
|
134
165
|
}),
|
|
135
166
|
{
|
|
136
167
|
description: options.description,
|
package/src/console.ts
CHANGED
|
@@ -12,7 +12,7 @@ export const makeProgressConsole = (
|
|
|
12
12
|
|
|
13
13
|
const delegate = (effect: Effect.Effect<void, never, never>) => effect;
|
|
14
14
|
|
|
15
|
-
return {
|
|
15
|
+
return Console.Console.of({
|
|
16
16
|
[Console.TypeId]: Console.TypeId,
|
|
17
17
|
assert(condition, ...args) {
|
|
18
18
|
return condition ? Effect.void : log("Assertion failed:", ...args);
|
|
@@ -81,5 +81,5 @@ export const makeProgressConsole = (
|
|
|
81
81
|
unsafeLog(...args);
|
|
82
82
|
},
|
|
83
83
|
},
|
|
84
|
-
};
|
|
84
|
+
});
|
|
85
85
|
};
|
package/src/renderer.ts
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
ProgressBarColorsSchema,
|
|
6
6
|
} from "./colors";
|
|
7
7
|
import type { ProgressTerminalService } from "./terminal";
|
|
8
|
-
import type { ProgressBarConfigShape, RendererConfigShape } from "./types";
|
|
9
|
-
import { DeterminateTaskUnits,
|
|
8
|
+
import type { ProgressBarConfigShape, RendererConfigShape, TaskStore } from "./types";
|
|
9
|
+
import { DeterminateTaskUnits, TaskSnapshot } from "./types";
|
|
10
10
|
|
|
11
11
|
const HIDE_CURSOR = "\x1b[?25l";
|
|
12
12
|
const SHOW_CURSOR = "\x1b[?25h";
|
|
@@ -33,7 +33,7 @@ const buildTaskLine = (
|
|
|
33
33
|
tick: number,
|
|
34
34
|
colors: CompiledProgressBarColors,
|
|
35
35
|
): string => {
|
|
36
|
-
const progressbar = snapshot.
|
|
36
|
+
const progressbar = snapshot.config;
|
|
37
37
|
const prefix = `${" ".repeat(depth)}- ${snapshot.description}: `;
|
|
38
38
|
|
|
39
39
|
if (snapshot.status === "failed") {
|
|
@@ -57,31 +57,8 @@ const buildTaskLine = (
|
|
|
57
57
|
return `${prefix}${colors.spinner(frame)}`;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
const orderTasksForRender = (
|
|
61
|
-
tasks: ReadonlyArray<TaskSnapshot>,
|
|
62
|
-
): ReadonlyArray<{ snapshot: TaskSnapshot; depth: number }> => {
|
|
63
|
-
const byParent = new Map<number | null, Array<TaskSnapshot>>();
|
|
64
|
-
for (const task of tasks) {
|
|
65
|
-
const bucket = byParent.get(task.parentId) ?? [];
|
|
66
|
-
bucket.push(task);
|
|
67
|
-
byParent.set(task.parentId, bucket);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const ordered: Array<{ snapshot: TaskSnapshot; depth: number }> = [];
|
|
71
|
-
const visit = (parentId: number | null, depth: number) => {
|
|
72
|
-
const children = byParent.get(parentId) ?? [];
|
|
73
|
-
for (const child of children) {
|
|
74
|
-
ordered.push({ snapshot: child, depth });
|
|
75
|
-
visit(child.id, depth + 1);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
visit(null, 0);
|
|
80
|
-
return ordered;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
60
|
export const runProgressServiceRenderer = (
|
|
84
|
-
|
|
61
|
+
storeRef: Ref.Ref<TaskStore>,
|
|
85
62
|
logsRef: Ref.Ref<ReadonlyArray<string>>,
|
|
86
63
|
pendingLogsRef: Ref.Ref<ReadonlyArray<string>>,
|
|
87
64
|
dirtyRef: Ref.Ref<boolean>,
|
|
@@ -187,14 +164,16 @@ export const runProgressServiceRenderer = (
|
|
|
187
164
|
const renderFrame = (mode: "tick" | "final") =>
|
|
188
165
|
Effect.gen(function* () {
|
|
189
166
|
const drainedLogs = yield* Ref.getAndSet(pendingLogsRef, []);
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
167
|
+
const store = yield* Ref.get(storeRef);
|
|
168
|
+
const ordered = store.renderOrder.flatMap((row) => {
|
|
169
|
+
const snapshot = store.tasks.get(row.id);
|
|
170
|
+
if (!snapshot || (snapshot.transient && snapshot.status !== "running")) return [];
|
|
171
|
+
return [{ snapshot, depth: row.depth }];
|
|
172
|
+
});
|
|
194
173
|
const frameTick = mode === "final" ? tick + 1 : tick;
|
|
195
174
|
const taskLines = ordered.map(({ snapshot, depth }) => {
|
|
196
175
|
const lineTick = isTTY ? frameTick : 0;
|
|
197
|
-
return buildTaskLine(snapshot, depth, lineTick, getCompiledColors(snapshot.
|
|
176
|
+
return buildTaskLine(snapshot, depth, lineTick, getCompiledColors(snapshot.config));
|
|
198
177
|
});
|
|
199
178
|
|
|
200
179
|
if (isTTY) {
|
|
@@ -248,7 +227,7 @@ export const runProgressServiceRenderer = (
|
|
|
248
227
|
|
|
249
228
|
while (true) {
|
|
250
229
|
const dirty = yield* Ref.getAndSet(dirtyRef, false);
|
|
251
|
-
const tasks = Array.from((yield* Ref.get(
|
|
230
|
+
const tasks = Array.from((yield* Ref.get(storeRef)).tasks.values()).filter(
|
|
252
231
|
(task) => !(task.transient && task.status !== "running"),
|
|
253
232
|
);
|
|
254
233
|
const hasActiveSpinners = tasks.some(
|
package/src/runtime.ts
CHANGED
|
@@ -6,7 +6,13 @@ import type { PartialDeep } from "type-fest";
|
|
|
6
6
|
import { makeProgressConsole } from "./console";
|
|
7
7
|
import { runProgressServiceRenderer } from "./renderer";
|
|
8
8
|
import { ProgressTerminal } from "./terminal";
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
AddTaskOptions,
|
|
11
|
+
ProgressService,
|
|
12
|
+
RenderRow,
|
|
13
|
+
TaskStore,
|
|
14
|
+
UpdateTaskOptions,
|
|
15
|
+
} from "./types";
|
|
10
16
|
import {
|
|
11
17
|
decodeProgressBarConfigSync,
|
|
12
18
|
decodeRendererConfigSync,
|
|
@@ -20,7 +26,6 @@ import {
|
|
|
20
26
|
TaskId,
|
|
21
27
|
TaskSnapshot,
|
|
22
28
|
} from "./types";
|
|
23
|
-
import { inferTotal } from "./utils";
|
|
24
29
|
|
|
25
30
|
const mergeConfig = <T extends Record<PropertyKey, any>>(
|
|
26
31
|
base: T,
|
|
@@ -76,10 +81,39 @@ const updatedSnapshot = (snapshot: TaskSnapshot, options: UpdateTaskOptions): Ta
|
|
|
76
81
|
status: snapshot.status,
|
|
77
82
|
transient: options.transient ?? snapshot.transient,
|
|
78
83
|
units,
|
|
79
|
-
|
|
84
|
+
config: snapshot.config,
|
|
80
85
|
});
|
|
81
86
|
};
|
|
82
87
|
|
|
88
|
+
const findInsertionIndex = (
|
|
89
|
+
renderOrder: ReadonlyArray<RenderRow>,
|
|
90
|
+
parentId: TaskId | null,
|
|
91
|
+
): { index: number; depth: number } => {
|
|
92
|
+
if (parentId === null) {
|
|
93
|
+
return { index: renderOrder.length, depth: 0 };
|
|
94
|
+
}
|
|
95
|
+
const parentIdx = renderOrder.findIndex((row) => row.id === parentId);
|
|
96
|
+
if (parentIdx === -1) return { index: renderOrder.length, depth: 0 };
|
|
97
|
+
const parentDepth = renderOrder[parentIdx]!.depth;
|
|
98
|
+
let i = parentIdx + 1;
|
|
99
|
+
while (i < renderOrder.length && renderOrder[i]!.depth > parentDepth) i++;
|
|
100
|
+
return { index: i, depth: parentDepth + 1 };
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const removeFromRenderOrder = (
|
|
104
|
+
renderOrder: ReadonlyArray<RenderRow>,
|
|
105
|
+
taskId: TaskId,
|
|
106
|
+
): ReadonlyArray<RenderRow> => {
|
|
107
|
+
const idx = renderOrder.findIndex((row) => row.id === taskId);
|
|
108
|
+
if (idx === -1) return renderOrder;
|
|
109
|
+
const taskDepth = renderOrder[idx]!.depth;
|
|
110
|
+
let end = idx + 1;
|
|
111
|
+
while (end < renderOrder.length && renderOrder[end]!.depth > taskDepth) end++;
|
|
112
|
+
const next = [...renderOrder];
|
|
113
|
+
next.splice(idx, end - idx);
|
|
114
|
+
return next;
|
|
115
|
+
};
|
|
116
|
+
|
|
83
117
|
const makeProgressService = Effect.gen(function* () {
|
|
84
118
|
const rendererConfigOption = yield* Effect.serviceOption(RendererConfig);
|
|
85
119
|
const progressBarConfigOption = yield* Effect.serviceOption(ProgressBarConfig);
|
|
@@ -101,7 +135,10 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
101
135
|
const maxRetainedLogLines = Math.max(0, Math.floor(rendererConfig.maxLogLines ?? 0));
|
|
102
136
|
|
|
103
137
|
const nextTaskIdRef = yield* Ref.make(0);
|
|
104
|
-
const
|
|
138
|
+
const storeRef = yield* Ref.make<TaskStore>({
|
|
139
|
+
tasks: new Map<TaskId, TaskSnapshot>(),
|
|
140
|
+
renderOrder: [],
|
|
141
|
+
});
|
|
105
142
|
const logsRef = yield* Ref.make<ReadonlyArray<string>>([]);
|
|
106
143
|
const pendingLogsRef = yield* Ref.make<ReadonlyArray<string>>([]);
|
|
107
144
|
const dirtyRef = yield* Ref.make(true);
|
|
@@ -110,7 +147,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
110
147
|
|
|
111
148
|
yield* Effect.forkIn(
|
|
112
149
|
runProgressServiceRenderer(
|
|
113
|
-
|
|
150
|
+
storeRef,
|
|
114
151
|
logsRef,
|
|
115
152
|
pendingLogsRef,
|
|
116
153
|
dirtyRef,
|
|
@@ -126,7 +163,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
126
163
|
|
|
127
164
|
const addTask = (options: AddTaskOptions) =>
|
|
128
165
|
Effect.gen(function* () {
|
|
129
|
-
const
|
|
166
|
+
const resolvedParentId =
|
|
130
167
|
options.parentId === undefined
|
|
131
168
|
? yield* FiberRef.get(currentParentRef)
|
|
132
169
|
: Option.some(options.parentId);
|
|
@@ -135,27 +172,33 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
135
172
|
options.total === undefined || options.total <= 0
|
|
136
173
|
? new IndeterminateTaskUnits({ spinnerFrame: 0 })
|
|
137
174
|
: new DeterminateTaskUnits({ completed: 0, total: Math.max(0, options.total) });
|
|
138
|
-
const
|
|
139
|
-
const parentSnapshot = Option.isSome(
|
|
140
|
-
|
|
175
|
+
const store = yield* Ref.get(storeRef);
|
|
176
|
+
const parentSnapshot = Option.isSome(resolvedParentId)
|
|
177
|
+
? store.tasks.get(resolvedParentId.value)
|
|
178
|
+
: undefined;
|
|
179
|
+
const inheritedProgressBarConfig = parentSnapshot?.config ?? progressBarConfig;
|
|
141
180
|
const resolvedProgressBarConfig = decodeProgressBarConfigSync(
|
|
142
181
|
mergeConfig(inheritedProgressBarConfig, options.progressbar),
|
|
143
182
|
);
|
|
144
183
|
|
|
184
|
+
const parentIdValue = Option.getOrNull(resolvedParentId);
|
|
145
185
|
const snapshot = new TaskSnapshot({
|
|
146
186
|
id: taskId,
|
|
147
|
-
parentId:
|
|
187
|
+
parentId: parentIdValue,
|
|
148
188
|
description: options.description,
|
|
149
189
|
status: "running",
|
|
150
190
|
transient: options.transient ?? false,
|
|
151
191
|
units,
|
|
152
|
-
|
|
192
|
+
config: resolvedProgressBarConfig,
|
|
153
193
|
});
|
|
154
194
|
|
|
155
|
-
yield* Ref.update(
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
195
|
+
yield* Ref.update(storeRef, (s) => {
|
|
196
|
+
const nextTasks = new Map(s.tasks);
|
|
197
|
+
nextTasks.set(taskId, snapshot);
|
|
198
|
+
const { index, depth } = findInsertionIndex(s.renderOrder, parentIdValue);
|
|
199
|
+
const nextOrder = [...s.renderOrder];
|
|
200
|
+
nextOrder.splice(index, 0, { id: taskId, depth });
|
|
201
|
+
return { tasks: nextTasks, renderOrder: nextOrder };
|
|
159
202
|
});
|
|
160
203
|
yield* markDirty;
|
|
161
204
|
|
|
@@ -163,25 +206,19 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
163
206
|
});
|
|
164
207
|
|
|
165
208
|
const updateTask = (taskId: TaskId, options: UpdateTaskOptions) =>
|
|
166
|
-
Ref.update(
|
|
167
|
-
const snapshot = tasks.get(taskId);
|
|
168
|
-
if (!snapshot)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const next = new Map(tasks);
|
|
173
|
-
next.set(taskId, updatedSnapshot(snapshot, options));
|
|
174
|
-
return next;
|
|
209
|
+
Ref.update(storeRef, (store) => {
|
|
210
|
+
const snapshot = store.tasks.get(taskId);
|
|
211
|
+
if (!snapshot) return store;
|
|
212
|
+
const nextTasks = new Map(store.tasks);
|
|
213
|
+
nextTasks.set(taskId, updatedSnapshot(snapshot, options));
|
|
214
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
175
215
|
}).pipe(Effect.zipRight(markDirty));
|
|
176
216
|
|
|
177
217
|
const advanceTask = (taskId: TaskId, amount = 1) =>
|
|
178
|
-
Ref.update(
|
|
179
|
-
const snapshot = tasks.get(taskId);
|
|
180
|
-
if (!snapshot)
|
|
181
|
-
return tasks;
|
|
182
|
-
}
|
|
218
|
+
Ref.update(storeRef, (store) => {
|
|
219
|
+
const snapshot = store.tasks.get(taskId);
|
|
220
|
+
if (!snapshot) return store;
|
|
183
221
|
|
|
184
|
-
const next = new Map(tasks);
|
|
185
222
|
const units =
|
|
186
223
|
snapshot.units._tag === "DeterminateTaskUnits"
|
|
187
224
|
? new DeterminateTaskUnits({
|
|
@@ -192,7 +229,8 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
192
229
|
spinnerFrame: Math.max(0, snapshot.units.spinnerFrame + amount),
|
|
193
230
|
});
|
|
194
231
|
|
|
195
|
-
|
|
232
|
+
const nextTasks = new Map(store.tasks);
|
|
233
|
+
nextTasks.set(
|
|
196
234
|
taskId,
|
|
197
235
|
new TaskSnapshot({
|
|
198
236
|
id: snapshot.id,
|
|
@@ -201,27 +239,25 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
201
239
|
status: snapshot.status,
|
|
202
240
|
transient: snapshot.transient,
|
|
203
241
|
units,
|
|
204
|
-
|
|
242
|
+
config: snapshot.config,
|
|
205
243
|
}),
|
|
206
244
|
);
|
|
207
245
|
|
|
208
|
-
return
|
|
246
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
209
247
|
}).pipe(Effect.zipRight(markDirty));
|
|
210
248
|
|
|
211
249
|
const completeTask = (taskId: TaskId) =>
|
|
212
|
-
Ref.update(
|
|
213
|
-
const snapshot = tasks.get(taskId);
|
|
214
|
-
if (!snapshot)
|
|
215
|
-
return tasks;
|
|
216
|
-
}
|
|
250
|
+
Ref.update(storeRef, (store) => {
|
|
251
|
+
const snapshot = store.tasks.get(taskId);
|
|
252
|
+
if (!snapshot) return store;
|
|
217
253
|
|
|
218
|
-
const
|
|
254
|
+
const nextTasks = new Map(store.tasks);
|
|
219
255
|
if (snapshot.transient) {
|
|
220
|
-
|
|
221
|
-
return
|
|
256
|
+
nextTasks.delete(taskId);
|
|
257
|
+
return { tasks: nextTasks, renderOrder: removeFromRenderOrder(store.renderOrder, taskId) };
|
|
222
258
|
}
|
|
223
259
|
|
|
224
|
-
|
|
260
|
+
nextTasks.set(
|
|
225
261
|
taskId,
|
|
226
262
|
new TaskSnapshot({
|
|
227
263
|
id: snapshot.id,
|
|
@@ -236,26 +272,24 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
236
272
|
total: snapshot.units.total,
|
|
237
273
|
})
|
|
238
274
|
: snapshot.units,
|
|
239
|
-
|
|
275
|
+
config: snapshot.config,
|
|
240
276
|
}),
|
|
241
277
|
);
|
|
242
|
-
return
|
|
278
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
243
279
|
}).pipe(Effect.zipRight(markDirty));
|
|
244
280
|
|
|
245
281
|
const failTask = (taskId: TaskId) =>
|
|
246
|
-
Ref.update(
|
|
247
|
-
const snapshot = tasks.get(taskId);
|
|
248
|
-
if (!snapshot)
|
|
249
|
-
return tasks;
|
|
250
|
-
}
|
|
282
|
+
Ref.update(storeRef, (store) => {
|
|
283
|
+
const snapshot = store.tasks.get(taskId);
|
|
284
|
+
if (!snapshot) return store;
|
|
251
285
|
|
|
252
|
-
const
|
|
286
|
+
const nextTasks = new Map(store.tasks);
|
|
253
287
|
if (snapshot.transient) {
|
|
254
|
-
|
|
255
|
-
return
|
|
288
|
+
nextTasks.delete(taskId);
|
|
289
|
+
return { tasks: nextTasks, renderOrder: removeFromRenderOrder(store.renderOrder, taskId) };
|
|
256
290
|
}
|
|
257
291
|
|
|
258
|
-
|
|
292
|
+
nextTasks.set(
|
|
259
293
|
taskId,
|
|
260
294
|
new TaskSnapshot({
|
|
261
295
|
id: snapshot.id,
|
|
@@ -264,10 +298,10 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
264
298
|
status: "failed",
|
|
265
299
|
transient: snapshot.transient,
|
|
266
300
|
units: snapshot.units,
|
|
267
|
-
|
|
301
|
+
config: snapshot.config,
|
|
268
302
|
}),
|
|
269
303
|
);
|
|
270
|
-
return
|
|
304
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
271
305
|
}).pipe(Effect.zipRight(markDirty));
|
|
272
306
|
|
|
273
307
|
const appendLog = (args: ReadonlyArray<unknown>) =>
|
|
@@ -302,11 +336,11 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
302
336
|
const log = (...args: ReadonlyArray<unknown>) => appendLog(args);
|
|
303
337
|
|
|
304
338
|
const getTask = (taskId: TaskId) =>
|
|
305
|
-
Ref.get(
|
|
339
|
+
Ref.get(storeRef).pipe(Effect.map((store) => Option.fromNullable(store.tasks.get(taskId))));
|
|
306
340
|
|
|
307
|
-
const listTasks = Ref.get(
|
|
341
|
+
const listTasks = Ref.get(storeRef).pipe(Effect.map((store) => Array.from(store.tasks.values())));
|
|
308
342
|
|
|
309
|
-
const
|
|
343
|
+
const runTask: ProgressService["runTask"] = dual(
|
|
310
344
|
2,
|
|
311
345
|
<A, E, R>(effect: Effect.Effect<A, E, R>, options: AddTaskOptions) =>
|
|
312
346
|
Effect.gen(function* () {
|
|
@@ -321,45 +355,39 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
321
355
|
transient: options.transient ?? Option.isSome(resolvedParentId),
|
|
322
356
|
});
|
|
323
357
|
|
|
324
|
-
|
|
325
|
-
Effect.
|
|
326
|
-
Effect.
|
|
327
|
-
|
|
328
|
-
makeProgressConsole(log, outerConsole),
|
|
329
|
-
),
|
|
330
|
-
currentParentRef,
|
|
331
|
-
Option.some(taskId),
|
|
358
|
+
return yield* Effect.locally(
|
|
359
|
+
Effect.withConsole(
|
|
360
|
+
Effect.provideService(effect, Task, taskId),
|
|
361
|
+
makeProgressConsole(log, outerConsole),
|
|
332
362
|
),
|
|
363
|
+
currentParentRef,
|
|
364
|
+
Option.some(taskId),
|
|
333
365
|
);
|
|
334
|
-
|
|
335
|
-
if (Exit.isSuccess(exit)) {
|
|
336
|
-
yield* completeTask(taskId);
|
|
337
|
-
} else {
|
|
338
|
-
yield* failTask(taskId);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return yield* Exit.match(exit, {
|
|
342
|
-
onFailure: Effect.failCause,
|
|
343
|
-
onSuccess: Effect.succeed,
|
|
344
|
-
});
|
|
345
366
|
}),
|
|
346
367
|
);
|
|
347
368
|
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
369
|
+
const withTask: ProgressService["withTask"] = dual(
|
|
370
|
+
2,
|
|
371
|
+
<A, E, R>(effect: Effect.Effect<A, E, R>, options: AddTaskOptions) =>
|
|
372
|
+
runTask(
|
|
373
|
+
Effect.gen(function* () {
|
|
374
|
+
const taskId = yield* Task;
|
|
375
|
+
const exit = yield* Effect.exit(effect);
|
|
376
|
+
|
|
377
|
+
if (Exit.isSuccess(exit)) {
|
|
378
|
+
yield* completeTask(taskId);
|
|
379
|
+
} else {
|
|
380
|
+
yield* failTask(taskId);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return yield* Exit.match(exit, {
|
|
384
|
+
onFailure: Effect.failCause,
|
|
385
|
+
onSuccess: Effect.succeed,
|
|
386
|
+
});
|
|
387
|
+
}),
|
|
388
|
+
options,
|
|
389
|
+
),
|
|
390
|
+
);
|
|
363
391
|
|
|
364
392
|
const service: ProgressService = {
|
|
365
393
|
addTask,
|
|
@@ -370,8 +398,8 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
370
398
|
log,
|
|
371
399
|
getTask,
|
|
372
400
|
listTasks,
|
|
401
|
+
runTask,
|
|
373
402
|
withTask,
|
|
374
|
-
trackIterable,
|
|
375
403
|
};
|
|
376
404
|
|
|
377
405
|
return Progress.of(service);
|
package/src/terminal.ts
CHANGED
|
@@ -5,9 +5,7 @@ export interface ProgressTerminalService {
|
|
|
5
5
|
readonly stderrRows: Effect.Effect<number | undefined>;
|
|
6
6
|
readonly stderrColumns: Effect.Effect<number | undefined>;
|
|
7
7
|
readonly writeStderr: (text: string) => Effect.Effect<void>;
|
|
8
|
-
readonly withRawInputCapture: <A, E, R>(
|
|
9
|
-
effect: Effect.Effect<A, E, R>,
|
|
10
|
-
) => Effect.Effect<A, E, R>;
|
|
8
|
+
readonly withRawInputCapture: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
const withRawInputCapture: ProgressTerminalService["withRawInputCapture"] = (effect) =>
|
package/src/types.ts
CHANGED
|
@@ -102,9 +102,19 @@ export class TaskSnapshot extends Schema.TaggedClass<TaskSnapshot>()("TaskSnapsh
|
|
|
102
102
|
status: TaskStatusSchema,
|
|
103
103
|
transient: Schema.Boolean,
|
|
104
104
|
units: TaskUnitsSchema,
|
|
105
|
-
|
|
105
|
+
config: ProgressBarConfigSchema,
|
|
106
106
|
}) {}
|
|
107
107
|
|
|
108
|
+
export interface RenderRow {
|
|
109
|
+
readonly id: TaskId;
|
|
110
|
+
readonly depth: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface TaskStore {
|
|
114
|
+
readonly tasks: Map<TaskId, TaskSnapshot>;
|
|
115
|
+
readonly renderOrder: ReadonlyArray<RenderRow>;
|
|
116
|
+
}
|
|
117
|
+
|
|
108
118
|
export interface ProgressService {
|
|
109
119
|
readonly addTask: (options: AddTaskOptions) => Effect.Effect<TaskId>;
|
|
110
120
|
readonly updateTask: (taskId: TaskId, options: UpdateTaskOptions) => Effect.Effect<void>;
|
|
@@ -114,6 +124,15 @@ export interface ProgressService {
|
|
|
114
124
|
readonly log: (...args: ReadonlyArray<unknown>) => Effect.Effect<void>;
|
|
115
125
|
readonly getTask: (taskId: TaskId) => Effect.Effect<Option.Option<TaskSnapshot>>;
|
|
116
126
|
readonly listTasks: Effect.Effect<ReadonlyArray<TaskSnapshot>>;
|
|
127
|
+
readonly runTask: {
|
|
128
|
+
<A, E, R>(
|
|
129
|
+
effect: Effect.Effect<A, E, R>,
|
|
130
|
+
options: AddTaskOptions,
|
|
131
|
+
): Effect.Effect<A, E, Exclude<R, Task>>;
|
|
132
|
+
<A, E, R>(
|
|
133
|
+
options: AddTaskOptions,
|
|
134
|
+
): (effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Task>>;
|
|
135
|
+
};
|
|
117
136
|
readonly withTask: {
|
|
118
137
|
<A, E, R>(
|
|
119
138
|
effect: Effect.Effect<A, E, R>,
|
|
@@ -123,11 +142,6 @@ export interface ProgressService {
|
|
|
123
142
|
options: AddTaskOptions,
|
|
124
143
|
): (effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Task>>;
|
|
125
144
|
};
|
|
126
|
-
readonly trackIterable: <A, B, E, R>(
|
|
127
|
-
iterable: Iterable<A>,
|
|
128
|
-
options: TrackOptions,
|
|
129
|
-
f: (item: A, index: number) => Effect.Effect<B, E, R>,
|
|
130
|
-
) => Effect.Effect<ReadonlyArray<B>, E, Exclude<R, Task>>;
|
|
131
145
|
}
|
|
132
146
|
|
|
133
147
|
export class Task extends Context.Tag("stromseng.dev/Task")<Task, TaskId>() {}
|