effective-progress 0.11.1 → 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 +30 -18
- package/dist/index.d.mts +74 -88
- package/dist/index.mjs +445 -428
- package/package.json +4 -3
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,8 +10,12 @@ 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,
|
|
@@ -36,7 +40,7 @@ const TaskSnapshotSchema = Schema.Struct({
|
|
|
36
40
|
metadata: Schema.Unknown
|
|
37
41
|
});
|
|
38
42
|
const TaskSnapshot = (snapshot) => snapshot;
|
|
39
|
-
var Task = class extends Context.
|
|
43
|
+
var Task = class extends Context.Service()("stromseng.dev/effective-progress/Task") {};
|
|
40
44
|
var TaskAddedEvent = class extends Schema.TaggedClass()("TaskAdded", {
|
|
41
45
|
taskId: TaskIdSchema,
|
|
42
46
|
parentId: Schema.NullOr(TaskIdSchema),
|
|
@@ -58,27 +62,30 @@ var TaskUpdatedEvent = class extends Schema.TaggedClass()("TaskUpdated", {
|
|
|
58
62
|
var TaskAdvancedEvent = class extends Schema.TaggedClass()("TaskAdvanced", {
|
|
59
63
|
taskId: TaskIdSchema,
|
|
60
64
|
amount: Schema.Number,
|
|
61
|
-
kind: Schema.
|
|
65
|
+
kind: Schema.Literals(["succeeded", "failed"])
|
|
62
66
|
}) {};
|
|
63
67
|
var TaskCompletedEvent = class extends Schema.TaggedClass()("TaskCompleted", { taskId: TaskIdSchema }) {};
|
|
64
68
|
var TaskFailedEvent = class extends Schema.TaggedClass()("TaskFailed", { taskId: TaskIdSchema }) {};
|
|
65
69
|
var TaskRemovedEvent = class extends Schema.TaggedClass()("TaskRemoved", { taskId: TaskIdSchema }) {};
|
|
66
|
-
const ProgressTaskEventSchema = Schema.Union(
|
|
70
|
+
const ProgressTaskEventSchema = Schema.Union([
|
|
71
|
+
TaskAddedEvent,
|
|
72
|
+
TaskUpdatedEvent,
|
|
73
|
+
TaskAdvancedEvent,
|
|
74
|
+
TaskCompletedEvent,
|
|
75
|
+
TaskFailedEvent,
|
|
76
|
+
TaskRemovedEvent
|
|
77
|
+
]);
|
|
67
78
|
const decodeProgressTaskEvent = Schema.decodeUnknownSync(ProgressTaskEventSchema);
|
|
68
79
|
|
|
69
80
|
//#endregion
|
|
70
|
-
//#region src/
|
|
81
|
+
//#region src/services/store/store.ts
|
|
71
82
|
const ETA_SAMPLE_WINDOW_MILLIS = 3e4;
|
|
72
83
|
const ETA_SAMPLE_MAX_LENGTH = 1e3;
|
|
73
84
|
const hasExplicitTotal = (options) => Object.prototype.hasOwnProperty.call(options, "total");
|
|
74
|
-
const
|
|
85
|
+
const sanitizeTotal = (total) => {
|
|
75
86
|
if (total === void 0) return;
|
|
76
87
|
return total < 0 ? void 0 : total;
|
|
77
88
|
};
|
|
78
|
-
const sanitizeTotalOnUpdate = (nextTotal) => {
|
|
79
|
-
if (nextTotal === void 0) return;
|
|
80
|
-
return nextTotal < 0 ? void 0 : nextTotal;
|
|
81
|
-
};
|
|
82
89
|
const normalizeUnits = (counts) => {
|
|
83
90
|
const succeeded = Math.max(0, counts.succeeded);
|
|
84
91
|
const failed = Math.max(0, counts.failed);
|
|
@@ -103,12 +110,13 @@ const appendProgressSample = (samples, now, processed) => {
|
|
|
103
110
|
const previousSamples = samples ?? [];
|
|
104
111
|
if (previousSamples.at(-1)?.processed === processed) return previousSamples;
|
|
105
112
|
const windowStart = now - ETA_SAMPLE_WINDOW_MILLIS;
|
|
106
|
-
const
|
|
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), {
|
|
107
117
|
timestamp: now,
|
|
108
118
|
processed
|
|
109
119
|
}];
|
|
110
|
-
while (nextSamples.length > 2 && (nextSamples.length > ETA_SAMPLE_MAX_LENGTH || nextSamples[1].timestamp < windowStart)) nextSamples.shift();
|
|
111
|
-
return nextSamples;
|
|
112
120
|
};
|
|
113
121
|
/** Applies mutable task fields and records a progress sample when the processed count changes. */
|
|
114
122
|
const updatedSnapshot = (snapshot, options, now) => {
|
|
@@ -116,7 +124,7 @@ const updatedSnapshot = (snapshot, options, now) => {
|
|
|
116
124
|
const units = options.succeeded === void 0 && options.failed === void 0 && options.total === void 0 && !hasExplicitTotal(options) ? currentUnits : normalizeUnits({
|
|
117
125
|
succeeded: options.succeeded ?? currentUnits.succeeded,
|
|
118
126
|
failed: options.failed ?? currentUnits.failed,
|
|
119
|
-
total: hasExplicitTotal(options) ?
|
|
127
|
+
total: hasExplicitTotal(options) ? sanitizeTotal(options.total) : currentUnits.total
|
|
120
128
|
});
|
|
121
129
|
return TaskSnapshot({
|
|
122
130
|
...snapshot,
|
|
@@ -163,8 +171,19 @@ const subtreeTaskIds = (renderOrder, taskId) => {
|
|
|
163
171
|
while (end < renderOrder.length && renderOrder[end].depth > taskDepth) end++;
|
|
164
172
|
return renderOrder.slice(idx, end).map((row) => row.id);
|
|
165
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
|
+
};
|
|
166
185
|
const SNAPSHOT_PUBLISH_INTERVAL_MILLIS = 100;
|
|
167
|
-
const
|
|
186
|
+
const makeProgressStoreRuntime = (publishQueue) => {
|
|
168
187
|
let nextTaskId = 0;
|
|
169
188
|
let state = {
|
|
170
189
|
tasks: /* @__PURE__ */ new Map(),
|
|
@@ -177,13 +196,13 @@ const makeProgressRenderStore = () => {
|
|
|
177
196
|
events: []
|
|
178
197
|
};
|
|
179
198
|
let hasPendingPublish = false;
|
|
180
|
-
let lastPublishAt =
|
|
181
|
-
let
|
|
199
|
+
let lastPublishAt = -SNAPSHOT_PUBLISH_INTERVAL_MILLIS;
|
|
200
|
+
let latestObservedAt = 0;
|
|
182
201
|
const listeners = /* @__PURE__ */ new Set();
|
|
183
202
|
const notifyListeners = () => {
|
|
184
203
|
for (const listener of listeners) listener();
|
|
185
204
|
};
|
|
186
|
-
const publishNow = () => {
|
|
205
|
+
const publishNow = (publishedAt) => {
|
|
187
206
|
const nextPublication = {
|
|
188
207
|
snapshot: state,
|
|
189
208
|
events: [...pendingEvents]
|
|
@@ -192,350 +211,351 @@ const makeProgressRenderStore = () => {
|
|
|
192
211
|
publishedPublication = nextPublication;
|
|
193
212
|
notifyListeners();
|
|
194
213
|
pendingEvents = [];
|
|
195
|
-
lastPublishAt =
|
|
196
|
-
};
|
|
197
|
-
const clearScheduledPublish = () => {
|
|
198
|
-
if (publishTimeout === void 0) return;
|
|
199
|
-
clearTimeout(publishTimeout);
|
|
200
|
-
publishTimeout = void 0;
|
|
214
|
+
lastPublishAt = publishedAt;
|
|
201
215
|
};
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
const now =
|
|
216
|
+
const publisherLoop = Effect.forever(Effect.gen(function* () {
|
|
217
|
+
yield* Queue.take(publishQueue);
|
|
218
|
+
const now = yield* Clock.currentTimeMillis;
|
|
205
219
|
const waitMillis = Math.max(0, SNAPSHOT_PUBLISH_INTERVAL_MILLIS - (now - lastPublishAt));
|
|
206
|
-
if (waitMillis
|
|
207
|
-
|
|
208
|
-
|
|
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);
|
|
209
229
|
return;
|
|
210
230
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}, waitMillis);
|
|
217
|
-
};
|
|
218
|
-
const publish = (update) => {
|
|
219
|
-
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;
|
|
220
236
|
state = update.state;
|
|
221
237
|
if (update.events.length > 0) pendingEvents.push(...update.events);
|
|
222
238
|
hasPendingPublish = true;
|
|
223
|
-
schedulePublish
|
|
239
|
+
return schedulePublish;
|
|
224
240
|
};
|
|
225
|
-
const updateState = (transform) => {
|
|
226
|
-
publish(transform(state));
|
|
241
|
+
const updateState = (transform, now) => {
|
|
242
|
+
return publish(transform(state), now);
|
|
227
243
|
};
|
|
228
244
|
return {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
},
|
|
236
|
-
flush: () => {
|
|
237
|
-
if (!hasPendingPublish) return;
|
|
238
|
-
clearScheduledPublish();
|
|
239
|
-
publishNow();
|
|
240
|
-
},
|
|
241
|
-
addTask: (options) => Effect.gen(function* () {
|
|
242
|
-
const taskId = TaskId(++nextTaskId);
|
|
243
|
-
const units = normalizeUnits({
|
|
244
|
-
succeeded: 0,
|
|
245
|
-
failed: 0,
|
|
246
|
-
total: sanitizeTotalOnAdd(options.total)
|
|
247
|
-
});
|
|
248
|
-
const parentSnapshot = options.parentId === void 0 ? void 0 : state.tasks.get(options.parentId);
|
|
249
|
-
const now = yield* Clock.currentTimeMillis;
|
|
250
|
-
const parentId = options.parentId ?? null;
|
|
251
|
-
const countDisplay = options.countDisplay ?? parentSnapshot?.countDisplay ?? "detailed";
|
|
252
|
-
const task = TaskSnapshot({
|
|
253
|
-
id: taskId,
|
|
254
|
-
parentId,
|
|
255
|
-
description: options.description,
|
|
256
|
-
status: "running",
|
|
257
|
-
countDisplay,
|
|
258
|
-
transient: (parentSnapshot?.transient ?? false) || (options.transient ?? false),
|
|
259
|
-
units,
|
|
260
|
-
startedAt: now,
|
|
261
|
-
completedAt: null,
|
|
262
|
-
progressSamples: [{
|
|
263
|
-
timestamp: now,
|
|
264
|
-
processed: units.processed
|
|
265
|
-
}],
|
|
266
|
-
metadata: options.metadata
|
|
267
|
-
});
|
|
268
|
-
updateState((current) => {
|
|
269
|
-
const nextTasks = new Map(current.tasks);
|
|
270
|
-
nextTasks.set(taskId, task);
|
|
271
|
-
const { index, depth } = findInsertionIndex(current.renderOrder, parentId);
|
|
272
|
-
const nextRenderOrder = [...current.renderOrder];
|
|
273
|
-
nextRenderOrder.splice(index, 0, {
|
|
274
|
-
id: taskId,
|
|
275
|
-
depth
|
|
276
|
-
});
|
|
277
|
-
return {
|
|
278
|
-
state: {
|
|
279
|
-
tasks: nextTasks,
|
|
280
|
-
renderOrder: nextRenderOrder,
|
|
281
|
-
columns: options.columns ? new Map(current.columns).set(taskId, options.columns) : current.columns
|
|
282
|
-
},
|
|
283
|
-
events: [new TaskAddedEvent({
|
|
284
|
-
taskId,
|
|
285
|
-
parentId,
|
|
286
|
-
description: task.description,
|
|
287
|
-
total: task.units.total,
|
|
288
|
-
transient: task.transient,
|
|
289
|
-
countDisplay: task.countDisplay
|
|
290
|
-
})]
|
|
291
|
-
};
|
|
292
|
-
});
|
|
293
|
-
return taskId;
|
|
294
|
-
}),
|
|
295
|
-
updateTask: (taskId, options) => Effect.gen(function* () {
|
|
296
|
-
const now = yield* Clock.currentTimeMillis;
|
|
297
|
-
updateState((current) => {
|
|
298
|
-
const currentTask = current.tasks.get(taskId);
|
|
299
|
-
if (!currentTask) return {
|
|
300
|
-
state: current,
|
|
301
|
-
events: []
|
|
302
|
-
};
|
|
303
|
-
const nextTask = updatedSnapshot(currentTask, options, now);
|
|
304
|
-
const nextTasks = new Map(current.tasks);
|
|
305
|
-
nextTasks.set(taskId, nextTask);
|
|
306
|
-
const events = [new TaskUpdatedEvent({
|
|
307
|
-
taskId,
|
|
308
|
-
description: options.description ?? void 0,
|
|
309
|
-
succeeded: options.succeeded ?? void 0,
|
|
310
|
-
failed: options.failed ?? void 0,
|
|
311
|
-
processed: options.succeeded !== void 0 || options.failed !== void 0 ? nextTask.units.processed : void 0,
|
|
312
|
-
total: hasExplicitTotal(options) ? nextTask.units.total : void 0,
|
|
313
|
-
transient: options.transient ?? void 0,
|
|
314
|
-
countDisplay: options.countDisplay ?? void 0
|
|
315
|
-
})];
|
|
316
|
-
if (options.transient !== void 0) for (const candidateId of subtreeTaskIds(current.renderOrder, taskId).slice(1)) {
|
|
317
|
-
const candidate = current.tasks.get(candidateId);
|
|
318
|
-
if (!candidate) continue;
|
|
319
|
-
const nextCandidate = TaskSnapshot({
|
|
320
|
-
...candidate,
|
|
321
|
-
transient: nextTask.transient
|
|
322
|
-
});
|
|
323
|
-
nextTasks.set(candidateId, nextCandidate);
|
|
324
|
-
events.push(new TaskUpdatedEvent({
|
|
325
|
-
taskId: candidateId,
|
|
326
|
-
transient: nextCandidate.transient
|
|
327
|
-
}));
|
|
328
|
-
}
|
|
329
|
-
return {
|
|
330
|
-
state: {
|
|
331
|
-
tasks: nextTasks,
|
|
332
|
-
renderOrder: current.renderOrder,
|
|
333
|
-
columns: current.columns
|
|
334
|
-
},
|
|
335
|
-
events
|
|
336
|
-
};
|
|
337
|
-
});
|
|
338
|
-
}),
|
|
339
|
-
incrementSucceeded: (taskId, amount = 1) => Effect.gen(function* () {
|
|
340
|
-
const now = yield* Clock.currentTimeMillis;
|
|
341
|
-
updateState((current) => {
|
|
342
|
-
const currentTask = current.tasks.get(taskId);
|
|
343
|
-
if (!currentTask) return {
|
|
344
|
-
state: current,
|
|
345
|
-
events: []
|
|
245
|
+
store: {
|
|
246
|
+
getSnapshot: () => publishedPublication,
|
|
247
|
+
subscribe: (listener) => {
|
|
248
|
+
listeners.add(listener);
|
|
249
|
+
return () => {
|
|
250
|
+
listeners.delete(listener);
|
|
346
251
|
};
|
|
252
|
+
},
|
|
253
|
+
flush: () => {
|
|
254
|
+
if (!hasPendingPublish) return;
|
|
255
|
+
publishNow(latestObservedAt);
|
|
256
|
+
},
|
|
257
|
+
addTask: (options) => Effect.gen(function* () {
|
|
258
|
+
const taskId = TaskId(++nextTaskId);
|
|
347
259
|
const units = normalizeUnits({
|
|
348
|
-
succeeded:
|
|
349
|
-
failed:
|
|
350
|
-
total:
|
|
260
|
+
succeeded: 0,
|
|
261
|
+
failed: 0,
|
|
262
|
+
total: sanitizeTotal(options.total)
|
|
351
263
|
});
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
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({
|
|
269
|
+
id: taskId,
|
|
270
|
+
parentId,
|
|
271
|
+
description: options.description,
|
|
272
|
+
status: "running",
|
|
273
|
+
countDisplay,
|
|
274
|
+
transient: (parentSnapshot?.transient ?? false) || (options.transient ?? false),
|
|
355
275
|
units,
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
},
|
|
364
|
-
events: [new TaskAdvancedEvent({
|
|
365
|
-
taskId,
|
|
366
|
-
amount,
|
|
367
|
-
kind: "succeeded"
|
|
368
|
-
})]
|
|
369
|
-
};
|
|
370
|
-
});
|
|
371
|
-
}),
|
|
372
|
-
incrementFailed: (taskId, amount = 1) => Effect.gen(function* () {
|
|
373
|
-
const now = yield* Clock.currentTimeMillis;
|
|
374
|
-
updateState((current) => {
|
|
375
|
-
const currentTask = current.tasks.get(taskId);
|
|
376
|
-
if (!currentTask) return {
|
|
377
|
-
state: current,
|
|
378
|
-
events: []
|
|
379
|
-
};
|
|
380
|
-
const units = normalizeUnits({
|
|
381
|
-
succeeded: currentTask.units.succeeded,
|
|
382
|
-
failed: currentTask.units.failed + amount,
|
|
383
|
-
total: currentTask.units.total
|
|
276
|
+
startedAt: now,
|
|
277
|
+
completedAt: null,
|
|
278
|
+
progressSamples: [{
|
|
279
|
+
timestamp: now,
|
|
280
|
+
processed: units.processed
|
|
281
|
+
}],
|
|
282
|
+
metadata: options.metadata
|
|
384
283
|
});
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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({
|
|
398
323
|
taskId,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
421
|
-
const nextColumns = new Map(current.columns);
|
|
422
|
-
for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
|
|
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
|
+
}
|
|
423
345
|
return {
|
|
424
346
|
state: {
|
|
425
347
|
tasks: nextTasks,
|
|
426
|
-
renderOrder:
|
|
427
|
-
columns:
|
|
348
|
+
renderOrder: current.renderOrder,
|
|
349
|
+
columns: current.columns
|
|
428
350
|
},
|
|
429
|
-
events
|
|
351
|
+
events
|
|
430
352
|
};
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
columns: current.columns
|
|
453
|
-
},
|
|
454
|
-
events: [new TaskCompletedEvent({ taskId })]
|
|
455
|
-
};
|
|
456
|
-
});
|
|
457
|
-
}),
|
|
458
|
-
failTask: (taskId) => Effect.gen(function* () {
|
|
459
|
-
const now = yield* Clock.currentTimeMillis;
|
|
460
|
-
updateState((current) => {
|
|
461
|
-
const currentTask = current.tasks.get(taskId);
|
|
462
|
-
if (!currentTask) return {
|
|
463
|
-
state: current,
|
|
464
|
-
events: []
|
|
465
|
-
};
|
|
466
|
-
if (currentTask.status !== "running") return {
|
|
467
|
-
state: current,
|
|
468
|
-
events: []
|
|
469
|
-
};
|
|
470
|
-
const nextTasks = new Map(current.tasks);
|
|
471
|
-
if (currentTask.transient) {
|
|
472
|
-
const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
|
|
473
|
-
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
474
|
-
const nextColumns = new Map(current.columns);
|
|
475
|
-
for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
|
|
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({
|
|
364
|
+
succeeded: currentTask.units.succeeded + amount,
|
|
365
|
+
failed: currentTask.units.failed,
|
|
366
|
+
total: currentTask.units.total
|
|
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
|
+
}));
|
|
476
374
|
return {
|
|
477
375
|
state: {
|
|
478
376
|
tasks: nextTasks,
|
|
479
|
-
renderOrder:
|
|
480
|
-
columns:
|
|
377
|
+
renderOrder: current.renderOrder,
|
|
378
|
+
columns: current.columns
|
|
481
379
|
},
|
|
482
|
-
events: [new
|
|
380
|
+
events: [new TaskAdvancedEvent({
|
|
381
|
+
taskId,
|
|
382
|
+
amount,
|
|
383
|
+
kind: "succeeded"
|
|
384
|
+
})]
|
|
483
385
|
};
|
|
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
|
-
|
|
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({
|
|
397
|
+
succeeded: currentTask.units.succeeded,
|
|
398
|
+
failed: currentTask.units.failed + amount,
|
|
399
|
+
total: currentTask.units.total
|
|
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
|
+
}));
|
|
407
|
+
return {
|
|
408
|
+
state: {
|
|
409
|
+
tasks: nextTasks,
|
|
410
|
+
renderOrder: current.renderOrder,
|
|
411
|
+
columns: current.columns
|
|
412
|
+
},
|
|
413
|
+
events: [new TaskAdvancedEvent({
|
|
414
|
+
taskId,
|
|
415
|
+
amount,
|
|
416
|
+
kind: "failed"
|
|
417
|
+
})]
|
|
418
|
+
};
|
|
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({
|
|
446
|
+
succeeded: currentTask.units.succeeded + (currentTask.units.total - currentTask.units.processed),
|
|
447
|
+
failed: currentTask.units.failed,
|
|
448
|
+
total: currentTask.units.total
|
|
449
|
+
}) : currentTask.units : currentTask.units.processed > 0 ? normalizeUnits({
|
|
450
|
+
succeeded: currentTask.units.succeeded,
|
|
451
|
+
failed: currentTask.units.failed,
|
|
452
|
+
total: currentTask.units.processed
|
|
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
|
+
}));
|
|
461
|
+
return {
|
|
462
|
+
state: {
|
|
463
|
+
tasks: nextTasks,
|
|
464
|
+
renderOrder: current.renderOrder,
|
|
465
|
+
columns: current.columns
|
|
466
|
+
},
|
|
467
|
+
events: [new TaskCompletedEvent({ taskId })]
|
|
468
|
+
};
|
|
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
|
|
527
539
|
};
|
|
528
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
|
+
};
|
|
529
549
|
|
|
530
550
|
//#endregion
|
|
531
|
-
//#region src/renderer/hooks/use-now-clock.ts
|
|
551
|
+
//#region src/services/renderer/hooks/use-now-clock.ts
|
|
532
552
|
const useNowClock = (active, intervalMillis) => {
|
|
533
|
-
const [now,
|
|
553
|
+
const [now, updateNow] = useReducer(() => Date.now(), void 0, Date.now);
|
|
534
554
|
useEffect(() => {
|
|
535
555
|
if (!active) return;
|
|
536
|
-
|
|
556
|
+
updateNow();
|
|
537
557
|
const interval = setInterval(() => {
|
|
538
|
-
|
|
558
|
+
updateNow();
|
|
539
559
|
}, intervalMillis);
|
|
540
560
|
return () => {
|
|
541
561
|
clearInterval(interval);
|
|
@@ -545,7 +565,7 @@ const useNowClock = (active, intervalMillis) => {
|
|
|
545
565
|
};
|
|
546
566
|
|
|
547
567
|
//#endregion
|
|
548
|
-
//#region src/renderer/context/now-context.tsx
|
|
568
|
+
//#region src/services/renderer/context/now-context.tsx
|
|
549
569
|
const NOW_INTERVAL_MILLIS = 1e3;
|
|
550
570
|
const NowContext = createContext(Date.now());
|
|
551
571
|
const NowProvider = ({ active, children, nowOverride }) => {
|
|
@@ -556,10 +576,10 @@ const NowProvider = ({ active, children, nowOverride }) => {
|
|
|
556
576
|
children
|
|
557
577
|
});
|
|
558
578
|
};
|
|
559
|
-
const useNow = () =>
|
|
579
|
+
const useNow = () => use(NowContext);
|
|
560
580
|
|
|
561
581
|
//#endregion
|
|
562
|
-
//#region src/renderer/hooks/use-spinner-clock.ts
|
|
582
|
+
//#region src/services/renderer/hooks/use-spinner-clock.ts
|
|
563
583
|
const normalizeIntervalMillis = (intervalMillis) => Math.max(1, intervalMillis);
|
|
564
584
|
const getSpinnerTickAtTime = (baseTick, startedAt, now, intervalMillis) => {
|
|
565
585
|
const elapsedMillis = Math.max(0, now - startedAt);
|
|
@@ -590,7 +610,7 @@ const useSpinnerClock = (active, intervalMillis) => {
|
|
|
590
610
|
};
|
|
591
611
|
|
|
592
612
|
//#endregion
|
|
593
|
-
//#region src/renderer/context/spinner-context.tsx
|
|
613
|
+
//#region src/services/renderer/context/spinner-context.tsx
|
|
594
614
|
const DEFAULT_SPINNER_INTERVAL_MILLIS = cliSpinners.dots.interval;
|
|
595
615
|
const SpinnerContext = createContext(0);
|
|
596
616
|
const SpinnerProvider = ({ active, children, intervalMillis = DEFAULT_SPINNER_INTERVAL_MILLIS, tickOverride }) => {
|
|
@@ -601,14 +621,14 @@ const SpinnerProvider = ({ active, children, intervalMillis = DEFAULT_SPINNER_IN
|
|
|
601
621
|
children
|
|
602
622
|
});
|
|
603
623
|
};
|
|
604
|
-
const useSpinnerTick = () =>
|
|
624
|
+
const useSpinnerTick = () => use(SpinnerContext);
|
|
605
625
|
|
|
606
626
|
//#endregion
|
|
607
|
-
//#region src/renderer/shared/determinate.ts
|
|
627
|
+
//#region src/services/renderer/shared/determinate.ts
|
|
608
628
|
const isDeterminate$2 = (task) => task.units.total !== void 0;
|
|
609
629
|
|
|
610
630
|
//#endregion
|
|
611
|
-
//#region src/renderer/shared/format.ts
|
|
631
|
+
//#region src/services/renderer/shared/format.ts
|
|
612
632
|
const isDeterminate$1 = (task) => task.units.total !== void 0;
|
|
613
633
|
const showsUnknownTotalCounts = (task) => task.units.total === void 0 && task.units.processed > 0;
|
|
614
634
|
const formatDurationSeconds = (seconds) => {
|
|
@@ -656,7 +676,7 @@ const formatElapsed = (task, now) => {
|
|
|
656
676
|
const formatElapsedClock = (task, now) => {
|
|
657
677
|
return formatClockDurationSeconds(Math.max(0, (task.completedAt ?? now) - task.startedAt) / 1e3);
|
|
658
678
|
};
|
|
659
|
-
const formatEta = (task
|
|
679
|
+
const formatEta = (task) => {
|
|
660
680
|
if (task.status !== "running" || !isDeterminate$1(task)) return "";
|
|
661
681
|
const { processed, total } = task.units;
|
|
662
682
|
const remaining = total - processed;
|
|
@@ -665,7 +685,7 @@ const formatEta = (task, now) => {
|
|
|
665
685
|
if (etaMillis === void 0) return "";
|
|
666
686
|
return formatClockDurationSeconds(etaMillis / 1e3);
|
|
667
687
|
};
|
|
668
|
-
const formatEtaClock = (task
|
|
688
|
+
const formatEtaClock = (task) => {
|
|
669
689
|
if (task.status !== "running" || !isDeterminate$1(task)) return;
|
|
670
690
|
const { processed, total } = task.units;
|
|
671
691
|
const remaining = total - processed;
|
|
@@ -674,7 +694,7 @@ const formatEtaClock = (task, now) => {
|
|
|
674
694
|
if (etaMillis === void 0) return;
|
|
675
695
|
return formatClockDurationSeconds(etaMillis / 1e3);
|
|
676
696
|
};
|
|
677
|
-
const formatElapsedEta = (task, now) => `${formatElapsedClock(task, now)}<${formatEtaClock(task
|
|
697
|
+
const formatElapsedEta = (task, now) => `${formatElapsedClock(task, now)}<${formatEtaClock(task) ?? "00:00"}`;
|
|
678
698
|
const formatDeterminateAmountParts = (task) => {
|
|
679
699
|
if (!isDeterminate$1(task)) return;
|
|
680
700
|
const totalText = `${task.units.total}`;
|
|
@@ -703,7 +723,7 @@ const formatAmount = (task, _tick) => {
|
|
|
703
723
|
};
|
|
704
724
|
|
|
705
725
|
//#endregion
|
|
706
|
-
//#region src/renderer/shared/text-width.ts
|
|
726
|
+
//#region src/services/renderer/shared/text-width.ts
|
|
707
727
|
const WIDTH_CACHE_LIMIT = 8192;
|
|
708
728
|
const widthCache = /* @__PURE__ */ new Map();
|
|
709
729
|
const textWidth = (text) => {
|
|
@@ -716,7 +736,7 @@ const textWidth = (text) => {
|
|
|
716
736
|
};
|
|
717
737
|
|
|
718
738
|
//#endregion
|
|
719
|
-
//#region src/renderer/columns/amount-column.tsx
|
|
739
|
+
//#region src/services/renderer/columns/amount-column.tsx
|
|
720
740
|
const hasUnknownTotalCounts = (task) => task.units.total === void 0 && task.units.processed > 0;
|
|
721
741
|
const hasCountedAmount = (task) => isDeterminate$2(task) || hasUnknownTotalCounts(task);
|
|
722
742
|
const totalTextFor = (task) => task.units.total === void 0 ? "?" : `${task.units.total}`;
|
|
@@ -752,7 +772,7 @@ const measureAmountLayout = (rows) => {
|
|
|
752
772
|
}, countedWidth)
|
|
753
773
|
};
|
|
754
774
|
};
|
|
755
|
-
const
|
|
775
|
+
const AmountValue = ({ task, layout }) => {
|
|
756
776
|
if (!hasCountedAmount(task)) return formatAmount(task, 0);
|
|
757
777
|
const processed = `${task.units.processed}`.padStart(layout.processedWidth, " ");
|
|
758
778
|
const total = totalTextFor(task).padStart(layout.totalWidth, " ");
|
|
@@ -775,16 +795,19 @@ const renderAmount = (task, layout) => {
|
|
|
775
795
|
};
|
|
776
796
|
const AmountCell = ({ task, layout }) => /* @__PURE__ */ jsx(Text, {
|
|
777
797
|
wrap: "truncate-end",
|
|
778
|
-
children:
|
|
798
|
+
children: /* @__PURE__ */ jsx(AmountValue, {
|
|
799
|
+
task,
|
|
800
|
+
layout
|
|
801
|
+
})
|
|
779
802
|
});
|
|
780
803
|
|
|
781
804
|
//#endregion
|
|
782
|
-
//#region src/renderer/columns/bar-column.tsx
|
|
805
|
+
//#region src/services/renderer/columns/bar-column.tsx
|
|
783
806
|
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
784
807
|
const prepareBar = (rows) => {
|
|
785
808
|
return { hasDeterminateRows: rows.some((row) => row.derived.isDeterminate) };
|
|
786
809
|
};
|
|
787
|
-
const
|
|
810
|
+
const ProgressBarSegments = ({ task, width }) => {
|
|
788
811
|
if (!isDeterminate$2(task)) return /* @__PURE__ */ jsx(Text, { children: ` `.repeat(Math.max(0, width)) });
|
|
789
812
|
const displayTotal = Math.max(task.units.total, task.units.succeeded + task.units.failed);
|
|
790
813
|
const succeededEnd = displayTotal === 0 ? width : Math.round(task.units.succeeded / displayTotal * width);
|
|
@@ -812,11 +835,14 @@ const renderProgressBar = (task, width) => {
|
|
|
812
835
|
};
|
|
813
836
|
const BarCell = ({ task, width }) => /* @__PURE__ */ jsx(Text, {
|
|
814
837
|
wrap: "truncate-end",
|
|
815
|
-
children:
|
|
838
|
+
children: /* @__PURE__ */ jsx(ProgressBarSegments, {
|
|
839
|
+
task,
|
|
840
|
+
width: width ?? 0
|
|
841
|
+
})
|
|
816
842
|
});
|
|
817
843
|
|
|
818
844
|
//#endregion
|
|
819
|
-
//#region src/renderer/columns/description-column.tsx
|
|
845
|
+
//#region src/services/renderer/columns/description-column.tsx
|
|
820
846
|
const MIN_TREE_DESCRIPTION_TEXT_WIDTH = 6;
|
|
821
847
|
const DEFAULT_SPINNER_TYPE = "dots";
|
|
822
848
|
const isDeterminate = (task) => task.units.total !== void 0;
|
|
@@ -894,7 +920,7 @@ const DescriptionCell = ({ cell, width, minTreeWidth, spinnerTick }) => {
|
|
|
894
920
|
};
|
|
895
921
|
|
|
896
922
|
//#endregion
|
|
897
|
-
//#region src/renderer/columns/elapsed-eta-column.tsx
|
|
923
|
+
//#region src/services/renderer/columns/elapsed-eta-column.tsx
|
|
898
924
|
const ElapsedEtaCell = ({ task, now }) => {
|
|
899
925
|
return /* @__PURE__ */ jsx(Text, {
|
|
900
926
|
wrap: "truncate-end",
|
|
@@ -904,7 +930,7 @@ const ElapsedEtaCell = ({ task, now }) => {
|
|
|
904
930
|
};
|
|
905
931
|
|
|
906
932
|
//#endregion
|
|
907
|
-
//#region src/renderer/columns/elapsed-column.tsx
|
|
933
|
+
//#region src/services/renderer/columns/elapsed-column.tsx
|
|
908
934
|
const ElapsedCell = ({ task, now }) => {
|
|
909
935
|
return /* @__PURE__ */ jsx(Text, {
|
|
910
936
|
wrap: "truncate-end",
|
|
@@ -914,9 +940,9 @@ const ElapsedCell = ({ task, now }) => {
|
|
|
914
940
|
};
|
|
915
941
|
|
|
916
942
|
//#endregion
|
|
917
|
-
//#region src/renderer/columns/eta-column.tsx
|
|
918
|
-
const EtaCell = ({ task
|
|
919
|
-
const eta = formatEta(task
|
|
943
|
+
//#region src/services/renderer/columns/eta-column.tsx
|
|
944
|
+
const EtaCell = ({ task }) => {
|
|
945
|
+
const eta = formatEta(task);
|
|
920
946
|
if (eta === "") return null;
|
|
921
947
|
return /* @__PURE__ */ jsx(Text, {
|
|
922
948
|
wrap: "truncate-end",
|
|
@@ -1006,10 +1032,7 @@ const eta = () => ({
|
|
|
1006
1032
|
align: "right",
|
|
1007
1033
|
flexShrink: 0,
|
|
1008
1034
|
minWidth: 8,
|
|
1009
|
-
render: ({ task }
|
|
1010
|
-
task,
|
|
1011
|
-
now: ctx.now
|
|
1012
|
-
})
|
|
1035
|
+
render: ({ task }) => /* @__PURE__ */ jsx(EtaCell, { task })
|
|
1013
1036
|
});
|
|
1014
1037
|
const defaults = () => [
|
|
1015
1038
|
description(),
|
|
@@ -1023,7 +1046,7 @@ const resolveColumnSizeValue = (value, prepared) => {
|
|
|
1023
1046
|
};
|
|
1024
1047
|
|
|
1025
1048
|
//#endregion
|
|
1026
|
-
//#region src/renderer/column-resolver.ts
|
|
1049
|
+
//#region src/services/renderer/column-resolver.ts
|
|
1027
1050
|
const NO_PREPARE = Symbol("no-prepare");
|
|
1028
1051
|
const getColumnsForRow = (row, columns) => columns.get(row.task.id) ?? defaults();
|
|
1029
1052
|
const toCellInfo = (row) => row;
|
|
@@ -1077,13 +1100,13 @@ const resolveColumns = (rows, columns) => {
|
|
|
1077
1100
|
};
|
|
1078
1101
|
|
|
1079
1102
|
//#endregion
|
|
1080
|
-
//#region src/renderer/public-api.tsx
|
|
1103
|
+
//#region src/services/renderer/public-api.tsx
|
|
1081
1104
|
const justifyContentForAlign = (align) => {
|
|
1082
1105
|
if (align === "right") return "flex-end";
|
|
1083
1106
|
if (align === "center") return "center";
|
|
1084
1107
|
return "flex-start";
|
|
1085
1108
|
};
|
|
1086
|
-
const
|
|
1109
|
+
const RenderedNode = ({ node }) => {
|
|
1087
1110
|
if (typeof node === "string" || typeof node === "number") return /* @__PURE__ */ jsx(Text, {
|
|
1088
1111
|
wrap: "truncate-end",
|
|
1089
1112
|
children: node
|
|
@@ -1107,7 +1130,7 @@ const ColumnPosition = ({ position }) => {
|
|
|
1107
1130
|
const column = entry?.column;
|
|
1108
1131
|
const cell = row;
|
|
1109
1132
|
const output = column?.render(cell, {
|
|
1110
|
-
width: hasMeasured ? width :
|
|
1133
|
+
width: hasMeasured ? width : position.flexBasis,
|
|
1111
1134
|
now,
|
|
1112
1135
|
spinnerTick,
|
|
1113
1136
|
prepared: entry?.prepared
|
|
@@ -1115,7 +1138,7 @@ const ColumnPosition = ({ position }) => {
|
|
|
1115
1138
|
return /* @__PURE__ */ jsx(Box, {
|
|
1116
1139
|
height: 1,
|
|
1117
1140
|
justifyContent: justifyContentForAlign(column?.align),
|
|
1118
|
-
children:
|
|
1141
|
+
children: /* @__PURE__ */ jsx(RenderedNode, { node: output })
|
|
1119
1142
|
}, row.task.id);
|
|
1120
1143
|
})
|
|
1121
1144
|
});
|
|
@@ -1131,7 +1154,7 @@ const ProgressRenderer = ({ rows, columns }) => {
|
|
|
1131
1154
|
};
|
|
1132
1155
|
|
|
1133
1156
|
//#endregion
|
|
1134
|
-
//#region src/
|
|
1157
|
+
//#region src/services/store/render-snapshot.ts
|
|
1135
1158
|
const orderedVisibleTasks = (store) => store.renderOrder.flatMap((row) => {
|
|
1136
1159
|
const snapshot = store.tasks.get(row.id);
|
|
1137
1160
|
if (!snapshot || snapshot.transient && snapshot.status !== "running") return [];
|
|
@@ -1210,7 +1233,7 @@ const toRenderSnapshot = (store, previousSnapshot) => {
|
|
|
1210
1233
|
};
|
|
1211
1234
|
|
|
1212
1235
|
//#endregion
|
|
1213
|
-
//#region src/renderer/
|
|
1236
|
+
//#region src/services/renderer/hooks/use-progress-render-view.ts
|
|
1214
1237
|
const useRenderSnapshot = (storeSnapshot) => {
|
|
1215
1238
|
const previousSnapshotRef = useRef(void 0);
|
|
1216
1239
|
const renderSnapshot = useMemo(() => toRenderSnapshot(storeSnapshot, previousSnapshotRef.current), [storeSnapshot]);
|
|
@@ -1230,7 +1253,17 @@ const useProgressRenderView = (store) => {
|
|
|
1230
1253
|
};
|
|
1231
1254
|
|
|
1232
1255
|
//#endregion
|
|
1233
|
-
//#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
|
|
1234
1267
|
const MAX_FPS = 24;
|
|
1235
1268
|
const ProgressRoot = ({ store }) => {
|
|
1236
1269
|
const { renderSnapshot, hasRunningTasks, publication } = useProgressRenderView(store);
|
|
@@ -1245,57 +1278,43 @@ const ProgressRoot = ({ store }) => {
|
|
|
1245
1278
|
})
|
|
1246
1279
|
});
|
|
1247
1280
|
};
|
|
1248
|
-
const makeRendererv2InkRendererService = ()
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
})
|
|
1265
|
-
} };
|
|
1266
|
-
};
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
//#region src/services/ink-renderer.tsx
|
|
1270
|
-
var InkRenderer = class InkRenderer extends Context.Tag("stromseng.dev/effective-progress/InkRenderer")() {
|
|
1271
|
-
static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeRendererv2InkRendererService()));
|
|
1272
|
-
};
|
|
1273
|
-
|
|
1274
|
-
//#endregion
|
|
1275
|
-
//#region src/services/stdio.ts
|
|
1276
|
-
const defaultStdioService = {
|
|
1277
|
-
stdout: process.stdout,
|
|
1278
|
-
stderr: process.stderr
|
|
1279
|
-
};
|
|
1280
|
-
var ProgressStdio = class ProgressStdio extends Context.Tag("stromseng.dev/effective-progress/ProgressStdio")() {
|
|
1281
|
-
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);
|
|
1282
1302
|
};
|
|
1283
1303
|
|
|
1284
1304
|
//#endregion
|
|
1285
1305
|
//#region src/services/progress.ts
|
|
1286
1306
|
/** Builds the scoped implementation used by `ProgressService.task(...)` without auto-providing services. */
|
|
1287
1307
|
const makeProgressService = Effect.gen(function* () {
|
|
1288
|
-
const
|
|
1289
|
-
const
|
|
1290
|
-
const outerConsole = yield* Effect.console;
|
|
1291
|
-
const store = makeProgressRenderStore();
|
|
1292
|
-
const currentParentRef = yield* FiberRef.make(Option.none());
|
|
1308
|
+
const inkRenderer = yield* Renderer;
|
|
1309
|
+
const store = yield* ProgressStore;
|
|
1293
1310
|
const scope = yield* Effect.scope;
|
|
1294
|
-
const
|
|
1295
|
-
|
|
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 });
|
|
1296
1315
|
yield* Effect.sleep("0 millis");
|
|
1297
1316
|
const addTask = (options) => Effect.gen(function* () {
|
|
1298
|
-
const resolvedParentId = options.parentId === void 0 ? yield*
|
|
1317
|
+
const resolvedParentId = options.parentId === void 0 ? yield* currentParentId : Option.some(options.parentId);
|
|
1299
1318
|
return yield* store.addTask({
|
|
1300
1319
|
...options,
|
|
1301
1320
|
parentId: Option.isSome(resolvedParentId) ? resolvedParentId.value : void 0
|
|
@@ -1329,14 +1348,17 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
1329
1348
|
else yield* failTask(taskId);
|
|
1330
1349
|
});
|
|
1331
1350
|
const scopedTask = dual(2, (effect, options) => Effect.gen(function* () {
|
|
1332
|
-
const inheritedParentId = yield*
|
|
1351
|
+
const inheritedParentId = yield* currentParentId;
|
|
1333
1352
|
const resolvedParentId = options.parentId === void 0 ? inheritedParentId : Option.some(options.parentId);
|
|
1334
1353
|
const taskId = yield* addTask({
|
|
1335
1354
|
...options,
|
|
1336
1355
|
parentId: Option.isSome(resolvedParentId) ? resolvedParentId.value : void 0,
|
|
1337
1356
|
transient: options.transient
|
|
1338
1357
|
});
|
|
1339
|
-
return yield* Effect.
|
|
1358
|
+
return yield* Effect.provideService(Effect.provideService(effect, Task, taskId), CurrentParent, Option.some({
|
|
1359
|
+
owner: parentOwner,
|
|
1360
|
+
taskId
|
|
1361
|
+
}));
|
|
1340
1362
|
}));
|
|
1341
1363
|
const service = {
|
|
1342
1364
|
addTask,
|
|
@@ -1374,15 +1396,14 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
1374
1396
|
};
|
|
1375
1397
|
return Progress.of(service);
|
|
1376
1398
|
});
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
}));
|
|
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))));
|
|
1386
1407
|
};
|
|
1387
1408
|
|
|
1388
1409
|
//#endregion
|
|
@@ -1400,7 +1421,7 @@ const inferTotal = (iterable) => {
|
|
|
1400
1421
|
const provideProgress = (effect) => Effect.gen(function* () {
|
|
1401
1422
|
const existing = yield* Effect.serviceOption(Progress);
|
|
1402
1423
|
if (Option.isSome(existing)) return yield* Effect.provideService(effect, Progress, existing.value);
|
|
1403
|
-
return yield* Effect.scoped(
|
|
1424
|
+
return yield* Effect.scoped(Effect.provide(effect, Progress.layer, { local: true }));
|
|
1404
1425
|
});
|
|
1405
1426
|
/**
|
|
1406
1427
|
* Runs an effect inside a task, creating and providing a `Progress` service automatically when one
|
|
@@ -1417,7 +1438,7 @@ const task = dual(2, (effectOrCallback, options) => {
|
|
|
1417
1438
|
});
|
|
1418
1439
|
const wrapEffects = (effects, tap) => Array.isArray(effects) ? effects.map(tap) : Object.fromEntries(Object.entries(effects).map(([k, effect]) => [k, tap(effect)]));
|
|
1419
1440
|
const countEffects = (effects) => Array.isArray(effects) ? effects.length : Object.keys(effects).length;
|
|
1420
|
-
const isCollectAllMode = (mode) => mode === "
|
|
1441
|
+
const isCollectAllMode = (mode) => mode === "result";
|
|
1421
1442
|
const allCountDisplay = (mode) => isCollectAllMode(mode) ? "detailed" : "processedOnly";
|
|
1422
1443
|
const wrapTrackedEffect = (progress, taskId, effect) => Effect.gen(function* () {
|
|
1423
1444
|
const exit = yield* Effect.exit(effect);
|
|
@@ -1425,7 +1446,7 @@ const wrapTrackedEffect = (progress, taskId, effect) => Effect.gen(function* ()
|
|
|
1425
1446
|
yield* progress.incrementSucceeded(taskId, 1);
|
|
1426
1447
|
return exit.value;
|
|
1427
1448
|
}
|
|
1428
|
-
if (Cause.
|
|
1449
|
+
if (Cause.hasInterruptsOnly(exit.cause)) return yield* Effect.failCause(exit.cause);
|
|
1429
1450
|
yield* progress.incrementFailed(taskId, 1);
|
|
1430
1451
|
return yield* Effect.failCause(exit.cause);
|
|
1431
1452
|
});
|
|
@@ -1445,10 +1466,8 @@ const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* (
|
|
|
1445
1466
|
const taskId = handle.id;
|
|
1446
1467
|
const exit = yield* Effect.exit(Effect.all(wrapEffects(effects, (effect) => wrapTrackedEffect(progress, taskId, effect)), {
|
|
1447
1468
|
concurrency: options.concurrency,
|
|
1448
|
-
batching: options.batching,
|
|
1449
1469
|
discard: options.discard,
|
|
1450
|
-
mode: options.mode
|
|
1451
|
-
concurrentFinalizers: options.concurrentFinalizers
|
|
1470
|
+
mode: options.mode
|
|
1452
1471
|
}));
|
|
1453
1472
|
if (Exit.isSuccess(exit)) yield* progress.completeTask(taskId);
|
|
1454
1473
|
else if (!isCollectAllMode(options.mode)) yield* progress.failTask(taskId);
|
|
@@ -1474,9 +1493,7 @@ const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(fun
|
|
|
1474
1493
|
const taskId = handle.id;
|
|
1475
1494
|
const exit = yield* Effect.exit(Effect.forEach(iterable, (item, index) => wrapTrackedEffect(progress, taskId, f(item, index)), {
|
|
1476
1495
|
concurrency: options.concurrency,
|
|
1477
|
-
|
|
1478
|
-
discard: options.discard,
|
|
1479
|
-
concurrentFinalizers: options.concurrentFinalizers
|
|
1496
|
+
discard: options.discard
|
|
1480
1497
|
}));
|
|
1481
1498
|
if (Exit.isSuccess(exit)) yield* progress.completeTask(taskId);
|
|
1482
1499
|
else yield* progress.failTask(taskId);
|