From 1e98ab9ccf976e8340859802bfd3a4dea244e47d Mon Sep 17 00:00:00 2001 From: flemming-it Date: Sat, 9 May 2026 10:12:59 +0200 Subject: [PATCH] feat: getFlowDefinition wrapper + FlowInputDef DTO (v0.15.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surface the new platform RPC `GetFlowDefinition` as a typed Dart method so Studio (and other GUI clients) can render an input form before calling `runSavedFlow`. Each declared input arrives as a `FlowInputDef` with the verbatim type tag from the flow YAML — usually `text`, `bytes`, `json`, or `file`. Regenerated proto bindings to pick up the new `GetFlowDefinitionRequest`, `FlowDefinition`, and `FlowInputSpec` message types. Signed-off-by: flemming-it --- lib/src/generated/fai/v1/hub.pb.dart | 164 +++++++++++++++++++++++ lib/src/generated/fai/v1/hub.pbgrpc.dart | 23 ++++ lib/src/generated/fai/v1/hub.pbjson.dart | 40 ++++++ lib/src/hub_client.dart | 27 ++++ pubspec.yaml | 2 +- 5 files changed, 255 insertions(+), 1 deletion(-) diff --git a/lib/src/generated/fai/v1/hub.pb.dart b/lib/src/generated/fai/v1/hub.pb.dart index 8a4187a..d20fef6 100644 --- a/lib/src/generated/fai/v1/hub.pb.dart +++ b/lib/src/generated/fai/v1/hub.pb.dart @@ -1351,6 +1351,170 @@ class RunSavedFlowRequest extends $pb.GeneratedMessage { $pb.PbMap<$core.String, $4.Payload> get inputs => $_getMap(1); } +class GetFlowDefinitionRequest extends $pb.GeneratedMessage { + factory GetFlowDefinitionRequest({ + $core.String? name, + }) { + final $result = create(); + if (name != null) { + $result.name = name; + } + return $result; + } + GetFlowDefinitionRequest._() : super(); + factory GetFlowDefinitionRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory GetFlowDefinitionRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'GetFlowDefinitionRequest', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'name') + ..hasRequiredFields = false + ; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetFlowDefinitionRequest clone() => GetFlowDefinitionRequest()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetFlowDefinitionRequest copyWith(void Function(GetFlowDefinitionRequest) updates) => super.copyWith((message) => updates(message as GetFlowDefinitionRequest)) as GetFlowDefinitionRequest; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetFlowDefinitionRequest create() => GetFlowDefinitionRequest._(); + GetFlowDefinitionRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static GetFlowDefinitionRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static GetFlowDefinitionRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get name => $_getSZ(0); + @$pb.TagNumber(1) + set name($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasName() => $_has(0); + @$pb.TagNumber(1) + void clearName() => $_clearField(1); +} + +/// Parsed shape of a saved flow. Currently surfaces only the +/// declared inputs — that is what Studio needs to render its +/// run-flow form. Fields can grow (steps, outputs) without +/// breaking older clients. +class FlowDefinition extends $pb.GeneratedMessage { + factory FlowDefinition({ + $core.String? name, + $core.Iterable? inputs, + }) { + final $result = create(); + if (name != null) { + $result.name = name; + } + if (inputs != null) { + $result.inputs.addAll(inputs); + } + return $result; + } + FlowDefinition._() : super(); + factory FlowDefinition.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory FlowDefinition.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'FlowDefinition', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'name') + ..pc(2, _omitFieldNames ? '' : 'inputs', $pb.PbFieldType.PM, subBuilder: FlowInputSpec.create) + ..hasRequiredFields = false + ; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + FlowDefinition clone() => FlowDefinition()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + FlowDefinition copyWith(void Function(FlowDefinition) updates) => super.copyWith((message) => updates(message as FlowDefinition)) as FlowDefinition; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static FlowDefinition create() => FlowDefinition._(); + FlowDefinition createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static FlowDefinition getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static FlowDefinition? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get name => $_getSZ(0); + @$pb.TagNumber(1) + set name($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasName() => $_has(0); + @$pb.TagNumber(1) + void clearName() => $_clearField(1); + + /// Declared input keys plus their declared type tag, exactly + /// as the flow YAML's `inputs:` section spelled them. Common + /// values: "text", "bytes", "json", "file". + @$pb.TagNumber(2) + $pb.PbList get inputs => $_getList(1); +} + +class FlowInputSpec extends $pb.GeneratedMessage { + factory FlowInputSpec({ + $core.String? name, + $core.String? type, + }) { + final $result = create(); + if (name != null) { + $result.name = name; + } + if (type != null) { + $result.type = type; + } + return $result; + } + FlowInputSpec._() : super(); + factory FlowInputSpec.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory FlowInputSpec.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'FlowInputSpec', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'name') + ..aOS(2, _omitFieldNames ? '' : 'type') + ..hasRequiredFields = false + ; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + FlowInputSpec clone() => FlowInputSpec()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + FlowInputSpec copyWith(void Function(FlowInputSpec) updates) => super.copyWith((message) => updates(message as FlowInputSpec)) as FlowInputSpec; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static FlowInputSpec create() => FlowInputSpec._(); + FlowInputSpec createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static FlowInputSpec getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static FlowInputSpec? _defaultInstance; + + /// Input key (the name the flow YAML uses). + @$pb.TagNumber(1) + $core.String get name => $_getSZ(0); + @$pb.TagNumber(1) + set name($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasName() => $_has(0); + @$pb.TagNumber(1) + void clearName() => $_clearField(1); + + /// Declared type tag from the flow YAML — opaque string so + /// the wire stays stable as the type system grows. + @$pb.TagNumber(2) + $core.String get type => $_getSZ(1); + @$pb.TagNumber(2) + set type($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasType() => $_has(1); + @$pb.TagNumber(2) + void clearType() => $_clearField(2); +} + class SearchStoreRequest extends $pb.GeneratedMessage { factory SearchStoreRequest({ $core.String? query, diff --git a/lib/src/generated/fai/v1/hub.pbgrpc.dart b/lib/src/generated/fai/v1/hub.pbgrpc.dart index 46014ce..b643be5 100644 --- a/lib/src/generated/fai/v1/hub.pbgrpc.dart +++ b/lib/src/generated/fai/v1/hub.pbgrpc.dart @@ -155,6 +155,10 @@ class HubAdminClient extends $grpc.Client { '/fai.v1.HubAdmin/RunSavedFlow', ($0.RunSavedFlowRequest value) => value.writeToBuffer(), ($core.List<$core.int> value) => $0.SubmitResponse.fromBuffer(value)); + static final _$getFlowDefinition = $grpc.ClientMethod<$0.GetFlowDefinitionRequest, $0.FlowDefinition>( + '/fai.v1.HubAdmin/GetFlowDefinition', + ($0.GetFlowDefinitionRequest value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.FlowDefinition.fromBuffer(value)); static final _$searchStore = $grpc.ClientMethod<$0.SearchStoreRequest, $0.StoreSearchResponse>( '/fai.v1.HubAdmin/SearchStore', ($0.SearchStoreRequest value) => value.writeToBuffer(), @@ -317,6 +321,13 @@ class HubAdminClient extends $grpc.Client { return $createUnaryCall(_$runSavedFlow, request, options: options); } + /// Return the parsed structure of a saved flow (declared + /// inputs + their type tags) so a client can render an + /// input form before calling RunSavedFlow. + $grpc.ResponseFuture<$0.FlowDefinition> getFlowDefinition($0.GetFlowDefinitionRequest request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$getFlowDefinition, request, options: options); + } + /// Search the bundled (and Phase 1+: federated) store index for /// modules matching a query. $grpc.ResponseFuture<$0.StoreSearchResponse> searchStore($0.SearchStoreRequest request, {$grpc.CallOptions? options}) { @@ -593,6 +604,13 @@ abstract class HubAdminServiceBase extends $grpc.Service { false, ($core.List<$core.int> value) => $0.RunSavedFlowRequest.fromBuffer(value), ($0.SubmitResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.GetFlowDefinitionRequest, $0.FlowDefinition>( + 'GetFlowDefinition', + getFlowDefinition_Pre, + false, + false, + ($core.List<$core.int> value) => $0.GetFlowDefinitionRequest.fromBuffer(value), + ($0.FlowDefinition value) => value.writeToBuffer())); $addMethod($grpc.ServiceMethod<$0.SearchStoreRequest, $0.StoreSearchResponse>( 'SearchStore', searchStore_Pre, @@ -837,6 +855,10 @@ abstract class HubAdminServiceBase extends $grpc.Service { return runSavedFlow($call, await $request); } + $async.Future<$0.FlowDefinition> getFlowDefinition_Pre($grpc.ServiceCall $call, $async.Future<$0.GetFlowDefinitionRequest> $request) async { + return getFlowDefinition($call, await $request); + } + $async.Future<$0.StoreSearchResponse> searchStore_Pre($grpc.ServiceCall $call, $async.Future<$0.SearchStoreRequest> $request) async { return searchStore($call, await $request); } @@ -965,6 +987,7 @@ abstract class HubAdminServiceBase extends $grpc.Service { $async.Future<$0.DeleteFlowResponse> deleteFlow($grpc.ServiceCall call, $0.DeleteFlowRequest request); $async.Future<$0.FlowList> listFlows($grpc.ServiceCall call, $1.Empty request); $async.Future<$0.SubmitResponse> runSavedFlow($grpc.ServiceCall call, $0.RunSavedFlowRequest request); + $async.Future<$0.FlowDefinition> getFlowDefinition($grpc.ServiceCall call, $0.GetFlowDefinitionRequest request); $async.Future<$0.StoreSearchResponse> searchStore($grpc.ServiceCall call, $0.SearchStoreRequest request); $async.Future<$0.InstallModuleResponse> installModule($grpc.ServiceCall call, $0.InstallModuleRequest request); $async.Future<$0.UninstallModuleResponse> uninstallModule($grpc.ServiceCall call, $0.UninstallModuleRequest request); diff --git a/lib/src/generated/fai/v1/hub.pbjson.dart b/lib/src/generated/fai/v1/hub.pbjson.dart index aaf94aa..d557faf 100644 --- a/lib/src/generated/fai/v1/hub.pbjson.dart +++ b/lib/src/generated/fai/v1/hub.pbjson.dart @@ -374,6 +374,46 @@ final $typed_data.Uint8List runSavedFlowRequestDescriptor = $convert.base64Decod 'bnB1dHNFbnRyeRIQCgNrZXkYASABKAlSA2tleRIlCgV2YWx1ZRgCIAEoCzIPLmZhaS52MS5QYX' 'lsb2FkUgV2YWx1ZToCOAE='); +@$core.Deprecated('Use getFlowDefinitionRequestDescriptor instead') +const GetFlowDefinitionRequest$json = { + '1': 'GetFlowDefinitionRequest', + '2': [ + {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, + ], +}; + +/// Descriptor for `GetFlowDefinitionRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getFlowDefinitionRequestDescriptor = $convert.base64Decode( + 'ChhHZXRGbG93RGVmaW5pdGlvblJlcXVlc3QSEgoEbmFtZRgBIAEoCVIEbmFtZQ=='); + +@$core.Deprecated('Use flowDefinitionDescriptor instead') +const FlowDefinition$json = { + '1': 'FlowDefinition', + '2': [ + {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, + {'1': 'inputs', '3': 2, '4': 3, '5': 11, '6': '.fai.v1.FlowInputSpec', '10': 'inputs'}, + ], +}; + +/// Descriptor for `FlowDefinition`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List flowDefinitionDescriptor = $convert.base64Decode( + 'Cg5GbG93RGVmaW5pdGlvbhISCgRuYW1lGAEgASgJUgRuYW1lEi0KBmlucHV0cxgCIAMoCzIVLm' + 'ZhaS52MS5GbG93SW5wdXRTcGVjUgZpbnB1dHM='); + +@$core.Deprecated('Use flowInputSpecDescriptor instead') +const FlowInputSpec$json = { + '1': 'FlowInputSpec', + '2': [ + {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, + {'1': 'type', '3': 2, '4': 1, '5': 9, '10': 'type'}, + ], +}; + +/// Descriptor for `FlowInputSpec`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List flowInputSpecDescriptor = $convert.base64Decode( + 'Cg1GbG93SW5wdXRTcGVjEhIKBG5hbWUYASABKAlSBG5hbWUSEgoEdHlwZRgCIAEoCVIEdHlwZQ' + '=='); + @$core.Deprecated('Use searchStoreRequestDescriptor instead') const SearchStoreRequest$json = { '1': 'SearchStoreRequest', diff --git a/lib/src/hub_client.dart b/lib/src/hub_client.dart index c1fb367..feddd92 100644 --- a/lib/src/hub_client.dart +++ b/lib/src/hub_client.dart @@ -466,6 +466,19 @@ class HubClient { return r.flows; } + /// Return the parsed input schema of a saved flow so a GUI + /// client can render an input form before calling + /// [runSavedFlow]. Each entry's `type` is the verbatim type + /// tag from the flow YAML (`text`, `bytes`, `json`, `file`, + /// or anything else the flow author put there). + Future> getFlowDefinition(String name) async { + final req = pb.GetFlowDefinitionRequest()..name = name; + final resp = await _admin.getFlowDefinition(req); + return resp.inputs + .map((spec) => FlowInputDef(name: spec.name, type: spec.type)) + .toList(); + } + /// Run a saved flow by name with the supplied named inputs. /// Two input shapes are supported in the same call so a /// flow that mixes text and binary inputs (e.g. extract @@ -503,3 +516,17 @@ class HubClient { await _channel.shutdown(); } } + +/// One declared input in a saved flow's `inputs:` section. +/// Returned by [HubClient.getFlowDefinition] so GUI clients +/// can build a typed run-flow form. The [type] field carries +/// the flow YAML's verbatim type tag — opaque to the SDK so +/// the type system can grow without an SDK release. +class FlowInputDef { + /// Input key (the name the flow YAML uses). + final String name; + /// Type tag — typically `text`, `bytes`, `json`, or `file`. + final String type; + + const FlowInputDef({required this.name, required this.type}); +} diff --git a/pubspec.yaml b/pubspec.yaml index 021eb88..8b23a19 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: fai_dart_sdk description: gRPC client SDK for the F∆I Platform hub. Used by F∆I Studio and Tier-3 domain apps. -version: 0.14.0 +version: 0.15.0 publish_to: 'none' repository: https://git.flemming.ws/fai/dart-sdk