// graphql.proto
//
// Copyright (c) 2020 ysugimoto
//
// Released under the MIT license.
// see https://opensource.org/licenses/MIT
syntax = "proto3";

package graphql;

option (rust_package) = "grpc_graphql_gateway::graphql";

import "google/protobuf/descriptor.proto";

// Extend ServiceOptions in order to define grpc connection setting.
// User can use this option as following:
//
// service Greeter {
//    option (graphql.service) = {
//      host: "localhost:50051" // define grpc connection host and port
//      insecure: true          // set true if connect to insecure grpc server
//    };
//
//    ... some rpc definitions
// }
message GraphqlService {
  // gRPC default connection host.
  // This value should include host and port, say localhost:50051.
  string host = 1;
  // If true, automatic connection with insecure option.
  bool insecure = 2;
}


// Extend MethodOptions in order to define GraphQL Query or Mutation.
// User can use this option as following:
//
// service Greeter {
//    rpc SayHello(HelloRequest) returns (HelloReply) {
//      option (graphql.schema) = {
//        type: QUERY    // declare as Query
//        name: "hello"  // query name
//      }
//    }
// }
//
// Since gRPC reason, it has limitation that the response could not be repeated.
// it's dificcurl to respond array response, so that we accept "response.pluck"
// in order to expose repeated fields in response message.
//
// For instance:
//
// message Member {
//   string name = 1;
// }
//
// message ListMembersResponse {
//   repeated Member members = 1; -- could be array response
// }
//
// message ListMembersRequest {
// }
//
// service MemberService {
//    rpc ListMembers(ListMembersRequest) returns (ListMembersResponse) {
//      option (graphql.schema) = {
//        type: QUERY
//        name: "members"
//        response {
//          repeated : true
//          pluck: "members" // Query will respond [Member] instead of ListMembersResponse
//        }
//      }
//    }
// }
//
// In mutation declaration:
//
// service MemberService {
//    rpc CreateMember(CreateMemberRequest) returns (Member) {
//      option (graphql.schema) = {
//        type: MUTATION        // declare as Mutation
//        name: "cretemember"   // mutation name
//      }
//    }
// }
//
// The Mutation's input always becomes an input object, so you need to declare argument name.
//
// message Member {
//   string name = 1;
// }
//
// message CreateMemberRequest {
//   string name = 1;
// }
//
// service MemberService {
//    rpc CreateMember(CreateMemberRequest) returns (Member) {
//      option (graphql.schema) = {
//        type: MUTATION
//        name: "createmember"
//        request {
//          name: "member" // this is equivalent to createbook(member: Member): Member in GraphQL
//        }
//      }
//    }
// }
//
// Finally, user can access this query via /graphql?query={members{name}}
message GraphqlSchema {
  // graphql type. Enum of QUERY or MUTATION is valid value
  GraphqlType type = 1;
  // query name. this field is required
  string name = 2;
  // Query request object configuration
  GraphqlRequest request = 3;
  // Query response object configuration
  GraphqlResponse response = 4;
}

// configuration option for request
message GraphqlRequest {
  // Define input name.
  // This field enables only for mutation and note that if this field is specified,
  // the gRPC request message will be dealt with an input.
  string name = 1;

  // Define pluck message fields
  repeated string plucks = 2;
}

// configuration option for response
message GraphqlResponse {
  // If true, this response object is required
  // But when you declare "pluck", we respect expose field definition.
  bool required = 1;

  // Define pluck message field.
  // Note that this field IS NOT repeated, just single string field.
  // It means the response could only be single.
  string pluck = 2;
}

// explicit schema declaration enum
enum GraphqlType {
  // schema will generate as Query
  QUERY = 0;
  // schema will generate as Mutation
  MUTATION = 1;
  // schema will generate as Resolver. Resolver behaves not listed in query, but can resolve nested field.
  RESOLVER = 2;
  // schema will generate Subscription.
  SUBSCRIPTION = 3;
}

