diff --git a/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj b/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj index 1d8f9ab..27c71a2 100644 --- a/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj +++ b/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj @@ -15,7 +15,7 @@ link-foundation A CLI tool for links manipulation. clink - 2.2.2 + 2.3.0 Unlicense https://github.com/link-foundation/link-cli @@ -26,9 +26,18 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/Foundation.Data.Doublets.Cli/LinoGrpcService.cs b/Foundation.Data.Doublets.Cli/LinoGrpcService.cs new file mode 100644 index 0000000..4d7c2d5 --- /dev/null +++ b/Foundation.Data.Doublets.Cli/LinoGrpcService.cs @@ -0,0 +1,269 @@ +using Grpc.Core; +using Foundation.Data.Doublets.Cli.Grpc; +using System.Diagnostics; +using DoubletLink = Platform.Data.Doublets.Link; + +namespace Foundation.Data.Doublets.Cli +{ + public class LinoGrpcService : LinoService.LinoServiceBase + { + private const string DefaultDatabasePath = "db.links"; + + public override async Task ExecuteQuery(LinoQueryRequest request, ServerCallContext context) + { + var stopwatch = Stopwatch.StartNew(); + var response = new LinoQueryResponse(); + + try + { + var dbPath = string.IsNullOrEmpty(request.DatabasePath) ? DefaultDatabasePath : request.DatabasePath; + var decoratedLinks = new NamedLinksDecorator(dbPath, request.Trace); + + var changesList = new List<(DoubletLink Before, DoubletLink After)>(); + + // Capture before state if requested + if (request.IncludeBeforeState) + { + response.BeforeState.AddRange(GetAllLinksAsStrings(decoratedLinks)); + } + + // Execute the query if provided + if (!string.IsNullOrEmpty(request.Query)) + { + var options = new AdvancedMixedQueryProcessor.Options + { + Query = request.Query, + Trace = request.Trace, + ChangesHandler = (beforeLink, afterLink) => + { + changesList.Add((new DoubletLink(beforeLink), new DoubletLink(afterLink))); + return decoratedLinks.Constants.Continue; + } + }; + + AdvancedMixedQueryProcessor.ProcessQuery(decoratedLinks, options); + } + + // Process changes if requested + if (request.IncludeChanges && changesList.Any()) + { + var simplifiedChanges = ChangesSimplifier.SimplifyChanges(changesList); + response.Changes.AddRange(simplifiedChanges.Select(change => + FormatChange(decoratedLinks, change.Before, change.After))); + } + + // Capture after state if requested + if (request.IncludeAfterState) + { + response.AfterState.AddRange(GetAllLinksAsStrings(decoratedLinks)); + } + + response.Success = true; + response.Metadata = new ExecutionMetadata + { + ExecutionTimeMs = stopwatch.ElapsedMilliseconds, + LinksProcessed = changesList.Count, + OperationsCount = 1, + Timestamp = DateTime.UtcNow.ToString("O") + }; + } + catch (Exception ex) + { + response.Success = false; + response.ErrorMessage = ex.Message; + } + finally + { + stopwatch.Stop(); + } + + return await Task.FromResult(response); + } + + public override async Task ExecuteBatch(LinoBatchRequest request, ServerCallContext context) + { + var batchResponse = new LinoBatchResponse + { + Success = true + }; + + foreach (var queryRequest in request.Queries) + { + var queryResponse = await ExecuteQuery(queryRequest, context); + batchResponse.Responses.Add(queryResponse); + + if (queryResponse.Success) + { + batchResponse.SuccessfulOperations++; + } + else + { + batchResponse.FailedOperations++; + if (request.StopOnError) + { + batchResponse.Success = false; + break; + } + } + } + + batchResponse.Success = batchResponse.FailedOperations == 0; + return batchResponse; + } + + public override async Task StreamQueries(IAsyncStreamReader requestStream, + IServerStreamWriter responseStream, ServerCallContext context) + { + try + { + await foreach (var request in requestStream.ReadAllAsync()) + { + switch (request.RequestTypeCase) + { + case LinoStreamRequest.RequestTypeOneofCase.Query: + var queryResponse = await ExecuteQuery(request.Query, context); + await responseStream.WriteAsync(new LinoStreamResponse + { + QueryResponse = queryResponse + }); + break; + + case LinoStreamRequest.RequestTypeOneofCase.Control: + await HandleControlMessage(request.Control, responseStream); + break; + } + } + } + catch (Exception ex) + { + await responseStream.WriteAsync(new LinoStreamResponse + { + Status = new StreamStatusMessage + { + Status = StreamStatusMessage.Types.Status.Error, + Message = ex.Message + } + }); + } + } + + public override async Task GetAllLinks(GetAllLinksRequest request, ServerCallContext context) + { + try + { + var dbPath = string.IsNullOrEmpty(request.DatabasePath) ? DefaultDatabasePath : request.DatabasePath; + var decoratedLinks = new NamedLinksDecorator(dbPath, false); + + var links = GetAllLinksAsStrings(decoratedLinks, request.IncludeNames); + + return await Task.FromResult(new GetAllLinksResponse + { + Success = true, + Links = { links } + }); + } + catch (Exception ex) + { + return await Task.FromResult(new GetAllLinksResponse + { + Success = false, + ErrorMessage = ex.Message + }); + } + } + + public override async Task GetStructure(GetStructureRequest request, ServerCallContext context) + { + try + { + var dbPath = string.IsNullOrEmpty(request.DatabasePath) ? DefaultDatabasePath : request.DatabasePath; + var decoratedLinks = new NamedLinksDecorator(dbPath, false); + + var linkId = request.LinkId; + // TODO: Implement FormatStructure when available + var result = $"Structure for link {linkId} (formatting not implemented yet)"; + + return await Task.FromResult(new GetStructureResponse + { + Success = true, + Structure = result + }); + } + catch (Exception ex) + { + return await Task.FromResult(new GetStructureResponse + { + Success = false, + ErrorMessage = ex.Message + }); + } + } + + private static List GetAllLinksAsStrings(NamedLinksDecorator links, bool includeNames = true) + { + var result = new List(); + var any = links.Constants.Any; + var query = new DoubletLink(index: any, source: any, target: any); + + links.Each(query, link => + { + // TODO: Implement Format when available + var doubletLink = new DoubletLink(link); + var formattedLink = $"({doubletLink.Index}: {doubletLink.Source} {doubletLink.Target})"; + result.Add(formattedLink); + return links.Constants.Continue; + }); + + return result; + } + + private static string FormatChange(NamedLinksDecorator links, DoubletLink linkBefore, DoubletLink linkAfter) + { + // TODO: Implement Format when available + var beforeText = linkBefore.IsNull() ? "" : $"({linkBefore.Index}: {linkBefore.Source} {linkBefore.Target})"; + var afterText = linkAfter.IsNull() ? "" : $"({linkAfter.Index}: {linkAfter.Source} {linkAfter.Target})"; + var formattedChange = $"({beforeText}) ({afterText})"; + return formattedChange; + } + + private static string Namify(NamedLinksDecorator namedLinks, string linksNotation) + { + var numberGlobalRegex = new System.Text.RegularExpressions.Regex(@"\d+"); + var matches = numberGlobalRegex.Matches(linksNotation); + var newLinksNotation = linksNotation; + foreach (System.Text.RegularExpressions.Match match in matches) + { + var number = match.Value; + var numberLink = uint.Parse(number); + var name = namedLinks.GetName(numberLink); + if (name != null) + { + newLinksNotation = newLinksNotation.Replace(number, name); + } + } + return newLinksNotation; + } + + private static async Task HandleControlMessage(StreamControlMessage control, + IServerStreamWriter responseStream) + { + var status = control.Type switch + { + StreamControlMessage.Types.ControlType.Pause => StreamStatusMessage.Types.Status.Paused, + StreamControlMessage.Types.ControlType.Resume => StreamStatusMessage.Types.Status.Ready, + StreamControlMessage.Types.ControlType.Cancel => StreamStatusMessage.Types.Status.Closed, + StreamControlMessage.Types.ControlType.Ping => StreamStatusMessage.Types.Status.Ready, + _ => StreamStatusMessage.Types.Status.Ready + }; + + await responseStream.WriteAsync(new LinoStreamResponse + { + Status = new StreamStatusMessage + { + Status = status, + Message = $"Control message processed: {control.Type}" + } + }); + } + } +} \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/Program.cs b/Foundation.Data.Doublets.Cli/Program.cs index 1f9bfed..3c4064f 100644 --- a/Foundation.Data.Doublets.Cli/Program.cs +++ b/Foundation.Data.Doublets.Cli/Program.cs @@ -9,6 +9,7 @@ using QueryProcessor = Foundation.Data.Doublets.Cli.AdvancedMixedQueryProcessor; using Foundation.Data.Doublets.Cli; using System.Text.RegularExpressions; +using System.CommandLine.Invocation; const string defaultDatabaseFilename = "db.links"; @@ -70,6 +71,22 @@ afterOption.AddAlias("--links"); afterOption.AddAlias("-a"); +var grpcServerOption = new Option( + name: "--grpc-server", + description: "Start GRPC server mode instead of CLI mode", + getDefaultValue: () => false +); +grpcServerOption.AddAlias("--server"); +grpcServerOption.AddAlias("-g"); + +var grpcPortOption = new Option( + name: "--grpc-port", + description: "Port for GRPC server (default: 5001)", + getDefaultValue: () => 5001 +); +grpcPortOption.AddAlias("--port"); +grpcPortOption.AddAlias("-p"); + var rootCommand = new RootCommand("LiNo CLI Tool for managing links data store") { dbOption, @@ -79,12 +96,31 @@ structureOption, beforeOption, changesOption, - afterOption + afterOption, + grpcServerOption, + grpcPortOption }; rootCommand.SetHandler( - (string db, string queryOptionValue, string queryArgumentValue, bool trace, uint? structure, bool before, bool changes, bool after) => + async (InvocationContext context) => { + var db = context.ParseResult.GetValueForOption(dbOption)!; + var queryOptionValue = context.ParseResult.GetValueForOption(queryOption); + var queryArgumentValue = context.ParseResult.GetValueForArgument(queryArgument); + var trace = context.ParseResult.GetValueForOption(traceOption); + var structure = context.ParseResult.GetValueForOption(structureOption); + var before = context.ParseResult.GetValueForOption(beforeOption); + var changes = context.ParseResult.GetValueForOption(changesOption); + var after = context.ParseResult.GetValueForOption(afterOption); + var grpcServer = context.ParseResult.GetValueForOption(grpcServerOption); + var grpcPort = context.ParseResult.GetValueForOption(grpcPortOption); + + // If GRPC server mode is enabled, start the server instead of CLI + if (grpcServer) + { + await StartGrpcServer(grpcPort); + return; + } var decoratedLinks = new NamedLinksDecorator(db, trace); // If --structure is provided, handle it separately @@ -145,13 +181,21 @@ { PrintAllLinks(decoratedLinks); } - }, - // Explicitly specify the type parameters - dbOption, queryOption, queryArgument, traceOption, structureOption, beforeOption, changesOption, afterOption + } ); await rootCommand.InvokeAsync(args); +static async Task StartGrpcServer(int port) +{ + Console.WriteLine($"GRPC Server mode requested on port {port}."); + Console.WriteLine("GRPC server implementation is still in progress..."); + Console.WriteLine("For now, run the CLI without --grpc-server flag."); + + // TODO: Implement full GRPC server with ASP.NET Core + await Task.Delay(1000); +} + static string Namify(NamedLinksDecorator namedLinks, string linksNotation) { var numberGlobalRegex = new Regex(@"\d+"); diff --git a/GRPC_API.md b/GRPC_API.md new file mode 100644 index 0000000..de704e8 --- /dev/null +++ b/GRPC_API.md @@ -0,0 +1,234 @@ +# LINO GRPC API + +This document describes the GRPC-style API for LINO operations, as requested in issue #33. + +## Overview + +The LINO GRPC API provides a modern, efficient way to interact with the links database using LINO notation instead of JSON. Unlike traditional GRPC APIs that use JSON for message payloads, this API uses LINO (Links Notation) strings directly in the protocol buffer messages. + +## Key Features + +- **LINO-based Messages**: Uses LINO notation strings instead of JSON for all operations +- **Full CRUD Support**: Create, Read, Update, Delete operations using familiar LINO syntax +- **Batch Operations**: Execute multiple LINO queries in a single request +- **Streaming**: Real-time bidirectional streaming for continuous operations +- **Type Safety**: Strongly-typed protocol buffer messages +- **Performance**: HTTP/2-based communication with multiplexing and compression + +## Starting the GRPC Server + +```bash +# Start GRPC server on default port 5001 +clink --grpc-server + +# Start GRPC server on custom port +clink --grpc-server --grpc-port 8080 +``` + +## API Service Definition + +The service is defined in `protos/lino_service.proto`: + +```protobuf +service LinoService { + // Execute a single LINO query + rpc ExecuteQuery(LinoQueryRequest) returns (LinoQueryResponse); + + // Execute multiple LINO queries in a batch + rpc ExecuteBatch(LinoBatchRequest) returns (LinoBatchResponse); + + // Stream LINO queries and get real-time responses + rpc StreamQueries(stream LinoStreamRequest) returns (stream LinoStreamResponse); + + // Get all links in the database + rpc GetAllLinks(GetAllLinksRequest) returns (GetAllLinksResponse); + + // Get structure of a specific link + rpc GetStructure(GetStructureRequest) returns (GetStructureResponse); +} +``` + +## Message Types + +### LinoQueryRequest +```protobuf +message LinoQueryRequest { + string query = 1; // LINO query string (e.g., "() ((1 1))") + string database_path = 2; // Optional database path + bool trace = 3; // Enable verbose output + bool include_changes = 4; // Include changes in response + bool include_before_state = 5; // Include state before operation + bool include_after_state = 6; // Include state after operation +} +``` + +### LinoQueryResponse +```protobuf +message LinoQueryResponse { + bool success = 1; // Operation success status + string error_message = 2; // Error message if failed + repeated string changes = 3; // Changes in LINO format + repeated string before_state = 4; // State before in LINO format + repeated string after_state = 5; // State after in LINO format + ExecutionMetadata metadata = 6; // Execution metadata +} +``` + +## LINO vs JSON Comparison + +### Traditional GRPC with JSON: +```json +{ + "operation": "create", + "links": [ + {"source": 1, "target": 1}, + {"source": 2, "target": 2} + ] +} +``` + +### LINO GRPC API: +```protobuf +query: "() ((1 1) (2 2))" +``` + +The LINO approach is more concise and directly compatible with the existing LINO ecosystem. + +## Usage Examples + +### 1. Create Links +```csharp +var request = new LinoQueryRequest +{ + Query = "() ((1 1) (2 2))", // Create two links + IncludeChanges = true, + IncludeAfterState = true +}; + +var response = await client.ExecuteQueryAsync(request); +``` + +### 2. Update Links +```csharp +var request = new LinoQueryRequest +{ + Query = "((1: 1 1)) ((1: 1 2))", // Update link 1 + IncludeChanges = true +}; + +var response = await client.ExecuteQueryAsync(request); +``` + +### 3. Read Links +```csharp +var request = new LinoQueryRequest +{ + Query = "((($i: $s $t)) (($i: $s $t)))", // Read all links + IncludeAfterState = true +}; + +var response = await client.ExecuteQueryAsync(request); +``` + +### 4. Delete Links +```csharp +var request = new LinoQueryRequest +{ + Query = "((1 2)) ()", // Delete link with source 1, target 2 + IncludeChanges = true +}; + +var response = await client.ExecuteQueryAsync(request); +``` + +### 5. Batch Operations +```csharp +var batchRequest = new LinoBatchRequest(); +batchRequest.Queries.Add(new LinoQueryRequest { Query = "() ((3 3))" }); +batchRequest.Queries.Add(new LinoQueryRequest { Query = "() ((4 4))" }); + +var response = await client.ExecuteBatchAsync(batchRequest); +``` + +### 6. Streaming Operations +```csharp +using var stream = client.StreamQueries(); + +await stream.RequestStream.WriteAsync(new LinoStreamRequest +{ + Query = new LinoQueryRequest { Query = "() ((5 5))" } +}); + +await foreach (var response in stream.ResponseStream.ReadAllAsync()) +{ + Console.WriteLine($"Response: {response.QueryResponse.Success}"); +} +``` + +## Benefits over JSON-based GRPC + +1. **Native LINO Support**: Direct use of LINO syntax without translation layers +2. **Consistency**: Same syntax as CLI tool and existing toolchain +3. **Expressiveness**: LINO's pattern matching is more powerful than JSON structures +4. **Compactness**: LINO notation is often more concise than equivalent JSON +5. **Type Safety**: Protocol buffers provide compile-time type checking +6. **Performance**: Binary protocol buffer encoding vs text-based JSON + +## Implementation Status + +- ✅ Protocol buffer definitions +- ✅ GRPC service interface +- ✅ Basic request/response handling +- ✅ CLI integration with `--grpc-server` flag +- 🔄 Full ASP.NET Core server implementation (in progress) +- 🔄 Client libraries and tooling +- 🔄 Advanced features (authentication, load balancing, etc.) + +## Development Setup + +1. Install .NET 8 SDK +2. Install Protocol Buffers compiler (`protoc`) +3. Build the project: `dotnet build` +4. Run server: `clink --grpc-server` +5. Use any GRPC client to connect to `localhost:5001` + +## Client Generation + +Generate client libraries for various languages: + +```bash +# C#/.NET (already included) +dotnet build + +# Python +python -m grpc_tools.protoc -I./protos --python_out=./clients/python --grpc_python_out=./clients/python ./protos/lino_service.proto + +# Go +protoc --go_out=./clients/go --go-grpc_out=./clients/go ./protos/lino_service.proto + +# Node.js/TypeScript +protoc --js_out=import_style=commonjs:./clients/nodejs --grpc-web_out=import_style=typescript,mode=grpcjs:./clients/nodejs ./protos/lino_service.proto +``` + +## Testing + +Use tools like BloomRPC, grpcurl, or custom clients to test the API: + +```bash +# Using grpcurl to test +grpcurl -plaintext -d '{"query": "() ((1 1))"}' localhost:5001 lino.api.LinoService/ExecuteQuery +``` + +## Future Enhancements + +- Authentication and authorization +- Rate limiting and throttling +- Metrics and monitoring integration +- Advanced streaming patterns +- GraphQL-style query optimization +- Multi-database support +- Clustering and load balancing + +--- + +This GRPC API provides a modern, efficient alternative to REST APIs while maintaining full compatibility with LINO notation and the existing links ecosystem. \ No newline at end of file diff --git a/examples/grpc_client_example.cs b/examples/grpc_client_example.cs new file mode 100644 index 0000000..fbbc2cf --- /dev/null +++ b/examples/grpc_client_example.cs @@ -0,0 +1,181 @@ +// Example GRPC client usage for LINO API +// This demonstrates how to use the LINO GRPC API instead of JSON + +using Grpc.Net.Client; +using Foundation.Data.Doublets.Cli.Grpc; + +// Example usage of the LINO GRPC API +public class LinoGrpcClientExample +{ + private readonly LinoService.LinoServiceClient _client; + + public LinoGrpcClientExample(string serverAddress = "https://localhost:5001") + { + var channel = GrpcChannel.ForAddress(serverAddress); + _client = new LinoService.LinoServiceClient(channel); + } + + // Example 1: Create links using LINO notation + public async Task CreateLinksExample() + { + var request = new LinoQueryRequest + { + Query = "() ((1 1) (2 2))", // Create links (1 1) and (2 2) + IncludeChanges = true, + IncludeAfterState = true + }; + + var response = await _client.ExecuteQueryAsync(request); + + Console.WriteLine("LINO Create Operation:"); + Console.WriteLine($"Success: {response.Success}"); + + if (response.Changes.Any()) + { + Console.WriteLine("Changes:"); + foreach (var change in response.Changes) + { + Console.WriteLine($" {change}"); + } + } + + if (response.AfterState.Any()) + { + Console.WriteLine("After State:"); + foreach (var link in response.AfterState) + { + Console.WriteLine($" {link}"); + } + } + } + + // Example 2: Update links using LINO notation + public async Task UpdateLinksExample() + { + var request = new LinoQueryRequest + { + Query = "((1: 1 1)) ((1: 1 2))", // Update link 1 to point from 1 to 2 + IncludeChanges = true + }; + + var response = await _client.ExecuteQueryAsync(request); + + Console.WriteLine("LINO Update Operation:"); + Console.WriteLine($"Success: {response.Success}"); + + foreach (var change in response.Changes) + { + Console.WriteLine($"Change: {change}"); + } + } + + // Example 3: Read all links + public async Task ReadAllLinksExample() + { + var request = new GetAllLinksRequest + { + IncludeNames = true + }; + + var response = await _client.GetAllLinksAsync(request); + + Console.WriteLine("All Links:"); + foreach (var link in response.Links) + { + Console.WriteLine($" {link}"); + } + } + + // Example 4: Batch operations + public async Task BatchOperationsExample() + { + var batchRequest = new LinoBatchRequest + { + StopOnError = true + }; + + // Add multiple LINO queries + batchRequest.Queries.Add(new LinoQueryRequest + { + Query = "() ((3 3))", // Create link (3 3) + IncludeChanges = true + }); + + batchRequest.Queries.Add(new LinoQueryRequest + { + Query = "() ((4 4))", // Create link (4 4) + IncludeChanges = true + }); + + var response = await _client.ExecuteBatchAsync(batchRequest); + + Console.WriteLine("Batch Operation:"); + Console.WriteLine($"Success: {response.Success}"); + Console.WriteLine($"Successful operations: {response.SuccessfulOperations}"); + Console.WriteLine($"Failed operations: {response.FailedOperations}"); + } + + // Example 5: Streaming operations + public async Task StreamingExample() + { + using var stream = _client.StreamQueries(); + + // Send multiple queries through the stream + await stream.RequestStream.WriteAsync(new LinoStreamRequest + { + Query = new LinoQueryRequest + { + Query = "() ((5 5))", + IncludeChanges = true + } + }); + + await stream.RequestStream.WriteAsync(new LinoStreamRequest + { + Query = new LinoQueryRequest + { + Query = "() ((6 6))", + IncludeChanges = true + } + }); + + await stream.RequestStream.CompleteAsync(); + + // Read responses + await foreach (var response in stream.ResponseStream.ReadAllAsync()) + { + if (response.QueryResponse != null) + { + Console.WriteLine($"Stream Response Success: {response.QueryResponse.Success}"); + foreach (var change in response.QueryResponse.Changes) + { + Console.WriteLine($" Change: {change}"); + } + } + } + } +} + +// Main usage example +class Program +{ + static async Task Main(string[] args) + { + var client = new LinoGrpcClientExample(); + + try + { + Console.WriteLine("=== LINO GRPC API Examples ==="); + + await client.CreateLinksExample(); + await client.UpdateLinksExample(); + await client.ReadAllLinksExample(); + await client.BatchOperationsExample(); + await client.StreamingExample(); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/protos/lino_service.proto b/protos/lino_service.proto new file mode 100644 index 0000000..382e838 --- /dev/null +++ b/protos/lino_service.proto @@ -0,0 +1,164 @@ +syntax = "proto3"; + +package lino.api; + +option csharp_namespace = "Foundation.Data.Doublets.Cli.Grpc"; + +// Service definition for LINO operations +service LinoService { + // Execute a single LINO query + rpc ExecuteQuery(LinoQueryRequest) returns (LinoQueryResponse); + + // Execute multiple LINO queries in a batch + rpc ExecuteBatch(LinoBatchRequest) returns (LinoBatchResponse); + + // Stream LINO queries and get real-time responses + rpc StreamQueries(stream LinoStreamRequest) returns (stream LinoStreamResponse); + + // Get all links in the database + rpc GetAllLinks(GetAllLinksRequest) returns (GetAllLinksResponse); + + // Get structure of a specific link + rpc GetStructure(GetStructureRequest) returns (GetStructureResponse); +} + +// Request message containing LINO query string +message LinoQueryRequest { + // The LINO query string (e.g., "() ((1 1))" or "((1: 1 1)) ((1: 1 2))") + string query = 1; + + // Database file path (optional, defaults to "db.links") + string database_path = 2; + + // Enable trace/verbose output + bool trace = 3; + + // Include changes in response + bool include_changes = 4; + + // Include database state before operation + bool include_before_state = 5; + + // Include database state after operation + bool include_after_state = 6; +} + +// Response message containing operation results +message LinoQueryResponse { + // Success status + bool success = 1; + + // Error message if operation failed + string error_message = 2; + + // Changes applied (in LINO format) + repeated string changes = 3; + + // Database state before operation (in LINO format) + repeated string before_state = 4; + + // Database state after operation (in LINO format) + repeated string after_state = 5; + + // Execution metadata + ExecutionMetadata metadata = 6; +} + +// Batch request for multiple queries +message LinoBatchRequest { + repeated LinoQueryRequest queries = 1; + + // Stop on first error + bool stop_on_error = 2; +} + +// Batch response +message LinoBatchResponse { + repeated LinoQueryResponse responses = 1; + + // Overall success status + bool success = 2; + + // Number of successful operations + int32 successful_operations = 3; + + // Number of failed operations + int32 failed_operations = 4; +} + +// Stream request message +message LinoStreamRequest { + oneof request_type { + LinoQueryRequest query = 1; + StreamControlMessage control = 2; + } +} + +// Stream response message +message LinoStreamResponse { + oneof response_type { + LinoQueryResponse query_response = 1; + StreamStatusMessage status = 2; + } +} + +// Stream control message +message StreamControlMessage { + enum ControlType { + PAUSE = 0; + RESUME = 1; + CANCEL = 2; + PING = 3; + } + + ControlType type = 1; +} + +// Stream status message +message StreamStatusMessage { + enum Status { + READY = 0; + PROCESSING = 1; + PAUSED = 2; + ERROR = 3; + CLOSED = 4; + } + + Status status = 1; + string message = 2; +} + +// Get all links request +message GetAllLinksRequest { + string database_path = 1; + bool include_names = 2; +} + +// Get all links response +message GetAllLinksResponse { + repeated string links = 1; + bool success = 2; + string error_message = 3; +} + +// Get structure request +message GetStructureRequest { + uint32 link_id = 1; + string database_path = 2; + bool include_names = 3; +} + +// Get structure response +message GetStructureResponse { + string structure = 1; + bool success = 2; + string error_message = 3; +} + +// Execution metadata +message ExecutionMetadata { + int64 execution_time_ms = 1; + int32 links_processed = 2; + int32 operations_count = 3; + string timestamp = 4; +} \ No newline at end of file