effective-progress 0.5.1 → 0.5.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/dist/index.d.mts +179 -0
- package/dist/index.mjs +815 -0
- package/package.json +15 -4
- package/index.ts +0 -1
- package/src/api.ts +0 -197
- package/src/index.ts +0 -4
- package/src/ink-renderer/app.tsx +0 -31
- package/src/ink-renderer/columns/amount-column.tsx +0 -17
- package/src/ink-renderer/columns/bar-column.tsx +0 -31
- package/src/ink-renderer/columns/description-column.tsx +0 -7
- package/src/ink-renderer/columns/elapsed-column.tsx +0 -7
- package/src/ink-renderer/columns/eta-column.tsx +0 -18
- package/src/ink-renderer/columns/index.ts +0 -6
- package/src/ink-renderer/columns/types.ts +0 -11
- package/src/ink-renderer/format.ts +0 -55
- package/src/ink-renderer/index.ts +0 -1
- package/src/ink-renderer/layout.ts +0 -145
- package/src/ink-renderer/model.ts +0 -24
- package/src/ink-renderer/service.tsx +0 -121
- package/src/ink-renderer/task-row.tsx +0 -53
- package/src/ink-renderer/tree.ts +0 -62
- package/src/ink-renderer/types.ts +0 -18
- package/src/runtime.ts +0 -396
- package/src/terminal.ts +0 -61
- package/src/types.ts +0 -143
- package/src/utils.ts +0 -20
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
import { Brand, Clock, Context, Effect, Exit, FiberRef, Layer, Option, Ref, Schema } from "effect";
|
|
2
|
+
import { dual } from "effect/Function";
|
|
3
|
+
import { Writable } from "node:stream";
|
|
4
|
+
import { Box, Text, render } from "ink";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
|
|
7
|
+
//#region src/ink-renderer/format.ts
|
|
8
|
+
const SPINNER_FRAMES = [
|
|
9
|
+
"⠋",
|
|
10
|
+
"⠙",
|
|
11
|
+
"⠹",
|
|
12
|
+
"⠸",
|
|
13
|
+
"⠼",
|
|
14
|
+
"⠴",
|
|
15
|
+
"⠦",
|
|
16
|
+
"⠧",
|
|
17
|
+
"⠇",
|
|
18
|
+
"⠏"
|
|
19
|
+
];
|
|
20
|
+
const formatDurationSeconds = (seconds) => {
|
|
21
|
+
const value = Math.max(0, Math.floor(seconds));
|
|
22
|
+
if (value < 60) return `${value}s`;
|
|
23
|
+
if (value < 3600) {
|
|
24
|
+
const mins = Math.floor(value / 60);
|
|
25
|
+
const secs = value % 60;
|
|
26
|
+
return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
|
|
27
|
+
}
|
|
28
|
+
const hours = Math.floor(value / 3600);
|
|
29
|
+
const mins = Math.floor(value % 3600 / 60);
|
|
30
|
+
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
31
|
+
};
|
|
32
|
+
const formatElapsed = (task, now) => {
|
|
33
|
+
return formatDurationSeconds(Math.max(0, (task.completedAt ?? now) - task.startedAt) / 1e3);
|
|
34
|
+
};
|
|
35
|
+
const formatEta = (task, now) => {
|
|
36
|
+
if (task.status !== "running" || task.units._tag !== "DeterminateTaskUnits") return "";
|
|
37
|
+
const { completed, total } = task.units;
|
|
38
|
+
const remaining = total - completed;
|
|
39
|
+
if (completed <= 0 || remaining <= 0) return "";
|
|
40
|
+
const elapsedMillis = Math.max(1, now - task.startedAt);
|
|
41
|
+
return formatDurationSeconds(Math.max(0, Math.floor(elapsedMillis / completed * remaining)) / 1e3);
|
|
42
|
+
};
|
|
43
|
+
const formatAmount = (task, tick) => {
|
|
44
|
+
if (task.units._tag === "DeterminateTaskUnits") {
|
|
45
|
+
const totalText = `${task.units.total}`;
|
|
46
|
+
return `${`${task.units.completed}`.padStart(totalText.length, " ")}/${totalText}`;
|
|
47
|
+
}
|
|
48
|
+
if (task.status === "running") return SPINNER_FRAMES[(task.units.spinnerFrame + tick) % SPINNER_FRAMES.length] ?? SPINNER_FRAMES[0];
|
|
49
|
+
return task.status === "done" ? "✓" : "✗";
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/ink-renderer/tree.ts
|
|
54
|
+
const treeAncestorPrefix = (tree) => tree.ancestorHasNextSibling.slice(1).map((hasNext) => hasNext ? "│ " : " ").join("");
|
|
55
|
+
const renderTreePrefix = (tree) => {
|
|
56
|
+
if (tree.depth <= 0) return "";
|
|
57
|
+
return `${treeAncestorPrefix(tree)}${tree.hasNextSibling ? "├─ " : "└─ "}`;
|
|
58
|
+
};
|
|
59
|
+
const computeTreeInfo = (ordered) => {
|
|
60
|
+
const hasNextSiblingByIndex = Array.from({ length: ordered.length }, () => false);
|
|
61
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
62
|
+
const depth = ordered[i].depth;
|
|
63
|
+
for (let j = i + 1; j < ordered.length; j++) {
|
|
64
|
+
const candidateDepth = ordered[j].depth;
|
|
65
|
+
if (candidateDepth < depth) break;
|
|
66
|
+
if (candidateDepth === depth) {
|
|
67
|
+
hasNextSiblingByIndex[i] = true;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const ancestorStateByDepth = [];
|
|
73
|
+
return ordered.map((entry, index) => {
|
|
74
|
+
const depth = entry.depth;
|
|
75
|
+
ancestorStateByDepth.length = depth;
|
|
76
|
+
const hasChildren = index + 1 < ordered.length && ordered[index + 1] !== void 0 && ordered[index + 1].depth > depth;
|
|
77
|
+
const tree = {
|
|
78
|
+
depth,
|
|
79
|
+
hasNextSibling: hasNextSiblingByIndex[index] ?? false,
|
|
80
|
+
hasChildren,
|
|
81
|
+
ancestorHasNextSibling: [...ancestorStateByDepth]
|
|
82
|
+
};
|
|
83
|
+
ancestorStateByDepth[depth] = hasNextSiblingByIndex[index] ?? false;
|
|
84
|
+
return {
|
|
85
|
+
...entry,
|
|
86
|
+
tree
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/ink-renderer/layout.ts
|
|
93
|
+
const DEFAULT_BAR_WIDTH = 20;
|
|
94
|
+
const MIN_DESCRIPTION_WIDTH = 8;
|
|
95
|
+
const MIN_BAR_WIDTH = 8;
|
|
96
|
+
const MIN_ELAPSED_WIDTH = 3;
|
|
97
|
+
const MIN_AMOUNT_WIDTH = 1;
|
|
98
|
+
const BASELINE_ROW_WIDTH = 100;
|
|
99
|
+
const MIN_DESCRIPTION_COLUMNS_FOR_TREE = 24;
|
|
100
|
+
const RESERVED_ELAPSED_WIDTH_UP_TO_ONE_HOUR = Array.from("59m 59s").length;
|
|
101
|
+
const RESERVED_ETA_WIDTH_UP_TO_ONE_HOUR = Array.from("ETA: 59m 59s").length;
|
|
102
|
+
const textWidth = (text) => Array.from(text).length;
|
|
103
|
+
const computeWidths = (rows, now, tick, terminalColumns, includeTree = true) => {
|
|
104
|
+
let hasDeterminate = false;
|
|
105
|
+
let description = MIN_DESCRIPTION_WIDTH;
|
|
106
|
+
let amount = 1;
|
|
107
|
+
let elapsed = RESERVED_ELAPSED_WIDTH_UP_TO_ONE_HOUR;
|
|
108
|
+
let eta = RESERVED_ETA_WIDTH_UP_TO_ONE_HOUR;
|
|
109
|
+
for (const row of rows) {
|
|
110
|
+
const { task, tree } = row;
|
|
111
|
+
const treePrefix = includeTree ? renderTreePrefix(tree) : "";
|
|
112
|
+
description = Math.max(description, textWidth(`${treePrefix}${task.description}`));
|
|
113
|
+
if (task.units._tag === "DeterminateTaskUnits") hasDeterminate = true;
|
|
114
|
+
amount = Math.max(amount, textWidth(formatAmount(task, tick)));
|
|
115
|
+
elapsed = Math.max(elapsed, textWidth(formatElapsed(task, now)));
|
|
116
|
+
if (task.status === "running" && task.units._tag === "DeterminateTaskUnits") {
|
|
117
|
+
const etaValue = formatEta(task, now);
|
|
118
|
+
const etaText = `ETA: ${etaValue.length > 0 ? etaValue : "--"}`;
|
|
119
|
+
eta = Math.max(eta, textWidth(etaText));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
let widths = {
|
|
123
|
+
description,
|
|
124
|
+
bar: hasDeterminate ? DEFAULT_BAR_WIDTH : 0,
|
|
125
|
+
amount,
|
|
126
|
+
elapsed,
|
|
127
|
+
eta
|
|
128
|
+
};
|
|
129
|
+
const visible = (w) => [
|
|
130
|
+
w.description,
|
|
131
|
+
w.bar,
|
|
132
|
+
w.amount,
|
|
133
|
+
w.elapsed,
|
|
134
|
+
w.eta
|
|
135
|
+
].filter((width) => width > 0);
|
|
136
|
+
const total = (w) => {
|
|
137
|
+
const cols = visible(w);
|
|
138
|
+
return cols.reduce((sum, width) => sum + width, 0) + Math.max(0, cols.length - 1);
|
|
139
|
+
};
|
|
140
|
+
const baselineTarget = Math.max(BASELINE_ROW_WIDTH, total(widths));
|
|
141
|
+
const target = terminalColumns === void 0 ? baselineTarget : Math.max(1, Math.min(Math.max(1, Math.floor(terminalColumns)), baselineTarget));
|
|
142
|
+
if (total(widths) < target) widths.description += target - total(widths);
|
|
143
|
+
else if (total(widths) > target) {
|
|
144
|
+
let overflow = total(widths) - target;
|
|
145
|
+
const reduceBy = (key, min) => {
|
|
146
|
+
if (overflow <= 0) return;
|
|
147
|
+
const current = widths[key];
|
|
148
|
+
if (current <= min) return;
|
|
149
|
+
const reducible = current - min;
|
|
150
|
+
const delta = Math.min(reducible, overflow);
|
|
151
|
+
widths = {
|
|
152
|
+
...widths,
|
|
153
|
+
[key]: current - delta
|
|
154
|
+
};
|
|
155
|
+
overflow -= delta;
|
|
156
|
+
};
|
|
157
|
+
reduceBy("description", MIN_DESCRIPTION_WIDTH);
|
|
158
|
+
reduceBy("eta", 0);
|
|
159
|
+
reduceBy("bar", MIN_BAR_WIDTH);
|
|
160
|
+
reduceBy("bar", 0);
|
|
161
|
+
reduceBy("elapsed", MIN_ELAPSED_WIDTH);
|
|
162
|
+
reduceBy("amount", MIN_AMOUNT_WIDTH);
|
|
163
|
+
reduceBy("description", 0);
|
|
164
|
+
if (total(widths) < target) widths.description += target - total(widths);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
row: total(widths),
|
|
168
|
+
description: widths.description,
|
|
169
|
+
bar: widths.bar,
|
|
170
|
+
amount: widths.amount,
|
|
171
|
+
elapsed: widths.elapsed,
|
|
172
|
+
eta: widths.eta
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
const computeSharedColumnWidths = (rows, now, tick, terminalColumns) => {
|
|
176
|
+
const withTree = computeWidths(rows, now, tick, terminalColumns, true);
|
|
177
|
+
if (withTree.description >= MIN_DESCRIPTION_COLUMNS_FOR_TREE) return {
|
|
178
|
+
...withTree,
|
|
179
|
+
showTree: true
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
...computeWidths(rows, now, tick, terminalColumns, false),
|
|
183
|
+
showTree: false
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/ink-renderer/columns/amount-column.tsx
|
|
189
|
+
const AmountColumn = ({ task, tick }) => {
|
|
190
|
+
const text = formatAmount(task, tick);
|
|
191
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
192
|
+
color: task.status === "failed" ? "red" : task.status === "done" ? "green" : task.units._tag === "DeterminateTaskUnits" ? "whiteBright" : "yellow",
|
|
193
|
+
children: text
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/ink-renderer/columns/bar-column.tsx
|
|
199
|
+
const clamp = (value, minimum, maximum) => Math.min(Math.max(value, minimum), maximum);
|
|
200
|
+
const BarColumn = ({ task, width }) => {
|
|
201
|
+
if (task.units._tag !== "DeterminateTaskUnits") return /* @__PURE__ */ jsx(Text, {});
|
|
202
|
+
const barWidth = Math.max(1, Math.floor(width));
|
|
203
|
+
const safeTotal = Math.max(1, task.units.total);
|
|
204
|
+
const ratio = task.status === "done" ? 1 : clamp(task.units.completed / safeTotal, 0, 1);
|
|
205
|
+
const filled = Math.round(barWidth * ratio);
|
|
206
|
+
const empty = Math.max(0, barWidth - filled);
|
|
207
|
+
const bar = `${"━".repeat(filled)}${"─".repeat(empty)}`;
|
|
208
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
209
|
+
wrap: "truncate-end",
|
|
210
|
+
color: task.status === "failed" ? "red" : task.status === "done" ? "green" : "blue",
|
|
211
|
+
children: bar
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/ink-renderer/columns/description-column.tsx
|
|
217
|
+
const DescriptionColumn = ({ task, tree, showTree }) => /* @__PURE__ */ jsx(Text, {
|
|
218
|
+
wrap: "truncate-end",
|
|
219
|
+
children: `${showTree ? renderTreePrefix(tree) : ""}${task.description}`
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/ink-renderer/columns/elapsed-column.tsx
|
|
224
|
+
const ElapsedColumn = ({ task, now }) => /* @__PURE__ */ jsx(Text, {
|
|
225
|
+
color: "gray",
|
|
226
|
+
children: formatElapsed(task, now)
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/ink-renderer/columns/eta-column.tsx
|
|
231
|
+
const EtaColumn = ({ task, now }) => {
|
|
232
|
+
if (task.status !== "running" || task.units._tag !== "DeterminateTaskUnits") return /* @__PURE__ */ jsx(Text, {});
|
|
233
|
+
const eta = formatEta(task, now);
|
|
234
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
235
|
+
wrap: "truncate-end",
|
|
236
|
+
color: "gray",
|
|
237
|
+
children: eta.length > 0 ? `ETA: ${eta}` : "ETA: --"
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/ink-renderer/task-row.tsx
|
|
243
|
+
const TaskRow = ({ row, now, tick, isTTY, widths }) => {
|
|
244
|
+
const props = {
|
|
245
|
+
task: row.task,
|
|
246
|
+
tree: row.tree,
|
|
247
|
+
now,
|
|
248
|
+
tick,
|
|
249
|
+
isTTY,
|
|
250
|
+
showTree: widths.showTree
|
|
251
|
+
};
|
|
252
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
253
|
+
flexDirection: "row",
|
|
254
|
+
minWidth: widths.row,
|
|
255
|
+
children: [
|
|
256
|
+
/* @__PURE__ */ jsx(Box, {
|
|
257
|
+
width: widths.description,
|
|
258
|
+
flexShrink: 1,
|
|
259
|
+
marginRight: 1,
|
|
260
|
+
children: /* @__PURE__ */ jsx(DescriptionColumn, { ...props })
|
|
261
|
+
}),
|
|
262
|
+
widths.bar > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
263
|
+
width: widths.bar,
|
|
264
|
+
flexShrink: 0,
|
|
265
|
+
marginRight: 1,
|
|
266
|
+
children: /* @__PURE__ */ jsx(BarColumn, {
|
|
267
|
+
...props,
|
|
268
|
+
width: Math.max(1, Math.min(widths.bar, DEFAULT_BAR_WIDTH))
|
|
269
|
+
})
|
|
270
|
+
}) : null,
|
|
271
|
+
/* @__PURE__ */ jsx(Box, {
|
|
272
|
+
width: widths.amount,
|
|
273
|
+
flexShrink: 0,
|
|
274
|
+
marginRight: 1,
|
|
275
|
+
children: /* @__PURE__ */ jsx(AmountColumn, { ...props })
|
|
276
|
+
}),
|
|
277
|
+
/* @__PURE__ */ jsx(Box, {
|
|
278
|
+
width: widths.elapsed,
|
|
279
|
+
flexShrink: 0,
|
|
280
|
+
marginRight: 1,
|
|
281
|
+
children: /* @__PURE__ */ jsx(ElapsedColumn, { ...props })
|
|
282
|
+
}),
|
|
283
|
+
widths.eta > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
284
|
+
width: widths.eta,
|
|
285
|
+
flexShrink: 0,
|
|
286
|
+
children: /* @__PURE__ */ jsx(EtaColumn, { ...props })
|
|
287
|
+
}) : null
|
|
288
|
+
]
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region src/ink-renderer/app.tsx
|
|
294
|
+
const ProgressApp = ({ rows, now, tick, isTTY, terminalColumns }) => {
|
|
295
|
+
const widths = computeSharedColumnWidths(rows, now, tick, terminalColumns);
|
|
296
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
297
|
+
flexDirection: "column",
|
|
298
|
+
children: rows.map((row) => /* @__PURE__ */ jsx(TaskRow, {
|
|
299
|
+
row,
|
|
300
|
+
now,
|
|
301
|
+
tick,
|
|
302
|
+
isTTY,
|
|
303
|
+
widths
|
|
304
|
+
}, row.task.id))
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/ink-renderer/model.ts
|
|
310
|
+
const orderedVisibleTasks = (store) => store.renderOrder.flatMap((row) => {
|
|
311
|
+
const snapshot = store.tasks.get(row.id);
|
|
312
|
+
if (!snapshot || snapshot.transient && snapshot.status !== "running") return [];
|
|
313
|
+
return [{
|
|
314
|
+
snapshot,
|
|
315
|
+
depth: row.depth
|
|
316
|
+
}];
|
|
317
|
+
});
|
|
318
|
+
const toTaskRows = (store) => computeTreeInfo(orderedVisibleTasks(store)).map((entry) => ({
|
|
319
|
+
task: entry.snapshot,
|
|
320
|
+
tree: entry.tree
|
|
321
|
+
}));
|
|
322
|
+
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/ink-renderer/service.tsx
|
|
325
|
+
const RENDER_INTERVAL_MILLIS = 100;
|
|
326
|
+
const hasRunningSpinners = (tasks) => tasks.some((task) => task.status === "running" && task.units._tag === "IndeterminateTaskUnits");
|
|
327
|
+
const createInkWritable = (terminal) => new Writable({ write(chunk, _encoding, callback) {
|
|
328
|
+
try {
|
|
329
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : `${chunk}`;
|
|
330
|
+
Effect.runSync(terminal.writeStderr(text));
|
|
331
|
+
callback();
|
|
332
|
+
} catch (error) {
|
|
333
|
+
callback(error);
|
|
334
|
+
}
|
|
335
|
+
} });
|
|
336
|
+
const makeDefaultInkRenderer = () => ({ run: (storeRef, dirtyRef, terminal, isTTY) => Effect.gen(function* () {
|
|
337
|
+
const output = createInkWritable(terminal);
|
|
338
|
+
let instance;
|
|
339
|
+
let tick = 0;
|
|
340
|
+
let rendererActive = false;
|
|
341
|
+
const renderStore = (store, now, terminalColumns) => Effect.sync(() => {
|
|
342
|
+
const app = /* @__PURE__ */ jsx(ProgressApp, {
|
|
343
|
+
rows: toTaskRows(store),
|
|
344
|
+
now,
|
|
345
|
+
tick,
|
|
346
|
+
isTTY,
|
|
347
|
+
terminalColumns
|
|
348
|
+
});
|
|
349
|
+
if (instance === void 0) {
|
|
350
|
+
instance = render(app, {
|
|
351
|
+
stdout: output,
|
|
352
|
+
stderr: output,
|
|
353
|
+
patchConsole: true,
|
|
354
|
+
exitOnCtrlC: false,
|
|
355
|
+
debug: false
|
|
356
|
+
});
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
instance.rerender(app);
|
|
360
|
+
});
|
|
361
|
+
return yield* Effect.gen(function* () {
|
|
362
|
+
rendererActive = true;
|
|
363
|
+
while (true) {
|
|
364
|
+
const dirty = yield* Ref.getAndSet(dirtyRef, false);
|
|
365
|
+
const store = yield* Ref.get(storeRef);
|
|
366
|
+
const tasks = Array.from(store.tasks.values()).filter((task) => !(task.transient && task.status !== "running"));
|
|
367
|
+
if (dirty || hasRunningSpinners(tasks)) yield* renderStore(store, yield* Clock.currentTimeMillis, isTTY ? yield* terminal.stderrColumns : void 0);
|
|
368
|
+
tick += 1;
|
|
369
|
+
yield* Effect.sleep(RENDER_INTERVAL_MILLIS);
|
|
370
|
+
}
|
|
371
|
+
}).pipe(Effect.ensuring(Effect.gen(function* () {
|
|
372
|
+
if (rendererActive) yield* renderStore(yield* Ref.get(storeRef), yield* Clock.currentTimeMillis, isTTY ? yield* terminal.stderrColumns : void 0);
|
|
373
|
+
yield* Effect.sync(() => {
|
|
374
|
+
instance?.unmount();
|
|
375
|
+
});
|
|
376
|
+
})));
|
|
377
|
+
}) });
|
|
378
|
+
var InkRenderer = class InkRenderer extends Context.Tag("stromseng.dev/effective-progress/InkRenderer")() {
|
|
379
|
+
static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeDefaultInkRenderer()));
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region src/terminal.ts
|
|
384
|
+
const withRawInputCapture = (effect) => Effect.suspend(() => {
|
|
385
|
+
if (!process.stdin.isTTY) return effect;
|
|
386
|
+
const stdin = process.stdin;
|
|
387
|
+
const wasRaw = Boolean(stdin.isRaw);
|
|
388
|
+
const onData = (chunk) => {
|
|
389
|
+
if (chunk.length === 1 && chunk[0] === 3) process.kill(process.pid, "SIGINT");
|
|
390
|
+
};
|
|
391
|
+
return Effect.acquireUseRelease(Effect.sync(() => {
|
|
392
|
+
stdin.resume();
|
|
393
|
+
stdin.setRawMode?.(true);
|
|
394
|
+
stdin.on("data", onData);
|
|
395
|
+
}), () => effect, () => Effect.sync(() => {
|
|
396
|
+
try {
|
|
397
|
+
stdin.off("data", onData);
|
|
398
|
+
stdin.setRawMode?.(wasRaw);
|
|
399
|
+
stdin.pause();
|
|
400
|
+
} catch {}
|
|
401
|
+
}));
|
|
402
|
+
});
|
|
403
|
+
const defaultTerminalService = {
|
|
404
|
+
isTTY: Effect.sync(() => Boolean(process.stderr.isTTY)),
|
|
405
|
+
stderrRows: Effect.sync(() => process.stderr.rows),
|
|
406
|
+
stderrColumns: Effect.sync(() => process.stderr.columns),
|
|
407
|
+
writeStderr: (text) => Effect.sync(() => {
|
|
408
|
+
process.stderr.write(text);
|
|
409
|
+
}),
|
|
410
|
+
withRawInputCapture
|
|
411
|
+
};
|
|
412
|
+
var ProgressTerminal = class ProgressTerminal extends Context.Tag("stromseng.dev/ProgressTerminal")() {
|
|
413
|
+
static Default = Layer.succeed(ProgressTerminal, defaultTerminalService);
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/types.ts
|
|
418
|
+
const TaskIdSchema = Schema.Number.pipe(Schema.brand("TaskId"));
|
|
419
|
+
const TaskId = Brand.nominal();
|
|
420
|
+
const TaskStatusSchema = Schema.Literal("running", "done", "failed");
|
|
421
|
+
var DeterminateTaskUnits = class extends Schema.TaggedClass()("DeterminateTaskUnits", {
|
|
422
|
+
completed: Schema.Number,
|
|
423
|
+
total: Schema.Number
|
|
424
|
+
}) {};
|
|
425
|
+
var IndeterminateTaskUnits = class extends Schema.TaggedClass()("IndeterminateTaskUnits", { spinnerFrame: Schema.Number }) {};
|
|
426
|
+
const TaskUnitsSchema = Schema.Union(DeterminateTaskUnits, IndeterminateTaskUnits);
|
|
427
|
+
var TaskSnapshot = class extends Schema.TaggedClass()("TaskSnapshot", {
|
|
428
|
+
id: TaskIdSchema,
|
|
429
|
+
parentId: Schema.NullOr(TaskIdSchema),
|
|
430
|
+
description: Schema.String,
|
|
431
|
+
status: TaskStatusSchema,
|
|
432
|
+
transient: Schema.Boolean,
|
|
433
|
+
units: TaskUnitsSchema,
|
|
434
|
+
startedAt: Schema.Number,
|
|
435
|
+
completedAt: Schema.NullOr(Schema.Number)
|
|
436
|
+
}) {};
|
|
437
|
+
var Task = class extends Context.Tag("stromseng.dev/effective-progress/Task")() {};
|
|
438
|
+
var TaskAddedEvent = class extends Schema.TaggedClass()("TaskAdded", {
|
|
439
|
+
taskId: TaskIdSchema,
|
|
440
|
+
parentId: Schema.NullOr(TaskIdSchema),
|
|
441
|
+
description: Schema.String,
|
|
442
|
+
total: Schema.optional(Schema.Number),
|
|
443
|
+
transient: Schema.Boolean
|
|
444
|
+
}) {};
|
|
445
|
+
var TaskUpdatedEvent = class extends Schema.TaggedClass()("TaskUpdated", {
|
|
446
|
+
taskId: TaskIdSchema,
|
|
447
|
+
description: Schema.optional(Schema.String),
|
|
448
|
+
completed: Schema.optional(Schema.Number),
|
|
449
|
+
total: Schema.optional(Schema.Number),
|
|
450
|
+
transient: Schema.optional(Schema.Boolean)
|
|
451
|
+
}) {};
|
|
452
|
+
var TaskAdvancedEvent = class extends Schema.TaggedClass()("TaskAdvanced", {
|
|
453
|
+
taskId: TaskIdSchema,
|
|
454
|
+
amount: Schema.Number
|
|
455
|
+
}) {};
|
|
456
|
+
var TaskCompletedEvent = class extends Schema.TaggedClass()("TaskCompleted", { taskId: TaskIdSchema }) {};
|
|
457
|
+
var TaskFailedEvent = class extends Schema.TaggedClass()("TaskFailed", { taskId: TaskIdSchema }) {};
|
|
458
|
+
var TaskRemovedEvent = class extends Schema.TaggedClass()("TaskRemoved", { taskId: TaskIdSchema }) {};
|
|
459
|
+
const ProgressTaskEventSchema = Schema.Union(TaskAddedEvent, TaskUpdatedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskFailedEvent, TaskRemovedEvent);
|
|
460
|
+
const decodeProgressTaskEvent = Schema.decodeUnknownSync(ProgressTaskEventSchema);
|
|
461
|
+
|
|
462
|
+
//#endregion
|
|
463
|
+
//#region src/runtime.ts
|
|
464
|
+
const updatedSnapshot = (snapshot, options) => {
|
|
465
|
+
const currentUnits = snapshot.units;
|
|
466
|
+
const units = (() => {
|
|
467
|
+
if (options.total !== void 0) {
|
|
468
|
+
if (options.total <= 0) return new IndeterminateTaskUnits({ spinnerFrame: 0 });
|
|
469
|
+
const completed = options.completed ?? (currentUnits._tag === "DeterminateTaskUnits" ? currentUnits.completed : 0);
|
|
470
|
+
return new DeterminateTaskUnits({
|
|
471
|
+
completed: Math.max(0, completed),
|
|
472
|
+
total: Math.max(0, options.total)
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (currentUnits._tag === "DeterminateTaskUnits") {
|
|
476
|
+
if (options.completed === void 0) return currentUnits;
|
|
477
|
+
return new DeterminateTaskUnits({
|
|
478
|
+
completed: Math.max(0, options.completed),
|
|
479
|
+
total: currentUnits.total
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return currentUnits;
|
|
483
|
+
})();
|
|
484
|
+
return new TaskSnapshot({
|
|
485
|
+
id: snapshot.id,
|
|
486
|
+
parentId: snapshot.parentId,
|
|
487
|
+
description: options.description ?? snapshot.description,
|
|
488
|
+
status: snapshot.status,
|
|
489
|
+
transient: options.transient ?? snapshot.transient,
|
|
490
|
+
units,
|
|
491
|
+
startedAt: snapshot.startedAt,
|
|
492
|
+
completedAt: snapshot.completedAt
|
|
493
|
+
});
|
|
494
|
+
};
|
|
495
|
+
const withTransient = (snapshot, transient) => new TaskSnapshot({
|
|
496
|
+
id: snapshot.id,
|
|
497
|
+
parentId: snapshot.parentId,
|
|
498
|
+
description: snapshot.description,
|
|
499
|
+
status: snapshot.status,
|
|
500
|
+
transient,
|
|
501
|
+
units: snapshot.units,
|
|
502
|
+
startedAt: snapshot.startedAt,
|
|
503
|
+
completedAt: snapshot.completedAt
|
|
504
|
+
});
|
|
505
|
+
const findInsertionIndex = (renderOrder, parentId) => {
|
|
506
|
+
if (parentId === null) return {
|
|
507
|
+
index: renderOrder.length,
|
|
508
|
+
depth: 0
|
|
509
|
+
};
|
|
510
|
+
const parentIdx = renderOrder.findIndex((row) => row.id === parentId);
|
|
511
|
+
if (parentIdx === -1) return {
|
|
512
|
+
index: renderOrder.length,
|
|
513
|
+
depth: 0
|
|
514
|
+
};
|
|
515
|
+
const parentDepth = renderOrder[parentIdx].depth;
|
|
516
|
+
let i = parentIdx + 1;
|
|
517
|
+
while (i < renderOrder.length && renderOrder[i].depth > parentDepth) i++;
|
|
518
|
+
return {
|
|
519
|
+
index: i,
|
|
520
|
+
depth: parentDepth + 1
|
|
521
|
+
};
|
|
522
|
+
};
|
|
523
|
+
const removeFromRenderOrder = (renderOrder, taskId) => {
|
|
524
|
+
const idx = renderOrder.findIndex((row) => row.id === taskId);
|
|
525
|
+
if (idx === -1) return renderOrder;
|
|
526
|
+
const taskDepth = renderOrder[idx].depth;
|
|
527
|
+
let end = idx + 1;
|
|
528
|
+
while (end < renderOrder.length && renderOrder[end].depth > taskDepth) end++;
|
|
529
|
+
const next = [...renderOrder];
|
|
530
|
+
next.splice(idx, end - idx);
|
|
531
|
+
return next;
|
|
532
|
+
};
|
|
533
|
+
const makeProgressService = Effect.gen(function* () {
|
|
534
|
+
const terminal = yield* ProgressTerminal;
|
|
535
|
+
const inkRenderer = yield* InkRenderer;
|
|
536
|
+
const outerConsole = yield* Effect.console;
|
|
537
|
+
const isTTY = yield* terminal.isTTY;
|
|
538
|
+
const nextTaskIdRef = yield* Ref.make(0);
|
|
539
|
+
const storeRef = yield* Ref.make({
|
|
540
|
+
tasks: /* @__PURE__ */ new Map(),
|
|
541
|
+
renderOrder: []
|
|
542
|
+
});
|
|
543
|
+
const dirtyRef = yield* Ref.make(true);
|
|
544
|
+
const currentParentRef = yield* FiberRef.make(Option.none());
|
|
545
|
+
const scope = yield* Effect.scope;
|
|
546
|
+
const markDirty = Ref.set(dirtyRef, true);
|
|
547
|
+
const log = (...args) => args.length === 0 ? Effect.void : outerConsole.log(...args);
|
|
548
|
+
yield* Effect.forkIn(inkRenderer.run(storeRef, dirtyRef, terminal, isTTY), scope);
|
|
549
|
+
yield* Effect.sleep("0 millis");
|
|
550
|
+
const addTask = (options) => Effect.gen(function* () {
|
|
551
|
+
const resolvedParentId = options.parentId === void 0 ? yield* FiberRef.get(currentParentRef) : Option.some(options.parentId);
|
|
552
|
+
const taskId = TaskId(yield* Ref.updateAndGet(nextTaskIdRef, (id) => id + 1));
|
|
553
|
+
const units = options.total === void 0 || options.total <= 0 ? new IndeterminateTaskUnits({ spinnerFrame: 0 }) : new DeterminateTaskUnits({
|
|
554
|
+
completed: 0,
|
|
555
|
+
total: Math.max(0, options.total)
|
|
556
|
+
});
|
|
557
|
+
const store = yield* Ref.get(storeRef);
|
|
558
|
+
const parentSnapshot = Option.isSome(resolvedParentId) ? store.tasks.get(resolvedParentId.value) : void 0;
|
|
559
|
+
const now = yield* Clock.currentTimeMillis;
|
|
560
|
+
const parentIdValue = Option.getOrNull(resolvedParentId);
|
|
561
|
+
const snapshot = new TaskSnapshot({
|
|
562
|
+
id: taskId,
|
|
563
|
+
parentId: parentIdValue,
|
|
564
|
+
description: options.description,
|
|
565
|
+
status: "running",
|
|
566
|
+
transient: parentSnapshot?.transient ?? options.transient ?? false,
|
|
567
|
+
units,
|
|
568
|
+
startedAt: now,
|
|
569
|
+
completedAt: null
|
|
570
|
+
});
|
|
571
|
+
yield* Ref.update(storeRef, (s) => {
|
|
572
|
+
const nextTasks = new Map(s.tasks);
|
|
573
|
+
nextTasks.set(taskId, snapshot);
|
|
574
|
+
const { index, depth } = findInsertionIndex(s.renderOrder, parentIdValue);
|
|
575
|
+
const nextOrder = [...s.renderOrder];
|
|
576
|
+
nextOrder.splice(index, 0, {
|
|
577
|
+
id: taskId,
|
|
578
|
+
depth
|
|
579
|
+
});
|
|
580
|
+
return {
|
|
581
|
+
tasks: nextTasks,
|
|
582
|
+
renderOrder: nextOrder
|
|
583
|
+
};
|
|
584
|
+
});
|
|
585
|
+
yield* markDirty;
|
|
586
|
+
return taskId;
|
|
587
|
+
});
|
|
588
|
+
const updateTask = (taskId, options) => Ref.update(storeRef, (store) => {
|
|
589
|
+
const snapshot = store.tasks.get(taskId);
|
|
590
|
+
if (!snapshot) return store;
|
|
591
|
+
const nextTasks = new Map(store.tasks);
|
|
592
|
+
const nextSnapshot = updatedSnapshot(snapshot, options);
|
|
593
|
+
nextTasks.set(taskId, nextSnapshot);
|
|
594
|
+
if (options.transient !== void 0) for (const [candidateId, candidate] of store.tasks.entries()) {
|
|
595
|
+
if (candidateId === taskId) continue;
|
|
596
|
+
let parentId = candidate.parentId;
|
|
597
|
+
let isDescendant = false;
|
|
598
|
+
while (parentId !== null) {
|
|
599
|
+
if (parentId === taskId) {
|
|
600
|
+
isDescendant = true;
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
parentId = store.tasks.get(parentId)?.parentId ?? null;
|
|
604
|
+
}
|
|
605
|
+
if (isDescendant) nextTasks.set(candidateId, withTransient(candidate, nextSnapshot.transient));
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
tasks: nextTasks,
|
|
609
|
+
renderOrder: store.renderOrder
|
|
610
|
+
};
|
|
611
|
+
}).pipe(Effect.zipRight(markDirty));
|
|
612
|
+
const advanceTask = (taskId, amount = 1) => Ref.update(storeRef, (store) => {
|
|
613
|
+
const snapshot = store.tasks.get(taskId);
|
|
614
|
+
if (!snapshot) return store;
|
|
615
|
+
const units = snapshot.units._tag === "DeterminateTaskUnits" ? new DeterminateTaskUnits({
|
|
616
|
+
completed: Math.min(snapshot.units.total, snapshot.units.completed + amount),
|
|
617
|
+
total: snapshot.units.total
|
|
618
|
+
}) : new IndeterminateTaskUnits({ spinnerFrame: Math.max(0, snapshot.units.spinnerFrame + amount) });
|
|
619
|
+
const nextTasks = new Map(store.tasks);
|
|
620
|
+
nextTasks.set(taskId, new TaskSnapshot({
|
|
621
|
+
id: snapshot.id,
|
|
622
|
+
parentId: snapshot.parentId,
|
|
623
|
+
description: snapshot.description,
|
|
624
|
+
status: snapshot.status,
|
|
625
|
+
transient: snapshot.transient,
|
|
626
|
+
units,
|
|
627
|
+
startedAt: snapshot.startedAt,
|
|
628
|
+
completedAt: snapshot.completedAt
|
|
629
|
+
}));
|
|
630
|
+
return {
|
|
631
|
+
tasks: nextTasks,
|
|
632
|
+
renderOrder: store.renderOrder
|
|
633
|
+
};
|
|
634
|
+
}).pipe(Effect.zipRight(markDirty));
|
|
635
|
+
const completeTask = (taskId) => Effect.gen(function* () {
|
|
636
|
+
const now = yield* Clock.currentTimeMillis;
|
|
637
|
+
yield* Ref.update(storeRef, (store) => {
|
|
638
|
+
const snapshot = store.tasks.get(taskId);
|
|
639
|
+
if (!snapshot) return store;
|
|
640
|
+
const nextTasks = new Map(store.tasks);
|
|
641
|
+
if (snapshot.transient) {
|
|
642
|
+
nextTasks.delete(taskId);
|
|
643
|
+
return {
|
|
644
|
+
tasks: nextTasks,
|
|
645
|
+
renderOrder: removeFromRenderOrder(store.renderOrder, taskId)
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
nextTasks.set(taskId, new TaskSnapshot({
|
|
649
|
+
id: snapshot.id,
|
|
650
|
+
parentId: snapshot.parentId,
|
|
651
|
+
description: snapshot.description,
|
|
652
|
+
status: "done",
|
|
653
|
+
transient: snapshot.transient,
|
|
654
|
+
units: snapshot.units._tag === "DeterminateTaskUnits" ? new DeterminateTaskUnits({
|
|
655
|
+
completed: snapshot.units.total,
|
|
656
|
+
total: snapshot.units.total
|
|
657
|
+
}) : snapshot.units,
|
|
658
|
+
startedAt: snapshot.startedAt,
|
|
659
|
+
completedAt: now
|
|
660
|
+
}));
|
|
661
|
+
return {
|
|
662
|
+
tasks: nextTasks,
|
|
663
|
+
renderOrder: store.renderOrder
|
|
664
|
+
};
|
|
665
|
+
});
|
|
666
|
+
yield* markDirty;
|
|
667
|
+
});
|
|
668
|
+
const failTask = (taskId) => Effect.gen(function* () {
|
|
669
|
+
const now = yield* Clock.currentTimeMillis;
|
|
670
|
+
yield* Ref.update(storeRef, (store) => {
|
|
671
|
+
const snapshot = store.tasks.get(taskId);
|
|
672
|
+
if (!snapshot) return store;
|
|
673
|
+
const nextTasks = new Map(store.tasks);
|
|
674
|
+
if (snapshot.transient) {
|
|
675
|
+
nextTasks.delete(taskId);
|
|
676
|
+
return {
|
|
677
|
+
tasks: nextTasks,
|
|
678
|
+
renderOrder: removeFromRenderOrder(store.renderOrder, taskId)
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
nextTasks.set(taskId, new TaskSnapshot({
|
|
682
|
+
id: snapshot.id,
|
|
683
|
+
parentId: snapshot.parentId,
|
|
684
|
+
description: snapshot.description,
|
|
685
|
+
status: "failed",
|
|
686
|
+
transient: snapshot.transient,
|
|
687
|
+
units: snapshot.units,
|
|
688
|
+
startedAt: snapshot.startedAt,
|
|
689
|
+
completedAt: now
|
|
690
|
+
}));
|
|
691
|
+
return {
|
|
692
|
+
tasks: nextTasks,
|
|
693
|
+
renderOrder: store.renderOrder
|
|
694
|
+
};
|
|
695
|
+
});
|
|
696
|
+
yield* markDirty;
|
|
697
|
+
});
|
|
698
|
+
const getTask = (taskId) => Ref.get(storeRef).pipe(Effect.map((store) => Option.fromNullable(store.tasks.get(taskId))));
|
|
699
|
+
const listTasks = Ref.get(storeRef).pipe(Effect.map((store) => Array.from(store.tasks.values())));
|
|
700
|
+
const runTask = dual(2, (effect, options) => Effect.gen(function* () {
|
|
701
|
+
const inheritedParentId = yield* FiberRef.get(currentParentRef);
|
|
702
|
+
const resolvedParentId = options.parentId === void 0 ? inheritedParentId : Option.some(options.parentId);
|
|
703
|
+
const taskId = yield* addTask({
|
|
704
|
+
...options,
|
|
705
|
+
parentId: Option.isSome(resolvedParentId) ? resolvedParentId.value : void 0,
|
|
706
|
+
transient: options.transient
|
|
707
|
+
});
|
|
708
|
+
return yield* Effect.locally(Effect.provideService(effect, Task, taskId), currentParentRef, Option.some(taskId));
|
|
709
|
+
}));
|
|
710
|
+
const service = {
|
|
711
|
+
addTask,
|
|
712
|
+
updateTask,
|
|
713
|
+
advanceTask,
|
|
714
|
+
completeTask,
|
|
715
|
+
failTask,
|
|
716
|
+
log,
|
|
717
|
+
getTask,
|
|
718
|
+
listTasks,
|
|
719
|
+
runTask,
|
|
720
|
+
withTask: dual(2, (effect, options) => runTask(Effect.gen(function* () {
|
|
721
|
+
const taskId = yield* Task;
|
|
722
|
+
const exit = yield* Effect.exit(effect);
|
|
723
|
+
if (Exit.isSuccess(exit)) yield* completeTask(taskId);
|
|
724
|
+
else yield* failTask(taskId);
|
|
725
|
+
return yield* Exit.match(exit, {
|
|
726
|
+
onFailure: Effect.failCause,
|
|
727
|
+
onSuccess: Effect.succeed
|
|
728
|
+
});
|
|
729
|
+
}), options))
|
|
730
|
+
};
|
|
731
|
+
return Progress.of(service);
|
|
732
|
+
});
|
|
733
|
+
var Progress = class Progress extends Context.Tag("stromseng.dev/effective-progress/Progress")() {
|
|
734
|
+
static Default = Layer.unwrapEffect(Effect.gen(function* () {
|
|
735
|
+
const terminalOption = yield* Effect.serviceOption(ProgressTerminal);
|
|
736
|
+
const inkRendererOption = yield* Effect.serviceOption(InkRenderer);
|
|
737
|
+
let layer = Layer.scoped(Progress, makeProgressService);
|
|
738
|
+
if (Option.isNone(inkRendererOption)) layer = layer.pipe(Layer.provide(InkRenderer.Default));
|
|
739
|
+
if (Option.isNone(terminalOption)) layer = layer.pipe(Layer.provide(ProgressTerminal.Default));
|
|
740
|
+
return layer;
|
|
741
|
+
}));
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
//#endregion
|
|
745
|
+
//#region src/utils.ts
|
|
746
|
+
const inferTotal = (iterable) => {
|
|
747
|
+
if (Array.isArray(iterable)) return iterable.length;
|
|
748
|
+
if (typeof iterable === "string") return iterable.length;
|
|
749
|
+
const candidate = iterable;
|
|
750
|
+
if (typeof candidate.length === "number") return candidate.length;
|
|
751
|
+
if (typeof candidate.size === "number") return candidate.size;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
//#endregion
|
|
755
|
+
//#region src/api.ts
|
|
756
|
+
const provideProgress = (effect) => Effect.gen(function* () {
|
|
757
|
+
const existing = yield* Effect.serviceOption(Progress);
|
|
758
|
+
if (Option.isSome(existing)) return yield* Effect.provideService(effect, Progress, existing.value);
|
|
759
|
+
return yield* Effect.scoped(effect.pipe(Effect.provide(Progress.Default)));
|
|
760
|
+
});
|
|
761
|
+
const task = dual(2, (effect, options) => {
|
|
762
|
+
return provideProgress(Effect.gen(function* () {
|
|
763
|
+
return yield* (yield* Progress).withTask(effect, options);
|
|
764
|
+
}));
|
|
765
|
+
});
|
|
766
|
+
const wrapEffects = (effects, tap) => Array.isArray(effects) ? effects.map(tap) : Object.fromEntries(Object.entries(effects).map(([k, effect]) => [k, tap(effect)]));
|
|
767
|
+
const countEffects = (effects) => Array.isArray(effects) ? effects.length : Object.keys(effects).length;
|
|
768
|
+
const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* () {
|
|
769
|
+
const progress = yield* Progress;
|
|
770
|
+
return yield* progress.runTask(Effect.gen(function* () {
|
|
771
|
+
const taskId = yield* Task;
|
|
772
|
+
const exit = yield* Effect.exit(Effect.all(wrapEffects(effects, (effect) => Effect.tap(effect, () => progress.advanceTask(taskId, 1))), {
|
|
773
|
+
concurrency: options.concurrency,
|
|
774
|
+
batching: options.batching,
|
|
775
|
+
discard: options.discard,
|
|
776
|
+
mode: options.mode,
|
|
777
|
+
concurrentFinalizers: options.concurrentFinalizers
|
|
778
|
+
}));
|
|
779
|
+
if (Exit.isSuccess(exit)) yield* progress.completeTask(taskId);
|
|
780
|
+
else yield* progress.failTask(taskId);
|
|
781
|
+
return yield* Exit.match(exit, {
|
|
782
|
+
onFailure: Effect.failCause,
|
|
783
|
+
onSuccess: Effect.succeed
|
|
784
|
+
});
|
|
785
|
+
}), {
|
|
786
|
+
description: options.description,
|
|
787
|
+
total: countEffects(effects),
|
|
788
|
+
transient: options.transient
|
|
789
|
+
});
|
|
790
|
+
})));
|
|
791
|
+
const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(function* () {
|
|
792
|
+
const progress = yield* Progress;
|
|
793
|
+
return yield* progress.runTask(Effect.gen(function* () {
|
|
794
|
+
const taskId = yield* Task;
|
|
795
|
+
const exit = yield* Effect.exit(Effect.forEach(iterable, (item, index) => Effect.tap(f(item, index), () => progress.advanceTask(taskId, 1)), {
|
|
796
|
+
concurrency: options.concurrency,
|
|
797
|
+
batching: options.batching,
|
|
798
|
+
discard: options.discard,
|
|
799
|
+
concurrentFinalizers: options.concurrentFinalizers
|
|
800
|
+
}));
|
|
801
|
+
if (Exit.isSuccess(exit)) yield* progress.completeTask(taskId);
|
|
802
|
+
else yield* progress.failTask(taskId);
|
|
803
|
+
return yield* Exit.match(exit, {
|
|
804
|
+
onFailure: Effect.failCause,
|
|
805
|
+
onSuccess: Effect.succeed
|
|
806
|
+
});
|
|
807
|
+
}), {
|
|
808
|
+
description: options.description,
|
|
809
|
+
total: options.total ?? inferTotal(iterable),
|
|
810
|
+
transient: options.transient
|
|
811
|
+
});
|
|
812
|
+
})));
|
|
813
|
+
|
|
814
|
+
//#endregion
|
|
815
|
+
export { DeterminateTaskUnits, IndeterminateTaskUnits, Progress, ProgressTaskEventSchema, ProgressTerminal, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskFailedEvent, TaskId, TaskRemovedEvent, TaskSnapshot, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|