// GraphqlField is FieldOptions in protobuf in order to define type field attribute.
// User can use this option as following:
//
// message Member {
//   string name = 1 [(graphql.field) = {required: true}]; // this field is required in GraphQL, it equivalent to String! on GraphQL
// }
//
// message CreateMemberRequest {
//   string name = 1; [(grahpql.field) = {default: "anonymous"}]; // use default value on input or query
// }
//
// Note that in protobuf, all fields are dealt with optional
// so the same as it, all GraphQL fields are optional as default.
// If you need to be required, use 'required: true' option
message GraphqlField {
  // If true, this field is required.
  bool required = 1;
  // Use as other field name (not recommend)
  string name = 2;
  // Define default value on input.
  string default = 3;
  // Omit this field from graphql definition
  bool omit = 4;
  // Resolve this field by nested query with additional RPC
  string resolver = 5;
  // Federation: Mark this field as external (part of another service's entity)
  bool external = 6;
  // Federation: Specify fields this field requires from other services
  string requires = 7;
  // Federation: Specify fields this field provides to the supergraph
  string provides = 8;
  // Federation: Mark this field as shareable (can be resolved from multiple subgraphs)
  bool shareable = 9;
}

// Federation configuration for message types (entities).
// User can use this option to define federated entities:
//
// message User {
//   option (graphql.entity) = {
//     keys: "id"
//     keys: "email"
//   };
//   string id = 1;
//   string email = 2;
//   string name = 3;
// }
//
// For extending entities from another service:
//
// message Product {
//   option (graphql.entity) = {
//     extend: true
//     keys: "upc"
//   };
//   string upc = 1 [(graphql.field) = {external: true}];
//   int32 stock = 2;
// }
message GraphqlEntity {
  // List of key fields or field sets that uniquely identify this entity
  // Each key can be a single field name or a field set like "id email"
  repeated string keys = 1;
  // If true, this message extends an entity defined in another service
  bool extend = 2;
  // If specified, this service will be resolvable (implements _entities resolver)
  bool resolvable = 3;
}

// Live Query configuration for reactive subscriptions.
// User can use this option to enable live queries on specific RPC methods:
//
// service UserService {
//    rpc GetUser(GetUserRequest) returns (User) {
//      option (graphql.schema) = {
//        type: QUERY
//        name: "user"
//      };
//      option (graphql.live_query) = {
//        enabled: true
//        throttle_ms: 100
//        triggers: ["User.update", "User.delete"]
//      };
//    }
// }
//
// When a client sends a query with @live directive:
//   query @live { user(id: "1") { name status } }
//
// The gateway will:
// 1. Execute the query immediately
// 2. Re-execute and push updates when any trigger fires
// 3. Throttle updates to prevent flooding (default: 100ms)
message GraphqlLiveQuery {
  // Enable live query support for this method
  bool enabled = 1;
  
  // Minimum time between updates in milliseconds (default: 100ms)
  // Prevents flooding clients with too many updates
  uint32 throttle_ms = 2;
  
  // List of invalidation triggers that cause re-execution
  // Format: "TypeName.action" where action is: create, update, delete, or *
  // Examples: "User.update", "Post.*", "Comment.create"
  repeated string triggers = 3;
  
  // Maximum number of concurrent live query connections per client (default: 10)
  uint32 max_connections = 4;
  
  // Time-to-live in seconds. Live query auto-closes after this duration (0 = infinite)
  uint32 ttl_seconds = 5;
  
  // Strategy for detecting changes
  LiveQueryStrategy strategy = 6;
  
  // For POLLING strategy: interval in milliseconds
  uint32 poll_interval_ms = 7;
  
  // Entities this query depends on (for automatic invalidation)
  repeated string depends_on = 8;
}

// Strategy for detecting when to push live query updates
enum LiveQueryStrategy {
  // Push updates when invalidation triggers fire (recommended)
  INVALIDATION = 0;
  // Poll the underlying data source at regular intervals
  POLLING = 1;
  // Compare response hashes to detect changes
  HASH_DIFF = 2;
}

// Extend builtin messages

extend google.protobuf.FileOptions {
  string rust_package = 1079;
}

extend google.protobuf.ServiceOptions {
  GraphqlService service = 1079;
}

extend google.protobuf.FieldOptions {
  GraphqlField field = 1079;
}

extend google.protobuf.MessageOptions {
  GraphqlEntity entity = 1079;
}

extend google.protobuf.MethodOptions {
  GraphqlSchema schema = 1079;
  GraphqlLiveQuery live_query = 1080;
}
