feat: installModuleStreaming for live install progress

Wraps the new HubAdmin/InstallModuleStream: yields one update per
phase and, during the download, as bytes arrive; completes on success
and surfaces the hub's failure text as a stream error, so callers
handle failures exactly as with the unary installModule. Generated
stubs regenerated from the protos (additive only).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-09-08 13:38:35 +02:00
parent 5759ce4c7a
commit 1d3d33d55a
4 changed files with 381 additions and 0 deletions

View file

@ -110,6 +110,7 @@ typedef ProjectInfo = pb.ProjectInfo;
typedef StoreEntry = pb.StoreEntry;
typedef StoreSearchResponse = pb.StoreSearchResponse;
typedef InstallModuleResponse = pb.InstallModuleResponse;
typedef InstallProgressUpdate = pb.InstallProgressUpdate;
typedef Payload = pb_common.Payload;
typedef SubmitResponse = pb.SubmitResponse;
// Live-stream (T2) + detached-invocation (T3) wire types, re-exported
@ -831,6 +832,41 @@ class HubClient {
));
}
/// Install a module while watching it happen.
///
/// Emits an update per phase and, during the download, as bytes
/// arrive. The stream completes when the install succeeds, and
/// carries the hub's failure text as a stream error when it does
/// not, so callers handle failures the same way as with
/// [installModule]. Use [onFinished] to receive the installed
/// module's name and version.
///
/// A caller that only needs the outcome should keep using the
/// unary [installModule]; this exists so a waiting operator can
/// see that a slow download is progressing rather than hung.
Stream<InstallProgressUpdate> installModuleStreaming({
required String source,
String expectedSha256 = '',
String version = '',
void Function(InstallModuleResponse result)? onFinished,
}) async* {
final stream = _admin.installModuleStream(pb.InstallModuleRequest(
source: source,
expectedSha256: expectedSha256,
version: version,
));
await for (final event in stream) {
if (event.hasUpdate()) {
yield event.update;
} else if (event.hasFinished()) {
onFinished?.call(event.finished);
return;
} else if (event.hasFailed()) {
throw Exception(event.failed);
}
}
}
/// All saved flows known to the hub. Each entry carries the
/// flow name, on-disk path and byte size.
Future<List<FlowSummary>> listFlows() async {