feat: submitStreaming + detached-invocation client (T2/T3)
Some checks failed
Security / Security check (push) Failing after 2s

Regenerates the gRPC stubs from the updated proto and adds:
- HubClient.submitStreaming(...) -> Stream<SubmitStreamEvent>: follow a
  flow execution live (step markers, module emit-events, terminal
  result/error). Works over native gRPC and, on web, gRPC-Web (the
  same conditional channel factory the rest of the SDK uses).
- submit(..., detach: true) and getInvocationStatus /
  getInvocationResult / cancelInvocation wrappers (T3).
- SubmitStreamEvent + InvocationStatus re-exported as typedefs.

Shared _buildSubmitRequest between submit + submitStreaming. analyze
clean, tests pass.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-07-06 01:56:43 +02:00
parent 0d2e939867
commit c46dc58ac3
5 changed files with 1038 additions and 5 deletions

View file

@ -86,6 +86,11 @@ typedef StoreSearchResponse = pb.StoreSearchResponse;
typedef InstallModuleResponse = pb.InstallModuleResponse;
typedef Payload = pb_common.Payload;
typedef SubmitResponse = pb.SubmitResponse;
// Live-stream (T2) + detached-invocation (T3) wire types, re-exported
// so consumers of [HubClient.submitStreaming] don't import the
// generated package directly.
typedef SubmitStreamEvent = pb.SubmitStreamEvent;
typedef InvocationStatus = pb.InvocationStatus;
/// Typed client for the FI Hub gRPC surface. Construct once,
/// reuse for the process lifetime, call [close] on shutdown.
@ -779,8 +784,80 @@ class HubClient {
Map<String, Uint8List> fileInputs = const {},
Map<String, Object?> jsonInputs = const {},
Map<String, String> fileMimeTypes = const {},
bool detach = false,
}) async {
final req = pb.SubmitRequest()..flowYaml = utf8.encode(flowYaml);
return _hub.submit(_buildSubmitRequest(
flowYaml: flowYaml,
textInputs: textInputs,
fileInputs: fileInputs,
jsonInputs: jsonInputs,
fileMimeTypes: fileMimeTypes,
detach: detach,
));
}
/// Submit a flow and follow its execution live (server streaming).
///
/// Yields [pb.SubmitStreamEvent]s in order: `stepStarted` /
/// `stepFinished` markers, `moduleEvent`s (a module's ephemeral
/// `host.emit-event`s), and finally either `finalResult` (== the
/// unary [submit] response) or `error`. Works over native gRPC and,
/// on web targets, gRPC-Web. Same inputs as [submit].
///
/// ```dart
/// await for (final ev in hub.submitStreaming(flowYaml: yaml)) {
/// if (ev.hasModuleEvent()) print('${ev.moduleEvent.name}');
/// if (ev.hasFinalResult()) print(ev.finalResult.outputs);
/// }
/// ```
Stream<SubmitStreamEvent> submitStreaming({
required String flowYaml,
Map<String, String> textInputs = const {},
Map<String, Uint8List> fileInputs = const {},
Map<String, Object?> jsonInputs = const {},
Map<String, String> fileMimeTypes = const {},
}) {
return _hub.submitStream(_buildSubmitRequest(
flowYaml: flowYaml,
textInputs: textInputs,
fileInputs: fileInputs,
jsonInputs: jsonInputs,
fileMimeTypes: fileMimeTypes,
));
}
/// Status of a detached invocation (T3). Throws a [GrpcError] with
/// code `notFound` for an unknown id.
Future<InvocationStatus> getInvocationStatus(String invocationId) {
return _hub.getInvocationStatus(pb_common.InvocationId()..value = invocationId);
}
/// Retained result of a succeeded detached invocation (T3). Throws
/// `failedPrecondition` if it is still running / failed, `notFound`
/// if unknown or already evicted.
Future<SubmitResponse> getInvocationResult(String invocationId) {
return _hub.getInvocationResult(pb_common.InvocationId()..value = invocationId);
}
/// Cancel a running detached invocation (T3). Returns true if it was
/// running and got signalled, false if already finished or unknown.
Future<bool> cancelInvocation(String invocationId) async {
final r =
await _hub.cancelInvocation(pb_common.InvocationId()..value = invocationId);
return r.cancelled;
}
pb.SubmitRequest _buildSubmitRequest({
required String flowYaml,
required Map<String, String> textInputs,
required Map<String, Uint8List> fileInputs,
required Map<String, Object?> jsonInputs,
required Map<String, String> fileMimeTypes,
bool detach = false,
}) {
final req = pb.SubmitRequest()
..flowYaml = utf8.encode(flowYaml)
..detach = detach;
for (final entry in textInputs.entries) {
req.inputs[entry.key] = pb_common.Payload()..text = entry.value;
}
@ -801,7 +878,7 @@ class HubClient {
}
req.inputs[entry.key] = pb_common.Payload()..json = _structFromMap(value);
}
return _hub.submit(req);
return req;
}
/// Closes the gRPC channel. Idempotent.