effective-progress 0.11.0 → 0.12.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 +35 -19
- package/dist/index.d.mts +87 -85
- package/dist/index.mjs +546 -467
- package/package.json +5 -7
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
|
|
2
|
-
import { Brand, Cause, Clock, Context, Effect, Exit,
|
|
2
|
+
import { Brand, Cause, Clock, Context, Effect, Exit, Layer, Option, Queue, Schema } from "effect";
|
|
3
3
|
import { dual } from "effect/Function";
|
|
4
4
|
import { Box, Text, render, useBoxMetrics } from "ink";
|
|
5
|
-
import { createContext,
|
|
5
|
+
import { createContext, use, useEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import cliSpinners from "cli-spinners";
|
|
8
8
|
import stringWidth from "fast-string-width";
|
|
@@ -10,14 +10,22 @@ import stringWidth from "fast-string-width";
|
|
|
10
10
|
//#region src/types.ts
|
|
11
11
|
const TaskIdSchema = Schema.Number.pipe(Schema.brand("TaskId"));
|
|
12
12
|
const TaskId = Brand.nominal();
|
|
13
|
-
const TaskStatusSchema = Schema.
|
|
14
|
-
|
|
13
|
+
const TaskStatusSchema = Schema.Literals([
|
|
14
|
+
"running",
|
|
15
|
+
"done",
|
|
16
|
+
"failed"
|
|
17
|
+
]);
|
|
18
|
+
const TaskCountDisplaySchema = Schema.Literals(["processedOnly", "detailed"]);
|
|
15
19
|
const TaskUnitsSchema = Schema.Struct({
|
|
16
20
|
succeeded: Schema.Number,
|
|
17
21
|
failed: Schema.Number,
|
|
18
22
|
processed: Schema.Number,
|
|
19
23
|
total: Schema.optional(Schema.Number)
|
|
20
24
|
});
|
|
25
|
+
const TaskProgressSampleSchema = Schema.Struct({
|
|
26
|
+
timestamp: Schema.Number,
|
|
27
|
+
processed: Schema.Number
|
|
28
|
+
});
|
|
21
29
|
const TaskSnapshotSchema = Schema.Struct({
|
|
22
30
|
id: TaskIdSchema,
|
|
23
31
|
parentId: Schema.NullOr(TaskIdSchema),
|
|
@@ -28,10 +36,11 @@ const TaskSnapshotSchema = Schema.Struct({
|
|
|
28
36
|
units: TaskUnitsSchema,
|
|
29
37
|
startedAt: Schema.Number,
|
|
30
38
|
completedAt: Schema.NullOr(Schema.Number),
|
|
39
|
+
progressSamples: Schema.Array(TaskProgressSampleSchema),
|
|
31
40
|
metadata: Schema.Unknown
|
|
32
41
|
});
|
|
33
42
|
const TaskSnapshot = (snapshot) => snapshot;
|
|
34
|
-
var Task = class extends Context.
|
|
43
|
+
var Task = class extends Context.Service()("stromseng.dev/effective-progress/Task") {};
|
|
35
44
|
var TaskAddedEvent = class extends Schema.TaggedClass()("TaskAdded", {
|
|
36
45
|
taskId: TaskIdSchema,
|
|
37
46
|
parentId: Schema.NullOr(TaskIdSchema),
|
|
@@ -53,25 +62,30 @@ var TaskUpdatedEvent = class extends Schema.TaggedClass()("TaskUpdated", {
|
|
|
53
62
|
var TaskAdvancedEvent = class extends Schema.TaggedClass()("TaskAdvanced", {
|
|
54
63
|
taskId: TaskIdSchema,
|
|
55
64
|
amount: Schema.Number,
|
|
56
|
-
kind: Schema.
|
|
65
|
+
kind: Schema.Literals(["succeeded", "failed"])
|
|
57
66
|
}) {};
|
|
58
67
|
var TaskCompletedEvent = class extends Schema.TaggedClass()("TaskCompleted", { taskId: TaskIdSchema }) {};
|
|
59
68
|
var TaskFailedEvent = class extends Schema.TaggedClass()("TaskFailed", { taskId: TaskIdSchema }) {};
|
|
60
69
|
var TaskRemovedEvent = class extends Schema.TaggedClass()("TaskRemoved", { taskId: TaskIdSchema }) {};
|
|
61
|
-
const ProgressTaskEventSchema = Schema.Union(
|
|
70
|
+
const ProgressTaskEventSchema = Schema.Union([
|
|
71
|
+
TaskAddedEvent,
|
|
72
|
+
TaskUpdatedEvent,
|
|
73
|
+
TaskAdvancedEvent,
|
|
74
|
+
TaskCompletedEvent,
|
|
75
|
+
TaskFailedEvent,
|
|
76
|
+
TaskRemovedEvent
|
|
77
|
+
]);
|
|
62
78
|
const decodeProgressTaskEvent = Schema.decodeUnknownSync(ProgressTaskEventSchema);
|
|
63
79
|
|
|
64
80
|
//#endregion
|
|
65
|
-
//#region src/
|
|
81
|
+
//#region src/services/store/store.ts
|
|
82
|
+
const ETA_SAMPLE_WINDOW_MILLIS = 3e4;
|
|
83
|
+
const ETA_SAMPLE_MAX_LENGTH = 1e3;
|
|
66
84
|
const hasExplicitTotal = (options) => Object.prototype.hasOwnProperty.call(options, "total");
|
|
67
|
-
const
|
|
85
|
+
const sanitizeTotal = (total) => {
|
|
68
86
|
if (total === void 0) return;
|
|
69
87
|
return total < 0 ? void 0 : total;
|
|
70
88
|
};
|
|
71
|
-
const sanitizeTotalOnUpdate = (nextTotal) => {
|
|
72
|
-
if (nextTotal === void 0) return;
|
|
73
|
-
return nextTotal < 0 ? void 0 : nextTotal;
|
|
74
|
-
};
|
|
75
89
|
const normalizeUnits = (counts) => {
|
|
76
90
|
const succeeded = Math.max(0, counts.succeeded);
|
|
77
91
|
const failed = Math.max(0, counts.failed);
|
|
@@ -86,38 +100,41 @@ const normalizeUnits = (counts) => {
|
|
|
86
100
|
total: counts.total
|
|
87
101
|
};
|
|
88
102
|
};
|
|
89
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Returns a new progress sample deque with the latest processed count appended.
|
|
105
|
+
*
|
|
106
|
+
* Samples are retained for the ETA rolling window and capped by count so very chatty tasks do not
|
|
107
|
+
* grow memory without bound.
|
|
108
|
+
*/
|
|
109
|
+
const appendProgressSample = (samples, now, processed) => {
|
|
110
|
+
const previousSamples = samples ?? [];
|
|
111
|
+
if (previousSamples.at(-1)?.processed === processed) return previousSamples;
|
|
112
|
+
const windowStart = now - ETA_SAMPLE_WINDOW_MILLIS;
|
|
113
|
+
const appendedLength = previousSamples.length + 1;
|
|
114
|
+
let firstRetainedIndex = Math.max(0, appendedLength - ETA_SAMPLE_MAX_LENGTH);
|
|
115
|
+
while (firstRetainedIndex + 1 < previousSamples.length && previousSamples[firstRetainedIndex + 1].timestamp < windowStart) firstRetainedIndex++;
|
|
116
|
+
return [...previousSamples.slice(firstRetainedIndex), {
|
|
117
|
+
timestamp: now,
|
|
118
|
+
processed
|
|
119
|
+
}];
|
|
120
|
+
};
|
|
121
|
+
/** Applies mutable task fields and records a progress sample when the processed count changes. */
|
|
122
|
+
const updatedSnapshot = (snapshot, options, now) => {
|
|
90
123
|
const currentUnits = snapshot.units;
|
|
91
124
|
const units = options.succeeded === void 0 && options.failed === void 0 && options.total === void 0 && !hasExplicitTotal(options) ? currentUnits : normalizeUnits({
|
|
92
125
|
succeeded: options.succeeded ?? currentUnits.succeeded,
|
|
93
126
|
failed: options.failed ?? currentUnits.failed,
|
|
94
|
-
total: hasExplicitTotal(options) ?
|
|
127
|
+
total: hasExplicitTotal(options) ? sanitizeTotal(options.total) : currentUnits.total
|
|
95
128
|
});
|
|
96
129
|
return TaskSnapshot({
|
|
97
|
-
|
|
98
|
-
parentId: snapshot.parentId,
|
|
130
|
+
...snapshot,
|
|
99
131
|
description: options.description ?? snapshot.description,
|
|
100
|
-
status: snapshot.status,
|
|
101
132
|
countDisplay: options.countDisplay ?? snapshot.countDisplay,
|
|
102
133
|
transient: options.transient ?? snapshot.transient,
|
|
103
134
|
units,
|
|
104
|
-
|
|
105
|
-
completedAt: snapshot.completedAt,
|
|
106
|
-
metadata: snapshot.metadata
|
|
135
|
+
progressSamples: appendProgressSample(snapshot.progressSamples, now, units.processed)
|
|
107
136
|
});
|
|
108
137
|
};
|
|
109
|
-
const withTransient = (snapshot, transient) => TaskSnapshot({
|
|
110
|
-
id: snapshot.id,
|
|
111
|
-
parentId: snapshot.parentId,
|
|
112
|
-
description: snapshot.description,
|
|
113
|
-
status: snapshot.status,
|
|
114
|
-
countDisplay: snapshot.countDisplay,
|
|
115
|
-
transient,
|
|
116
|
-
units: snapshot.units,
|
|
117
|
-
startedAt: snapshot.startedAt,
|
|
118
|
-
completedAt: snapshot.completedAt,
|
|
119
|
-
metadata: snapshot.metadata
|
|
120
|
-
});
|
|
121
138
|
const findInsertionIndex = (renderOrder, parentId) => {
|
|
122
139
|
if (parentId === null) return {
|
|
123
140
|
index: renderOrder.length,
|
|
@@ -154,8 +171,19 @@ const subtreeTaskIds = (renderOrder, taskId) => {
|
|
|
154
171
|
while (end < renderOrder.length && renderOrder[end].depth > taskDepth) end++;
|
|
155
172
|
return renderOrder.slice(idx, end).map((row) => row.id);
|
|
156
173
|
};
|
|
174
|
+
const removeTransientSubtree = (current, nextTasks, taskId) => {
|
|
175
|
+
const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
|
|
176
|
+
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
177
|
+
const nextColumns = new Map(current.columns);
|
|
178
|
+
for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
|
|
179
|
+
return {
|
|
180
|
+
removedTaskIds,
|
|
181
|
+
renderOrder: removeFromRenderOrder(current.renderOrder, taskId),
|
|
182
|
+
columns: nextColumns
|
|
183
|
+
};
|
|
184
|
+
};
|
|
157
185
|
const SNAPSHOT_PUBLISH_INTERVAL_MILLIS = 100;
|
|
158
|
-
const
|
|
186
|
+
const makeProgressStoreRuntime = (publishQueue) => {
|
|
159
187
|
let nextTaskId = 0;
|
|
160
188
|
let state = {
|
|
161
189
|
tasks: /* @__PURE__ */ new Map(),
|
|
@@ -168,13 +196,13 @@ const makeProgressRenderStore = () => {
|
|
|
168
196
|
events: []
|
|
169
197
|
};
|
|
170
198
|
let hasPendingPublish = false;
|
|
171
|
-
let lastPublishAt =
|
|
172
|
-
let
|
|
199
|
+
let lastPublishAt = -SNAPSHOT_PUBLISH_INTERVAL_MILLIS;
|
|
200
|
+
let latestObservedAt = 0;
|
|
173
201
|
const listeners = /* @__PURE__ */ new Set();
|
|
174
202
|
const notifyListeners = () => {
|
|
175
203
|
for (const listener of listeners) listener();
|
|
176
204
|
};
|
|
177
|
-
const publishNow = () => {
|
|
205
|
+
const publishNow = (publishedAt) => {
|
|
178
206
|
const nextPublication = {
|
|
179
207
|
snapshot: state,
|
|
180
208
|
events: [...pendingEvents]
|
|
@@ -183,253 +211,238 @@ const makeProgressRenderStore = () => {
|
|
|
183
211
|
publishedPublication = nextPublication;
|
|
184
212
|
notifyListeners();
|
|
185
213
|
pendingEvents = [];
|
|
186
|
-
lastPublishAt =
|
|
214
|
+
lastPublishAt = publishedAt;
|
|
187
215
|
};
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
publishTimeout = void 0;
|
|
192
|
-
};
|
|
193
|
-
const schedulePublish = () => {
|
|
194
|
-
if (!hasPendingPublish) return;
|
|
195
|
-
const now = Date.now();
|
|
216
|
+
const publisherLoop = Effect.forever(Effect.gen(function* () {
|
|
217
|
+
yield* Queue.take(publishQueue);
|
|
218
|
+
const now = yield* Clock.currentTimeMillis;
|
|
196
219
|
const waitMillis = Math.max(0, SNAPSHOT_PUBLISH_INTERVAL_MILLIS - (now - lastPublishAt));
|
|
197
|
-
if (waitMillis
|
|
198
|
-
|
|
199
|
-
|
|
220
|
+
if (waitMillis > 0) yield* Effect.sleep(waitMillis);
|
|
221
|
+
if (!hasPendingPublish) return;
|
|
222
|
+
publishNow(yield* Clock.currentTimeMillis);
|
|
223
|
+
}));
|
|
224
|
+
const schedulePublish = Effect.gen(function* () {
|
|
225
|
+
if (!hasPendingPublish) return;
|
|
226
|
+
const now = yield* Clock.currentTimeMillis;
|
|
227
|
+
if (Math.max(0, SNAPSHOT_PUBLISH_INTERVAL_MILLIS - (now - lastPublishAt)) === 0) {
|
|
228
|
+
publishNow(now);
|
|
200
229
|
return;
|
|
201
230
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}, waitMillis);
|
|
208
|
-
};
|
|
209
|
-
const publish = (update) => {
|
|
210
|
-
if (update.state === state) return;
|
|
231
|
+
yield* Queue.offer(publishQueue, void 0);
|
|
232
|
+
});
|
|
233
|
+
const publish = (update, now) => {
|
|
234
|
+
if (update.state === state) return Effect.void;
|
|
235
|
+
latestObservedAt = now;
|
|
211
236
|
state = update.state;
|
|
212
237
|
if (update.events.length > 0) pendingEvents.push(...update.events);
|
|
213
238
|
hasPendingPublish = true;
|
|
214
|
-
schedulePublish
|
|
239
|
+
return schedulePublish;
|
|
215
240
|
};
|
|
216
|
-
const updateState = (transform) => {
|
|
217
|
-
publish(transform(state));
|
|
241
|
+
const updateState = (transform, now) => {
|
|
242
|
+
return publish(transform(state), now);
|
|
218
243
|
};
|
|
219
244
|
return {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
id: taskId,
|
|
245
|
-
parentId,
|
|
246
|
-
description: options.description,
|
|
247
|
-
status: "running",
|
|
248
|
-
countDisplay,
|
|
249
|
-
transient: (parentSnapshot?.transient ?? false) || (options.transient ?? false),
|
|
250
|
-
units,
|
|
251
|
-
startedAt: now,
|
|
252
|
-
completedAt: null,
|
|
253
|
-
metadata: options.metadata
|
|
254
|
-
});
|
|
255
|
-
updateState((current) => {
|
|
256
|
-
const nextTasks = new Map(current.tasks);
|
|
257
|
-
nextTasks.set(taskId, task);
|
|
258
|
-
const { index, depth } = findInsertionIndex(current.renderOrder, parentId);
|
|
259
|
-
const nextRenderOrder = [...current.renderOrder];
|
|
260
|
-
nextRenderOrder.splice(index, 0, {
|
|
245
|
+
store: {
|
|
246
|
+
getSnapshot: () => publishedPublication,
|
|
247
|
+
subscribe: (listener) => {
|
|
248
|
+
listeners.add(listener);
|
|
249
|
+
return () => {
|
|
250
|
+
listeners.delete(listener);
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
flush: () => {
|
|
254
|
+
if (!hasPendingPublish) return;
|
|
255
|
+
publishNow(latestObservedAt);
|
|
256
|
+
},
|
|
257
|
+
addTask: (options) => Effect.gen(function* () {
|
|
258
|
+
const taskId = TaskId(++nextTaskId);
|
|
259
|
+
const units = normalizeUnits({
|
|
260
|
+
succeeded: 0,
|
|
261
|
+
failed: 0,
|
|
262
|
+
total: sanitizeTotal(options.total)
|
|
263
|
+
});
|
|
264
|
+
const parentSnapshot = options.parentId === void 0 ? void 0 : state.tasks.get(options.parentId);
|
|
265
|
+
const now = yield* Clock.currentTimeMillis;
|
|
266
|
+
const parentId = options.parentId ?? null;
|
|
267
|
+
const countDisplay = options.countDisplay ?? parentSnapshot?.countDisplay ?? "detailed";
|
|
268
|
+
const task = TaskSnapshot({
|
|
261
269
|
id: taskId,
|
|
262
|
-
|
|
270
|
+
parentId,
|
|
271
|
+
description: options.description,
|
|
272
|
+
status: "running",
|
|
273
|
+
countDisplay,
|
|
274
|
+
transient: (parentSnapshot?.transient ?? false) || (options.transient ?? false),
|
|
275
|
+
units,
|
|
276
|
+
startedAt: now,
|
|
277
|
+
completedAt: null,
|
|
278
|
+
progressSamples: [{
|
|
279
|
+
timestamp: now,
|
|
280
|
+
processed: units.processed
|
|
281
|
+
}],
|
|
282
|
+
metadata: options.metadata
|
|
263
283
|
});
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
284
|
+
yield* updateState((current) => {
|
|
285
|
+
const nextTasks = new Map(current.tasks);
|
|
286
|
+
nextTasks.set(taskId, task);
|
|
287
|
+
const { index, depth } = findInsertionIndex(current.renderOrder, parentId);
|
|
288
|
+
const nextRenderOrder = [...current.renderOrder];
|
|
289
|
+
nextRenderOrder.splice(index, 0, {
|
|
290
|
+
id: taskId,
|
|
291
|
+
depth
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
state: {
|
|
295
|
+
tasks: nextTasks,
|
|
296
|
+
renderOrder: nextRenderOrder,
|
|
297
|
+
columns: options.columns ? new Map(current.columns).set(taskId, options.columns) : current.columns
|
|
298
|
+
},
|
|
299
|
+
events: [new TaskAddedEvent({
|
|
300
|
+
taskId,
|
|
301
|
+
parentId,
|
|
302
|
+
description: task.description,
|
|
303
|
+
total: task.units.total,
|
|
304
|
+
transient: task.transient,
|
|
305
|
+
countDisplay: task.countDisplay
|
|
306
|
+
})]
|
|
307
|
+
};
|
|
308
|
+
}, now);
|
|
309
|
+
return taskId;
|
|
310
|
+
}),
|
|
311
|
+
updateTask: (taskId, options) => Effect.gen(function* () {
|
|
312
|
+
const now = yield* Clock.currentTimeMillis;
|
|
313
|
+
yield* updateState((current) => {
|
|
314
|
+
const currentTask = current.tasks.get(taskId);
|
|
315
|
+
if (!currentTask) return {
|
|
316
|
+
state: current,
|
|
317
|
+
events: []
|
|
318
|
+
};
|
|
319
|
+
const nextTask = updatedSnapshot(currentTask, options, now);
|
|
320
|
+
const nextTasks = new Map(current.tasks);
|
|
321
|
+
nextTasks.set(taskId, nextTask);
|
|
322
|
+
const events = [new TaskUpdatedEvent({
|
|
271
323
|
taskId,
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return {
|
|
313
|
-
state: {
|
|
314
|
-
tasks: nextTasks,
|
|
315
|
-
renderOrder: current.renderOrder,
|
|
316
|
-
columns: current.columns
|
|
317
|
-
},
|
|
318
|
-
events
|
|
319
|
-
};
|
|
320
|
-
});
|
|
321
|
-
}),
|
|
322
|
-
incrementSucceeded: (taskId, amount = 1) => Effect.sync(() => {
|
|
323
|
-
updateState((current) => {
|
|
324
|
-
const currentTask = current.tasks.get(taskId);
|
|
325
|
-
if (!currentTask) return {
|
|
326
|
-
state: current,
|
|
327
|
-
events: []
|
|
328
|
-
};
|
|
329
|
-
const nextTasks = new Map(current.tasks);
|
|
330
|
-
nextTasks.set(taskId, TaskSnapshot({
|
|
331
|
-
id: currentTask.id,
|
|
332
|
-
parentId: currentTask.parentId,
|
|
333
|
-
description: currentTask.description,
|
|
334
|
-
status: currentTask.status,
|
|
335
|
-
countDisplay: currentTask.countDisplay,
|
|
336
|
-
transient: currentTask.transient,
|
|
337
|
-
units: normalizeUnits({
|
|
324
|
+
description: options.description ?? void 0,
|
|
325
|
+
succeeded: options.succeeded ?? void 0,
|
|
326
|
+
failed: options.failed ?? void 0,
|
|
327
|
+
processed: options.succeeded !== void 0 || options.failed !== void 0 ? nextTask.units.processed : void 0,
|
|
328
|
+
total: hasExplicitTotal(options) ? nextTask.units.total : void 0,
|
|
329
|
+
transient: options.transient ?? void 0,
|
|
330
|
+
countDisplay: options.countDisplay ?? void 0
|
|
331
|
+
})];
|
|
332
|
+
if (options.transient !== void 0) for (const candidateId of subtreeTaskIds(current.renderOrder, taskId).slice(1)) {
|
|
333
|
+
const candidate = current.tasks.get(candidateId);
|
|
334
|
+
if (!candidate) continue;
|
|
335
|
+
const nextCandidate = TaskSnapshot({
|
|
336
|
+
...candidate,
|
|
337
|
+
transient: nextTask.transient
|
|
338
|
+
});
|
|
339
|
+
nextTasks.set(candidateId, nextCandidate);
|
|
340
|
+
events.push(new TaskUpdatedEvent({
|
|
341
|
+
taskId: candidateId,
|
|
342
|
+
transient: nextCandidate.transient
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
state: {
|
|
347
|
+
tasks: nextTasks,
|
|
348
|
+
renderOrder: current.renderOrder,
|
|
349
|
+
columns: current.columns
|
|
350
|
+
},
|
|
351
|
+
events
|
|
352
|
+
};
|
|
353
|
+
}, now);
|
|
354
|
+
}),
|
|
355
|
+
incrementSucceeded: (taskId, amount = 1) => Effect.gen(function* () {
|
|
356
|
+
const now = yield* Clock.currentTimeMillis;
|
|
357
|
+
yield* updateState((current) => {
|
|
358
|
+
const currentTask = current.tasks.get(taskId);
|
|
359
|
+
if (!currentTask) return {
|
|
360
|
+
state: current,
|
|
361
|
+
events: []
|
|
362
|
+
};
|
|
363
|
+
const units = normalizeUnits({
|
|
338
364
|
succeeded: currentTask.units.succeeded + amount,
|
|
339
365
|
failed: currentTask.units.failed,
|
|
340
366
|
total: currentTask.units.total
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
description: currentTask.description,
|
|
372
|
-
status: currentTask.status,
|
|
373
|
-
countDisplay: currentTask.countDisplay,
|
|
374
|
-
transient: currentTask.transient,
|
|
375
|
-
units: normalizeUnits({
|
|
367
|
+
});
|
|
368
|
+
const nextTasks = new Map(current.tasks);
|
|
369
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
370
|
+
...currentTask,
|
|
371
|
+
units,
|
|
372
|
+
progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
|
|
373
|
+
}));
|
|
374
|
+
return {
|
|
375
|
+
state: {
|
|
376
|
+
tasks: nextTasks,
|
|
377
|
+
renderOrder: current.renderOrder,
|
|
378
|
+
columns: current.columns
|
|
379
|
+
},
|
|
380
|
+
events: [new TaskAdvancedEvent({
|
|
381
|
+
taskId,
|
|
382
|
+
amount,
|
|
383
|
+
kind: "succeeded"
|
|
384
|
+
})]
|
|
385
|
+
};
|
|
386
|
+
}, now);
|
|
387
|
+
}),
|
|
388
|
+
incrementFailed: (taskId, amount = 1) => Effect.gen(function* () {
|
|
389
|
+
const now = yield* Clock.currentTimeMillis;
|
|
390
|
+
yield* updateState((current) => {
|
|
391
|
+
const currentTask = current.tasks.get(taskId);
|
|
392
|
+
if (!currentTask) return {
|
|
393
|
+
state: current,
|
|
394
|
+
events: []
|
|
395
|
+
};
|
|
396
|
+
const units = normalizeUnits({
|
|
376
397
|
succeeded: currentTask.units.succeeded,
|
|
377
398
|
failed: currentTask.units.failed + amount,
|
|
378
399
|
total: currentTask.units.total
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
tasks: nextTasks,
|
|
387
|
-
renderOrder: current.renderOrder,
|
|
388
|
-
columns: current.columns
|
|
389
|
-
},
|
|
390
|
-
events: [new TaskAdvancedEvent({
|
|
391
|
-
taskId,
|
|
392
|
-
amount,
|
|
393
|
-
kind: "failed"
|
|
394
|
-
})]
|
|
395
|
-
};
|
|
396
|
-
});
|
|
397
|
-
}),
|
|
398
|
-
completeTask: (taskId) => Effect.gen(function* () {
|
|
399
|
-
const now = yield* Clock.currentTimeMillis;
|
|
400
|
-
updateState((current) => {
|
|
401
|
-
const currentTask = current.tasks.get(taskId);
|
|
402
|
-
if (!currentTask) return {
|
|
403
|
-
state: current,
|
|
404
|
-
events: []
|
|
405
|
-
};
|
|
406
|
-
if (currentTask.status !== "running") return {
|
|
407
|
-
state: current,
|
|
408
|
-
events: []
|
|
409
|
-
};
|
|
410
|
-
const nextTasks = new Map(current.tasks);
|
|
411
|
-
if (currentTask.transient) {
|
|
412
|
-
const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
|
|
413
|
-
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
414
|
-
const nextColumns = new Map(current.columns);
|
|
415
|
-
for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
|
|
400
|
+
});
|
|
401
|
+
const nextTasks = new Map(current.tasks);
|
|
402
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
403
|
+
...currentTask,
|
|
404
|
+
units,
|
|
405
|
+
progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
|
|
406
|
+
}));
|
|
416
407
|
return {
|
|
417
408
|
state: {
|
|
418
409
|
tasks: nextTasks,
|
|
419
|
-
renderOrder:
|
|
420
|
-
columns:
|
|
410
|
+
renderOrder: current.renderOrder,
|
|
411
|
+
columns: current.columns
|
|
421
412
|
},
|
|
422
|
-
events: [new
|
|
413
|
+
events: [new TaskAdvancedEvent({
|
|
414
|
+
taskId,
|
|
415
|
+
amount,
|
|
416
|
+
kind: "failed"
|
|
417
|
+
})]
|
|
423
418
|
};
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
419
|
+
}, now);
|
|
420
|
+
}),
|
|
421
|
+
completeTask: (taskId) => Effect.gen(function* () {
|
|
422
|
+
const now = yield* Clock.currentTimeMillis;
|
|
423
|
+
yield* updateState((current) => {
|
|
424
|
+
const currentTask = current.tasks.get(taskId);
|
|
425
|
+
if (!currentTask) return {
|
|
426
|
+
state: current,
|
|
427
|
+
events: []
|
|
428
|
+
};
|
|
429
|
+
if (currentTask.status !== "running") return {
|
|
430
|
+
state: current,
|
|
431
|
+
events: []
|
|
432
|
+
};
|
|
433
|
+
const nextTasks = new Map(current.tasks);
|
|
434
|
+
if (currentTask.transient) {
|
|
435
|
+
const removedSubtree = removeTransientSubtree(current, nextTasks, taskId);
|
|
436
|
+
return {
|
|
437
|
+
state: {
|
|
438
|
+
tasks: nextTasks,
|
|
439
|
+
renderOrder: removedSubtree.renderOrder,
|
|
440
|
+
columns: removedSubtree.columns
|
|
441
|
+
},
|
|
442
|
+
events: [new TaskCompletedEvent({ taskId }), ...removedSubtree.removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
const units = currentTask.units.total !== void 0 ? currentTask.units.processed < currentTask.units.total ? normalizeUnits({
|
|
433
446
|
succeeded: currentTask.units.succeeded + (currentTask.units.total - currentTask.units.processed),
|
|
434
447
|
failed: currentTask.units.failed,
|
|
435
448
|
total: currentTask.units.total
|
|
@@ -437,117 +450,112 @@ const makeProgressRenderStore = () => {
|
|
|
437
450
|
succeeded: currentTask.units.succeeded,
|
|
438
451
|
failed: currentTask.units.failed,
|
|
439
452
|
total: currentTask.units.processed
|
|
440
|
-
}) : currentTask.units
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
renderOrder: current.renderOrder,
|
|
449
|
-
columns: current.columns
|
|
450
|
-
},
|
|
451
|
-
events: [new TaskCompletedEvent({ taskId })]
|
|
452
|
-
};
|
|
453
|
-
});
|
|
454
|
-
}),
|
|
455
|
-
failTask: (taskId) => Effect.gen(function* () {
|
|
456
|
-
const now = yield* Clock.currentTimeMillis;
|
|
457
|
-
updateState((current) => {
|
|
458
|
-
const currentTask = current.tasks.get(taskId);
|
|
459
|
-
if (!currentTask) return {
|
|
460
|
-
state: current,
|
|
461
|
-
events: []
|
|
462
|
-
};
|
|
463
|
-
if (currentTask.status !== "running") return {
|
|
464
|
-
state: current,
|
|
465
|
-
events: []
|
|
466
|
-
};
|
|
467
|
-
const nextTasks = new Map(current.tasks);
|
|
468
|
-
if (currentTask.transient) {
|
|
469
|
-
const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
|
|
470
|
-
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
471
|
-
const nextColumns = new Map(current.columns);
|
|
472
|
-
for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
|
|
453
|
+
}) : currentTask.units;
|
|
454
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
455
|
+
...currentTask,
|
|
456
|
+
status: "done",
|
|
457
|
+
units,
|
|
458
|
+
completedAt: now,
|
|
459
|
+
progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
|
|
460
|
+
}));
|
|
473
461
|
return {
|
|
474
462
|
state: {
|
|
475
463
|
tasks: nextTasks,
|
|
476
|
-
renderOrder:
|
|
477
|
-
columns:
|
|
464
|
+
renderOrder: current.renderOrder,
|
|
465
|
+
columns: current.columns
|
|
478
466
|
},
|
|
479
|
-
events: [new
|
|
467
|
+
events: [new TaskCompletedEvent({ taskId })]
|
|
480
468
|
};
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
469
|
+
}, now);
|
|
470
|
+
}),
|
|
471
|
+
failTask: (taskId) => Effect.gen(function* () {
|
|
472
|
+
const now = yield* Clock.currentTimeMillis;
|
|
473
|
+
yield* updateState((current) => {
|
|
474
|
+
const currentTask = current.tasks.get(taskId);
|
|
475
|
+
if (!currentTask) return {
|
|
476
|
+
state: current,
|
|
477
|
+
events: []
|
|
478
|
+
};
|
|
479
|
+
if (currentTask.status !== "running") return {
|
|
480
|
+
state: current,
|
|
481
|
+
events: []
|
|
482
|
+
};
|
|
483
|
+
const nextTasks = new Map(current.tasks);
|
|
484
|
+
if (currentTask.transient) {
|
|
485
|
+
const removedSubtree = removeTransientSubtree(current, nextTasks, taskId);
|
|
486
|
+
return {
|
|
487
|
+
state: {
|
|
488
|
+
tasks: nextTasks,
|
|
489
|
+
renderOrder: removedSubtree.renderOrder,
|
|
490
|
+
columns: removedSubtree.columns
|
|
491
|
+
},
|
|
492
|
+
events: [new TaskFailedEvent({ taskId }), ...removedSubtree.removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
496
|
+
...currentTask,
|
|
497
|
+
status: "failed",
|
|
498
|
+
completedAt: now
|
|
499
|
+
}));
|
|
500
|
+
return {
|
|
501
|
+
state: {
|
|
502
|
+
tasks: nextTasks,
|
|
503
|
+
renderOrder: current.renderOrder,
|
|
504
|
+
columns: current.columns
|
|
505
|
+
},
|
|
506
|
+
events: [new TaskFailedEvent({ taskId })]
|
|
507
|
+
};
|
|
508
|
+
}, now);
|
|
509
|
+
}),
|
|
510
|
+
getTask: (taskId) => Effect.sync(() => Option.fromNullishOr(state.tasks.get(taskId))),
|
|
511
|
+
listTasks: Effect.sync(() => Array.from(state.tasks.values())),
|
|
512
|
+
setMetadata: (taskId, metadata) => Effect.gen(function* () {
|
|
513
|
+
yield* updateState((current) => {
|
|
514
|
+
const currentTask = current.tasks.get(taskId);
|
|
515
|
+
if (!currentTask) return {
|
|
516
|
+
state: current,
|
|
517
|
+
events: []
|
|
518
|
+
};
|
|
519
|
+
const nextTasks = new Map(current.tasks);
|
|
520
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
521
|
+
...currentTask,
|
|
522
|
+
metadata
|
|
523
|
+
}));
|
|
524
|
+
return {
|
|
525
|
+
state: {
|
|
526
|
+
tasks: nextTasks,
|
|
527
|
+
renderOrder: current.renderOrder,
|
|
528
|
+
columns: current.columns
|
|
529
|
+
},
|
|
530
|
+
events: []
|
|
531
|
+
};
|
|
532
|
+
}, yield* Clock.currentTimeMillis);
|
|
533
|
+
}),
|
|
534
|
+
getMetadata: (taskId) => Effect.sync(() => {
|
|
535
|
+
return state.tasks.get(taskId)?.metadata;
|
|
536
|
+
})
|
|
537
|
+
},
|
|
538
|
+
publisherLoop
|
|
539
539
|
};
|
|
540
540
|
};
|
|
541
|
+
const makeProgressStore = Effect.gen(function* () {
|
|
542
|
+
const runtime = makeProgressStoreRuntime(yield* Queue.sliding(1));
|
|
543
|
+
yield* Effect.forkDetach(runtime.publisherLoop);
|
|
544
|
+
return runtime.store;
|
|
545
|
+
});
|
|
546
|
+
var ProgressStore = class ProgressStore extends Context.Service()("stromseng.dev/effective-progress/ProgressStore") {
|
|
547
|
+
static layer = Layer.effect(ProgressStore, makeProgressStore);
|
|
548
|
+
};
|
|
541
549
|
|
|
542
550
|
//#endregion
|
|
543
|
-
//#region src/renderer/hooks/use-now-clock.ts
|
|
551
|
+
//#region src/services/renderer/hooks/use-now-clock.ts
|
|
544
552
|
const useNowClock = (active, intervalMillis) => {
|
|
545
|
-
const [now,
|
|
553
|
+
const [now, updateNow] = useReducer(() => Date.now(), void 0, Date.now);
|
|
546
554
|
useEffect(() => {
|
|
547
555
|
if (!active) return;
|
|
548
|
-
|
|
556
|
+
updateNow();
|
|
549
557
|
const interval = setInterval(() => {
|
|
550
|
-
|
|
558
|
+
updateNow();
|
|
551
559
|
}, intervalMillis);
|
|
552
560
|
return () => {
|
|
553
561
|
clearInterval(interval);
|
|
@@ -557,7 +565,7 @@ const useNowClock = (active, intervalMillis) => {
|
|
|
557
565
|
};
|
|
558
566
|
|
|
559
567
|
//#endregion
|
|
560
|
-
//#region src/renderer/context/now-context.tsx
|
|
568
|
+
//#region src/services/renderer/context/now-context.tsx
|
|
561
569
|
const NOW_INTERVAL_MILLIS = 1e3;
|
|
562
570
|
const NowContext = createContext(Date.now());
|
|
563
571
|
const NowProvider = ({ active, children, nowOverride }) => {
|
|
@@ -568,10 +576,10 @@ const NowProvider = ({ active, children, nowOverride }) => {
|
|
|
568
576
|
children
|
|
569
577
|
});
|
|
570
578
|
};
|
|
571
|
-
const useNow = () =>
|
|
579
|
+
const useNow = () => use(NowContext);
|
|
572
580
|
|
|
573
581
|
//#endregion
|
|
574
|
-
//#region src/renderer/hooks/use-spinner-clock.ts
|
|
582
|
+
//#region src/services/renderer/hooks/use-spinner-clock.ts
|
|
575
583
|
const normalizeIntervalMillis = (intervalMillis) => Math.max(1, intervalMillis);
|
|
576
584
|
const getSpinnerTickAtTime = (baseTick, startedAt, now, intervalMillis) => {
|
|
577
585
|
const elapsedMillis = Math.max(0, now - startedAt);
|
|
@@ -602,7 +610,7 @@ const useSpinnerClock = (active, intervalMillis) => {
|
|
|
602
610
|
};
|
|
603
611
|
|
|
604
612
|
//#endregion
|
|
605
|
-
//#region src/renderer/context/spinner-context.tsx
|
|
613
|
+
//#region src/services/renderer/context/spinner-context.tsx
|
|
606
614
|
const DEFAULT_SPINNER_INTERVAL_MILLIS = cliSpinners.dots.interval;
|
|
607
615
|
const SpinnerContext = createContext(0);
|
|
608
616
|
const SpinnerProvider = ({ active, children, intervalMillis = DEFAULT_SPINNER_INTERVAL_MILLIS, tickOverride }) => {
|
|
@@ -613,14 +621,14 @@ const SpinnerProvider = ({ active, children, intervalMillis = DEFAULT_SPINNER_IN
|
|
|
613
621
|
children
|
|
614
622
|
});
|
|
615
623
|
};
|
|
616
|
-
const useSpinnerTick = () =>
|
|
624
|
+
const useSpinnerTick = () => use(SpinnerContext);
|
|
617
625
|
|
|
618
626
|
//#endregion
|
|
619
|
-
//#region src/renderer/shared/determinate.ts
|
|
627
|
+
//#region src/services/renderer/shared/determinate.ts
|
|
620
628
|
const isDeterminate$2 = (task) => task.units.total !== void 0;
|
|
621
629
|
|
|
622
630
|
//#endregion
|
|
623
|
-
//#region src/renderer/shared/format.ts
|
|
631
|
+
//#region src/services/renderer/shared/format.ts
|
|
624
632
|
const isDeterminate$1 = (task) => task.units.total !== void 0;
|
|
625
633
|
const showsUnknownTotalCounts = (task) => task.units.total === void 0 && task.units.processed > 0;
|
|
626
634
|
const formatDurationSeconds = (seconds) => {
|
|
@@ -635,17 +643,58 @@ const formatDurationSeconds = (seconds) => {
|
|
|
635
643
|
const mins = Math.floor(value % 3600 / 60);
|
|
636
644
|
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
637
645
|
};
|
|
646
|
+
const formatClockDurationSeconds = (seconds) => {
|
|
647
|
+
const value = Math.max(0, Math.floor(seconds));
|
|
648
|
+
const hours = Math.floor(value / 3600);
|
|
649
|
+
const mins = Math.floor(value % 3600 / 60);
|
|
650
|
+
const secs = value % 60;
|
|
651
|
+
const clock = `${`${mins}`.padStart(2, "0")}:${`${secs}`.padStart(2, "0")}`;
|
|
652
|
+
return hours > 0 ? `${`${hours}`.padStart(2, "0")}:${clock}` : clock;
|
|
653
|
+
};
|
|
654
|
+
/**
|
|
655
|
+
* Estimates remaining time from the task's retained progress sample deque.
|
|
656
|
+
*
|
|
657
|
+
* Returns undefined until there are at least two samples with positive processed and time deltas.
|
|
658
|
+
*/
|
|
659
|
+
const getSmoothedEtaMillis = (task) => {
|
|
660
|
+
const { processed, total } = task.units;
|
|
661
|
+
const remaining = total - processed;
|
|
662
|
+
if (processed <= 0 || remaining <= 0) return;
|
|
663
|
+
const samples = task.progressSamples;
|
|
664
|
+
const lastSample = samples.at(-1);
|
|
665
|
+
if (lastSample === void 0) return;
|
|
666
|
+
const firstSample = samples[0];
|
|
667
|
+
if (firstSample === void 0 || firstSample === lastSample) return;
|
|
668
|
+
const deltaProcessed = lastSample.processed - firstSample.processed;
|
|
669
|
+
const deltaMillis = lastSample.timestamp - firstSample.timestamp;
|
|
670
|
+
if (deltaProcessed <= 0 || deltaMillis <= 0) return;
|
|
671
|
+
return Math.max(0, Math.floor(remaining * deltaMillis / deltaProcessed));
|
|
672
|
+
};
|
|
638
673
|
const formatElapsed = (task, now) => {
|
|
639
674
|
return formatDurationSeconds(Math.max(0, (task.completedAt ?? now) - task.startedAt) / 1e3);
|
|
640
675
|
};
|
|
641
|
-
const
|
|
676
|
+
const formatElapsedClock = (task, now) => {
|
|
677
|
+
return formatClockDurationSeconds(Math.max(0, (task.completedAt ?? now) - task.startedAt) / 1e3);
|
|
678
|
+
};
|
|
679
|
+
const formatEta = (task) => {
|
|
642
680
|
if (task.status !== "running" || !isDeterminate$1(task)) return "";
|
|
643
681
|
const { processed, total } = task.units;
|
|
644
682
|
const remaining = total - processed;
|
|
645
683
|
if (processed <= 0 || remaining <= 0) return "";
|
|
646
|
-
const
|
|
647
|
-
|
|
684
|
+
const etaMillis = getSmoothedEtaMillis(task);
|
|
685
|
+
if (etaMillis === void 0) return "";
|
|
686
|
+
return formatClockDurationSeconds(etaMillis / 1e3);
|
|
648
687
|
};
|
|
688
|
+
const formatEtaClock = (task) => {
|
|
689
|
+
if (task.status !== "running" || !isDeterminate$1(task)) return;
|
|
690
|
+
const { processed, total } = task.units;
|
|
691
|
+
const remaining = total - processed;
|
|
692
|
+
if (processed <= 0 || remaining <= 0) return;
|
|
693
|
+
const etaMillis = getSmoothedEtaMillis(task);
|
|
694
|
+
if (etaMillis === void 0) return;
|
|
695
|
+
return formatClockDurationSeconds(etaMillis / 1e3);
|
|
696
|
+
};
|
|
697
|
+
const formatElapsedEta = (task, now) => `${formatElapsedClock(task, now)}<${formatEtaClock(task) ?? "00:00"}`;
|
|
649
698
|
const formatDeterminateAmountParts = (task) => {
|
|
650
699
|
if (!isDeterminate$1(task)) return;
|
|
651
700
|
const totalText = `${task.units.total}`;
|
|
@@ -674,7 +723,7 @@ const formatAmount = (task, _tick) => {
|
|
|
674
723
|
};
|
|
675
724
|
|
|
676
725
|
//#endregion
|
|
677
|
-
//#region src/renderer/shared/text-width.ts
|
|
726
|
+
//#region src/services/renderer/shared/text-width.ts
|
|
678
727
|
const WIDTH_CACHE_LIMIT = 8192;
|
|
679
728
|
const widthCache = /* @__PURE__ */ new Map();
|
|
680
729
|
const textWidth = (text) => {
|
|
@@ -687,7 +736,7 @@ const textWidth = (text) => {
|
|
|
687
736
|
};
|
|
688
737
|
|
|
689
738
|
//#endregion
|
|
690
|
-
//#region src/renderer/columns/amount-column.tsx
|
|
739
|
+
//#region src/services/renderer/columns/amount-column.tsx
|
|
691
740
|
const hasUnknownTotalCounts = (task) => task.units.total === void 0 && task.units.processed > 0;
|
|
692
741
|
const hasCountedAmount = (task) => isDeterminate$2(task) || hasUnknownTotalCounts(task);
|
|
693
742
|
const totalTextFor = (task) => task.units.total === void 0 ? "?" : `${task.units.total}`;
|
|
@@ -723,7 +772,7 @@ const measureAmountLayout = (rows) => {
|
|
|
723
772
|
}, countedWidth)
|
|
724
773
|
};
|
|
725
774
|
};
|
|
726
|
-
const
|
|
775
|
+
const AmountValue = ({ task, layout }) => {
|
|
727
776
|
if (!hasCountedAmount(task)) return formatAmount(task, 0);
|
|
728
777
|
const processed = `${task.units.processed}`.padStart(layout.processedWidth, " ");
|
|
729
778
|
const total = totalTextFor(task).padStart(layout.totalWidth, " ");
|
|
@@ -746,16 +795,19 @@ const renderAmount = (task, layout) => {
|
|
|
746
795
|
};
|
|
747
796
|
const AmountCell = ({ task, layout }) => /* @__PURE__ */ jsx(Text, {
|
|
748
797
|
wrap: "truncate-end",
|
|
749
|
-
children:
|
|
798
|
+
children: /* @__PURE__ */ jsx(AmountValue, {
|
|
799
|
+
task,
|
|
800
|
+
layout
|
|
801
|
+
})
|
|
750
802
|
});
|
|
751
803
|
|
|
752
804
|
//#endregion
|
|
753
|
-
//#region src/renderer/columns/bar-column.tsx
|
|
805
|
+
//#region src/services/renderer/columns/bar-column.tsx
|
|
754
806
|
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
755
807
|
const prepareBar = (rows) => {
|
|
756
808
|
return { hasDeterminateRows: rows.some((row) => row.derived.isDeterminate) };
|
|
757
809
|
};
|
|
758
|
-
const
|
|
810
|
+
const ProgressBarSegments = ({ task, width }) => {
|
|
759
811
|
if (!isDeterminate$2(task)) return /* @__PURE__ */ jsx(Text, { children: ` `.repeat(Math.max(0, width)) });
|
|
760
812
|
const displayTotal = Math.max(task.units.total, task.units.succeeded + task.units.failed);
|
|
761
813
|
const succeededEnd = displayTotal === 0 ? width : Math.round(task.units.succeeded / displayTotal * width);
|
|
@@ -783,11 +835,14 @@ const renderProgressBar = (task, width) => {
|
|
|
783
835
|
};
|
|
784
836
|
const BarCell = ({ task, width }) => /* @__PURE__ */ jsx(Text, {
|
|
785
837
|
wrap: "truncate-end",
|
|
786
|
-
children:
|
|
838
|
+
children: /* @__PURE__ */ jsx(ProgressBarSegments, {
|
|
839
|
+
task,
|
|
840
|
+
width: width ?? 0
|
|
841
|
+
})
|
|
787
842
|
});
|
|
788
843
|
|
|
789
844
|
//#endregion
|
|
790
|
-
//#region src/renderer/columns/description-column.tsx
|
|
845
|
+
//#region src/services/renderer/columns/description-column.tsx
|
|
791
846
|
const MIN_TREE_DESCRIPTION_TEXT_WIDTH = 6;
|
|
792
847
|
const DEFAULT_SPINNER_TYPE = "dots";
|
|
793
848
|
const isDeterminate = (task) => task.units.total !== void 0;
|
|
@@ -826,7 +881,10 @@ const getTaskIndicator = (task, tick, spinnerType = DEFAULT_SPINNER_TYPE) => {
|
|
|
826
881
|
color: "green"
|
|
827
882
|
};
|
|
828
883
|
};
|
|
829
|
-
const prepareDescription = (rows) => ({
|
|
884
|
+
const prepareDescription = (rows) => ({
|
|
885
|
+
minTreeWidth: rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + MIN_TREE_DESCRIPTION_TEXT_WIDTH), MIN_TREE_DESCRIPTION_TEXT_WIDTH + 2),
|
|
886
|
+
preferredWidth: rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + row.derived.descriptionWidth), 2)
|
|
887
|
+
});
|
|
830
888
|
const TaskIndicatorGlyph = ({ task, tick, spinnerType = DEFAULT_SPINNER_TYPE }) => {
|
|
831
889
|
const indicator = getTaskIndicator(task, tick, spinnerType);
|
|
832
890
|
return /* @__PURE__ */ jsx(Text, {
|
|
@@ -862,7 +920,17 @@ const DescriptionCell = ({ cell, width, minTreeWidth, spinnerTick }) => {
|
|
|
862
920
|
};
|
|
863
921
|
|
|
864
922
|
//#endregion
|
|
865
|
-
//#region src/renderer/columns/elapsed-column.tsx
|
|
923
|
+
//#region src/services/renderer/columns/elapsed-eta-column.tsx
|
|
924
|
+
const ElapsedEtaCell = ({ task, now }) => {
|
|
925
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
926
|
+
wrap: "truncate-end",
|
|
927
|
+
color: "gray",
|
|
928
|
+
children: formatElapsedEta(task, now)
|
|
929
|
+
});
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
//#endregion
|
|
933
|
+
//#region src/services/renderer/columns/elapsed-column.tsx
|
|
866
934
|
const ElapsedCell = ({ task, now }) => {
|
|
867
935
|
return /* @__PURE__ */ jsx(Text, {
|
|
868
936
|
wrap: "truncate-end",
|
|
@@ -872,9 +940,9 @@ const ElapsedCell = ({ task, now }) => {
|
|
|
872
940
|
};
|
|
873
941
|
|
|
874
942
|
//#endregion
|
|
875
|
-
//#region src/renderer/columns/eta-column.tsx
|
|
876
|
-
const EtaCell = ({ task
|
|
877
|
-
const eta = formatEta(task
|
|
943
|
+
//#region src/services/renderer/columns/eta-column.tsx
|
|
944
|
+
const EtaCell = ({ task }) => {
|
|
945
|
+
const eta = formatEta(task);
|
|
878
946
|
if (eta === "") return null;
|
|
879
947
|
return /* @__PURE__ */ jsx(Text, {
|
|
880
948
|
wrap: "truncate-end",
|
|
@@ -891,10 +959,17 @@ var columns_exports = /* @__PURE__ */ __exportAll({
|
|
|
891
959
|
defaults: () => defaults,
|
|
892
960
|
description: () => description,
|
|
893
961
|
elapsed: () => elapsed,
|
|
962
|
+
elapsedEta: () => elapsedEta,
|
|
894
963
|
eta: () => eta,
|
|
895
964
|
resolveColumnSizeValue: () => resolveColumnSizeValue,
|
|
896
965
|
spacer: () => spacer
|
|
897
966
|
});
|
|
967
|
+
const DEFAULT_BAR_SIZE = 30;
|
|
968
|
+
const resolveBarSize = (size) => {
|
|
969
|
+
if (size === "fullwidth") return size;
|
|
970
|
+
if (typeof size !== "number" || !Number.isFinite(size)) return DEFAULT_BAR_SIZE;
|
|
971
|
+
return Math.max(1, Math.floor(size));
|
|
972
|
+
};
|
|
898
973
|
const spacer = ({ flexGrow, flexShrink, flexBasis, minWidth } = {}) => ({
|
|
899
974
|
render: () => null,
|
|
900
975
|
flexGrow,
|
|
@@ -904,8 +979,8 @@ const spacer = ({ flexGrow, flexShrink, flexBasis, minWidth } = {}) => ({
|
|
|
904
979
|
});
|
|
905
980
|
const description = () => ({
|
|
906
981
|
prepare: prepareDescription,
|
|
907
|
-
flexGrow: 1,
|
|
908
982
|
flexShrink: 1,
|
|
983
|
+
flexBasis: (prepared) => prepared.preferredWidth,
|
|
909
984
|
minWidth: 1,
|
|
910
985
|
render: (cell, ctx) => /* @__PURE__ */ jsx(DescriptionCell, {
|
|
911
986
|
cell,
|
|
@@ -914,16 +989,20 @@ const description = () => ({
|
|
|
914
989
|
spinnerTick: ctx.spinnerTick
|
|
915
990
|
})
|
|
916
991
|
});
|
|
917
|
-
const bar = () =>
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
992
|
+
const bar = ({ size } = {}) => {
|
|
993
|
+
const resolvedSize = resolveBarSize(size);
|
|
994
|
+
return {
|
|
995
|
+
prepare: prepareBar,
|
|
996
|
+
flexGrow: (prepared) => prepared.hasDeterminateRows && resolvedSize === "fullwidth" ? 1 : 0,
|
|
997
|
+
flexShrink: (prepared) => prepared.hasDeterminateRows ? 1 : 0,
|
|
998
|
+
flexBasis: (prepared) => prepared.hasDeterminateRows ? resolvedSize === "fullwidth" ? DEFAULT_BAR_SIZE : resolvedSize : 0,
|
|
999
|
+
minWidth: (prepared) => prepared.hasDeterminateRows ? resolvedSize === "fullwidth" ? 4 : resolvedSize : 0,
|
|
1000
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(BarCell, {
|
|
1001
|
+
task,
|
|
1002
|
+
width: ctx.width
|
|
1003
|
+
})
|
|
1004
|
+
};
|
|
1005
|
+
};
|
|
927
1006
|
const amount = () => ({
|
|
928
1007
|
prepare: measureAmountLayout,
|
|
929
1008
|
align: "right",
|
|
@@ -940,20 +1019,26 @@ const elapsed = () => ({
|
|
|
940
1019
|
now: ctx.now
|
|
941
1020
|
})
|
|
942
1021
|
});
|
|
943
|
-
const
|
|
1022
|
+
const elapsedEta = () => ({
|
|
944
1023
|
align: "right",
|
|
945
1024
|
flexShrink: 0,
|
|
946
|
-
|
|
1025
|
+
minWidth: 11,
|
|
1026
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(ElapsedEtaCell, {
|
|
947
1027
|
task,
|
|
948
1028
|
now: ctx.now
|
|
949
1029
|
})
|
|
950
1030
|
});
|
|
1031
|
+
const eta = () => ({
|
|
1032
|
+
align: "right",
|
|
1033
|
+
flexShrink: 0,
|
|
1034
|
+
minWidth: 8,
|
|
1035
|
+
render: ({ task }) => /* @__PURE__ */ jsx(EtaCell, { task })
|
|
1036
|
+
});
|
|
951
1037
|
const defaults = () => [
|
|
952
1038
|
description(),
|
|
953
1039
|
bar(),
|
|
954
1040
|
amount(),
|
|
955
|
-
|
|
956
|
-
eta()
|
|
1041
|
+
elapsedEta()
|
|
957
1042
|
];
|
|
958
1043
|
const resolveColumnSizeValue = (value, prepared) => {
|
|
959
1044
|
if (typeof value === "function") return value(prepared);
|
|
@@ -961,7 +1046,7 @@ const resolveColumnSizeValue = (value, prepared) => {
|
|
|
961
1046
|
};
|
|
962
1047
|
|
|
963
1048
|
//#endregion
|
|
964
|
-
//#region src/renderer/column-resolver.ts
|
|
1049
|
+
//#region src/services/renderer/column-resolver.ts
|
|
965
1050
|
const NO_PREPARE = Symbol("no-prepare");
|
|
966
1051
|
const getColumnsForRow = (row, columns) => columns.get(row.task.id) ?? defaults();
|
|
967
1052
|
const toCellInfo = (row) => row;
|
|
@@ -1015,13 +1100,13 @@ const resolveColumns = (rows, columns) => {
|
|
|
1015
1100
|
};
|
|
1016
1101
|
|
|
1017
1102
|
//#endregion
|
|
1018
|
-
//#region src/renderer/public-api.tsx
|
|
1103
|
+
//#region src/services/renderer/public-api.tsx
|
|
1019
1104
|
const justifyContentForAlign = (align) => {
|
|
1020
1105
|
if (align === "right") return "flex-end";
|
|
1021
1106
|
if (align === "center") return "center";
|
|
1022
1107
|
return "flex-start";
|
|
1023
1108
|
};
|
|
1024
|
-
const
|
|
1109
|
+
const RenderedNode = ({ node }) => {
|
|
1025
1110
|
if (typeof node === "string" || typeof node === "number") return /* @__PURE__ */ jsx(Text, {
|
|
1026
1111
|
wrap: "truncate-end",
|
|
1027
1112
|
children: node
|
|
@@ -1045,7 +1130,7 @@ const ColumnPosition = ({ position }) => {
|
|
|
1045
1130
|
const column = entry?.column;
|
|
1046
1131
|
const cell = row;
|
|
1047
1132
|
const output = column?.render(cell, {
|
|
1048
|
-
width: hasMeasured ? width :
|
|
1133
|
+
width: hasMeasured ? width : position.flexBasis,
|
|
1049
1134
|
now,
|
|
1050
1135
|
spinnerTick,
|
|
1051
1136
|
prepared: entry?.prepared
|
|
@@ -1053,7 +1138,7 @@ const ColumnPosition = ({ position }) => {
|
|
|
1053
1138
|
return /* @__PURE__ */ jsx(Box, {
|
|
1054
1139
|
height: 1,
|
|
1055
1140
|
justifyContent: justifyContentForAlign(column?.align),
|
|
1056
|
-
children:
|
|
1141
|
+
children: /* @__PURE__ */ jsx(RenderedNode, { node: output })
|
|
1057
1142
|
}, row.task.id);
|
|
1058
1143
|
})
|
|
1059
1144
|
});
|
|
@@ -1069,7 +1154,7 @@ const ProgressRenderer = ({ rows, columns }) => {
|
|
|
1069
1154
|
};
|
|
1070
1155
|
|
|
1071
1156
|
//#endregion
|
|
1072
|
-
//#region src/
|
|
1157
|
+
//#region src/services/store/render-snapshot.ts
|
|
1073
1158
|
const orderedVisibleTasks = (store) => store.renderOrder.flatMap((row) => {
|
|
1074
1159
|
const snapshot = store.tasks.get(row.id);
|
|
1075
1160
|
if (!snapshot || snapshot.transient && snapshot.status !== "running") return [];
|
|
@@ -1148,7 +1233,7 @@ const toRenderSnapshot = (store, previousSnapshot) => {
|
|
|
1148
1233
|
};
|
|
1149
1234
|
|
|
1150
1235
|
//#endregion
|
|
1151
|
-
//#region src/renderer/
|
|
1236
|
+
//#region src/services/renderer/hooks/use-progress-render-view.ts
|
|
1152
1237
|
const useRenderSnapshot = (storeSnapshot) => {
|
|
1153
1238
|
const previousSnapshotRef = useRef(void 0);
|
|
1154
1239
|
const renderSnapshot = useMemo(() => toRenderSnapshot(storeSnapshot, previousSnapshotRef.current), [storeSnapshot]);
|
|
@@ -1168,7 +1253,17 @@ const useProgressRenderView = (store) => {
|
|
|
1168
1253
|
};
|
|
1169
1254
|
|
|
1170
1255
|
//#endregion
|
|
1171
|
-
//#region src/
|
|
1256
|
+
//#region src/services/stdio.ts
|
|
1257
|
+
const defaultStdioService = {
|
|
1258
|
+
stdout: process.stdout,
|
|
1259
|
+
stderr: process.stderr
|
|
1260
|
+
};
|
|
1261
|
+
var ProgressStdio = class ProgressStdio extends Context.Service()("stromseng.dev/effective-progress/ProgressStdio") {
|
|
1262
|
+
static layer = Layer.succeed(ProgressStdio, defaultStdioService);
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
//#endregion
|
|
1266
|
+
//#region src/services/renderer/renderer.tsx
|
|
1172
1267
|
const MAX_FPS = 24;
|
|
1173
1268
|
const ProgressRoot = ({ store }) => {
|
|
1174
1269
|
const { renderSnapshot, hasRunningTasks, publication } = useProgressRenderView(store);
|
|
@@ -1183,57 +1278,43 @@ const ProgressRoot = ({ store }) => {
|
|
|
1183
1278
|
})
|
|
1184
1279
|
});
|
|
1185
1280
|
};
|
|
1186
|
-
const makeRendererv2InkRendererService = ()
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
})
|
|
1203
|
-
} };
|
|
1204
|
-
};
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
//#region src/services/ink-renderer.tsx
|
|
1208
|
-
var InkRenderer = class InkRenderer extends Context.Tag("stromseng.dev/effective-progress/InkRenderer")() {
|
|
1209
|
-
static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeRendererv2InkRendererService()));
|
|
1210
|
-
};
|
|
1211
|
-
|
|
1212
|
-
//#endregion
|
|
1213
|
-
//#region src/services/stdio.ts
|
|
1214
|
-
const defaultStdioService = {
|
|
1215
|
-
stdout: process.stdout,
|
|
1216
|
-
stderr: process.stderr
|
|
1217
|
-
};
|
|
1218
|
-
var ProgressStdio = class ProgressStdio extends Context.Tag("stromseng.dev/effective-progress/ProgressStdio")() {
|
|
1219
|
-
static Default = Layer.succeed(ProgressStdio, defaultStdioService);
|
|
1281
|
+
const makeRendererv2InkRendererService = Effect.gen(function* () {
|
|
1282
|
+
const store = yield* ProgressStore;
|
|
1283
|
+
const stdio = yield* ProgressStdio;
|
|
1284
|
+
const proot = /* @__PURE__ */ jsx(ProgressRoot, { store });
|
|
1285
|
+
return { run: Effect.sync(() => render(proot, {
|
|
1286
|
+
stdout: stdio.stdout,
|
|
1287
|
+
stderr: stdio.stderr,
|
|
1288
|
+
patchConsole: true,
|
|
1289
|
+
exitOnCtrlC: false,
|
|
1290
|
+
debug: false,
|
|
1291
|
+
maxFps: MAX_FPS
|
|
1292
|
+
})).pipe(Effect.flatMap((instance) => Effect.never.pipe(Effect.ensuring(Effect.gen(function* () {
|
|
1293
|
+
store.flush();
|
|
1294
|
+
instance.rerender(proot);
|
|
1295
|
+
yield* Effect.sync(() => {
|
|
1296
|
+
instance.unmount();
|
|
1297
|
+
});
|
|
1298
|
+
}))))) };
|
|
1299
|
+
});
|
|
1300
|
+
var Renderer = class Renderer extends Context.Service()("stromseng.dev/effective-progress/Renderer") {
|
|
1301
|
+
static layer = Layer.effect(Renderer, makeRendererv2InkRendererService);
|
|
1220
1302
|
};
|
|
1221
1303
|
|
|
1222
1304
|
//#endregion
|
|
1223
1305
|
//#region src/services/progress.ts
|
|
1224
1306
|
/** Builds the scoped implementation used by `ProgressService.task(...)` without auto-providing services. */
|
|
1225
1307
|
const makeProgressService = Effect.gen(function* () {
|
|
1226
|
-
const
|
|
1227
|
-
const
|
|
1228
|
-
const outerConsole = yield* Effect.console;
|
|
1229
|
-
const store = makeProgressRenderStore();
|
|
1230
|
-
const currentParentRef = yield* FiberRef.make(Option.none());
|
|
1308
|
+
const inkRenderer = yield* Renderer;
|
|
1309
|
+
const store = yield* ProgressStore;
|
|
1231
1310
|
const scope = yield* Effect.scope;
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1311
|
+
const parentOwner = Symbol();
|
|
1312
|
+
const currentParentId = Effect.map(CurrentParent, (cp) => Option.isSome(cp) && cp.value.owner === parentOwner ? Option.some(cp.value.taskId) : Option.none());
|
|
1313
|
+
const log = (...args) => Effect.log(...args);
|
|
1314
|
+
yield* Effect.forkIn(inkRenderer.run, scope, { startImmediately: true });
|
|
1234
1315
|
yield* Effect.sleep("0 millis");
|
|
1235
1316
|
const addTask = (options) => Effect.gen(function* () {
|
|
1236
|
-
const resolvedParentId = options.parentId === void 0 ? yield*
|
|
1317
|
+
const resolvedParentId = options.parentId === void 0 ? yield* currentParentId : Option.some(options.parentId);
|
|
1237
1318
|
return yield* store.addTask({
|
|
1238
1319
|
...options,
|
|
1239
1320
|
parentId: Option.isSome(resolvedParentId) ? resolvedParentId.value : void 0
|
|
@@ -1267,14 +1348,17 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
1267
1348
|
else yield* failTask(taskId);
|
|
1268
1349
|
});
|
|
1269
1350
|
const scopedTask = dual(2, (effect, options) => Effect.gen(function* () {
|
|
1270
|
-
const inheritedParentId = yield*
|
|
1351
|
+
const inheritedParentId = yield* currentParentId;
|
|
1271
1352
|
const resolvedParentId = options.parentId === void 0 ? inheritedParentId : Option.some(options.parentId);
|
|
1272
1353
|
const taskId = yield* addTask({
|
|
1273
1354
|
...options,
|
|
1274
1355
|
parentId: Option.isSome(resolvedParentId) ? resolvedParentId.value : void 0,
|
|
1275
1356
|
transient: options.transient
|
|
1276
1357
|
});
|
|
1277
|
-
return yield* Effect.
|
|
1358
|
+
return yield* Effect.provideService(Effect.provideService(effect, Task, taskId), CurrentParent, Option.some({
|
|
1359
|
+
owner: parentOwner,
|
|
1360
|
+
taskId
|
|
1361
|
+
}));
|
|
1278
1362
|
}));
|
|
1279
1363
|
const service = {
|
|
1280
1364
|
addTask,
|
|
@@ -1312,15 +1396,14 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
1312
1396
|
};
|
|
1313
1397
|
return Progress.of(service);
|
|
1314
1398
|
});
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
}));
|
|
1399
|
+
/**
|
|
1400
|
+
* Returns a layer that uses an already-provided service for the given tag when available,
|
|
1401
|
+
* or falls back to the supplied default layer otherwise.
|
|
1402
|
+
*/
|
|
1403
|
+
const serviceOptionDefaultLayer = (tag, defaultLayer) => Layer.unwrap(Effect.map(Effect.serviceOption(tag), (option) => Option.getOrElse(Option.map(option, (service) => Layer.succeed(tag, service)), () => defaultLayer)));
|
|
1404
|
+
const CurrentParent = Context.Reference("stromseng.dev/effective-progress/CurrentParent", { defaultValue: Option.none });
|
|
1405
|
+
var Progress = class Progress extends Context.Service()("stromseng.dev/effective-progress/Progress") {
|
|
1406
|
+
static layer = Layer.effect(Progress, makeProgressService).pipe(Layer.provide(serviceOptionDefaultLayer(Renderer, Renderer.layer).pipe(Layer.provideMerge(serviceOptionDefaultLayer(ProgressStdio, ProgressStdio.layer)), Layer.provideMerge(ProgressStore.layer))));
|
|
1324
1407
|
};
|
|
1325
1408
|
|
|
1326
1409
|
//#endregion
|
|
@@ -1338,7 +1421,7 @@ const inferTotal = (iterable) => {
|
|
|
1338
1421
|
const provideProgress = (effect) => Effect.gen(function* () {
|
|
1339
1422
|
const existing = yield* Effect.serviceOption(Progress);
|
|
1340
1423
|
if (Option.isSome(existing)) return yield* Effect.provideService(effect, Progress, existing.value);
|
|
1341
|
-
return yield* Effect.scoped(
|
|
1424
|
+
return yield* Effect.scoped(Effect.provide(effect, Progress.layer, { local: true }));
|
|
1342
1425
|
});
|
|
1343
1426
|
/**
|
|
1344
1427
|
* Runs an effect inside a task, creating and providing a `Progress` service automatically when one
|
|
@@ -1355,7 +1438,7 @@ const task = dual(2, (effectOrCallback, options) => {
|
|
|
1355
1438
|
});
|
|
1356
1439
|
const wrapEffects = (effects, tap) => Array.isArray(effects) ? effects.map(tap) : Object.fromEntries(Object.entries(effects).map(([k, effect]) => [k, tap(effect)]));
|
|
1357
1440
|
const countEffects = (effects) => Array.isArray(effects) ? effects.length : Object.keys(effects).length;
|
|
1358
|
-
const isCollectAllMode = (mode) => mode === "
|
|
1441
|
+
const isCollectAllMode = (mode) => mode === "result";
|
|
1359
1442
|
const allCountDisplay = (mode) => isCollectAllMode(mode) ? "detailed" : "processedOnly";
|
|
1360
1443
|
const wrapTrackedEffect = (progress, taskId, effect) => Effect.gen(function* () {
|
|
1361
1444
|
const exit = yield* Effect.exit(effect);
|
|
@@ -1363,7 +1446,7 @@ const wrapTrackedEffect = (progress, taskId, effect) => Effect.gen(function* ()
|
|
|
1363
1446
|
yield* progress.incrementSucceeded(taskId, 1);
|
|
1364
1447
|
return exit.value;
|
|
1365
1448
|
}
|
|
1366
|
-
if (Cause.
|
|
1449
|
+
if (Cause.hasInterruptsOnly(exit.cause)) return yield* Effect.failCause(exit.cause);
|
|
1367
1450
|
yield* progress.incrementFailed(taskId, 1);
|
|
1368
1451
|
return yield* Effect.failCause(exit.cause);
|
|
1369
1452
|
});
|
|
@@ -1383,10 +1466,8 @@ const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* (
|
|
|
1383
1466
|
const taskId = handle.id;
|
|
1384
1467
|
const exit = yield* Effect.exit(Effect.all(wrapEffects(effects, (effect) => wrapTrackedEffect(progress, taskId, effect)), {
|
|
1385
1468
|
concurrency: options.concurrency,
|
|
1386
|
-
batching: options.batching,
|
|
1387
1469
|
discard: options.discard,
|
|
1388
|
-
mode: options.mode
|
|
1389
|
-
concurrentFinalizers: options.concurrentFinalizers
|
|
1470
|
+
mode: options.mode
|
|
1390
1471
|
}));
|
|
1391
1472
|
if (Exit.isSuccess(exit)) yield* progress.completeTask(taskId);
|
|
1392
1473
|
else if (!isCollectAllMode(options.mode)) yield* progress.failTask(taskId);
|
|
@@ -1412,9 +1493,7 @@ const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(fun
|
|
|
1412
1493
|
const taskId = handle.id;
|
|
1413
1494
|
const exit = yield* Effect.exit(Effect.forEach(iterable, (item, index) => wrapTrackedEffect(progress, taskId, f(item, index)), {
|
|
1414
1495
|
concurrency: options.concurrency,
|
|
1415
|
-
|
|
1416
|
-
discard: options.discard,
|
|
1417
|
-
concurrentFinalizers: options.concurrentFinalizers
|
|
1496
|
+
discard: options.discard
|
|
1418
1497
|
}));
|
|
1419
1498
|
if (Exit.isSuccess(exit)) yield* progress.completeTask(taskId);
|
|
1420
1499
|
else yield* progress.failTask(taskId);
|
|
@@ -1431,4 +1510,4 @@ const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(fun
|
|
|
1431
1510
|
})));
|
|
1432
1511
|
|
|
1433
1512
|
//#endregion
|
|
1434
|
-
export { columns_exports as Columns, Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskRemovedEvent, TaskSnapshot, TaskSnapshotSchema, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|
|
1513
|
+
export { columns_exports as Columns, Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskProgressSampleSchema, TaskRemovedEvent, TaskSnapshot, TaskSnapshotSchema, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|