From 64e3c53747c7a6a47eebff74a5b85a6ea17f19e4 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:44:40 +0000 Subject: [PATCH 01/17] refactor(config): represent field resolver as separate enum --- generated/.tailcallrc.schema.json | 140 +++++++------- src/cli/tc/init.rs | 4 +- src/core/blueprint/definitions.rs | 26 +-- src/core/blueprint/from_config.rs | 2 +- src/core/blueprint/operators/call.rs | 6 +- src/core/blueprint/operators/expr.rs | 13 +- src/core/blueprint/operators/graphql.rs | 6 +- src/core/blueprint/operators/grpc.rs | 4 +- src/core/blueprint/operators/http.rs | 4 +- src/core/blueprint/operators/js.rs | 4 +- src/core/config/config.rs | 182 +++++++++++------- src/core/config/from_document.rs | 69 +++---- src/core/config/into_document.rs | 7 +- .../consolidate_url/consolidate_url.rs | 18 +- src/core/directive.rs | 23 +-- src/core/generator/from_proto.rs | 6 +- .../json/field_base_url_generator.rs | 40 ++-- src/core/generator/json/query_generator.rs | 6 +- src/core/grpc/data_loader_request.rs | 7 +- src/core/grpc/protobuf.rs | 7 +- src/core/grpc/request_template.rs | 9 +- .../test-graphql-with-add-field.md_error.snap | 14 ++ .../test-invalid-query-in-http.md_error.snap | 2 + tests/execution/test-all-blueprint-errors.md | 2 +- .../execution/test-graphql-with-add-field.md | 28 +++ 25 files changed, 342 insertions(+), 287 deletions(-) create mode 100644 tests/core/snapshots/test-graphql-with-add-field.md_error.snap create mode 100644 tests/execution/test-graphql-with-add-field.md diff --git a/generated/.tailcallrc.schema.json b/generated/.tailcallrc.schema.json index baeee6655e..3914a26e28 100644 --- a/generated/.tailcallrc.schema.json +++ b/generated/.tailcallrc.schema.json @@ -372,6 +372,80 @@ "Field": { "description": "A field definition containing all the metadata information about resolving a field.", "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "http" + ], + "properties": { + "http": { + "$ref": "#/definitions/Http" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "grpc" + ], + "properties": { + "grpc": { + "$ref": "#/definitions/Grpc" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "graphql" + ], + "properties": { + "graphql": { + "$ref": "#/definitions/GraphQL" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "call" + ], + "properties": { + "call": { + "$ref": "#/definitions/Call" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "js" + ], + "properties": { + "js": { + "$ref": "#/definitions/JS" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "expr" + ], + "properties": { + "expr": { + "$ref": "#/definitions/Expr" + } + }, + "additionalProperties": false + } + ], "properties": { "args": { "description": "Map of argument name and its definition.", @@ -391,17 +465,6 @@ } ] }, - "call": { - "description": "Inserts a call resolver for the field.", - "anyOf": [ - { - "$ref": "#/definitions/Call" - }, - { - "type": "null" - } - ] - }, "default_value": { "description": "Stores the default value for the field" }, @@ -412,50 +475,6 @@ "null" ] }, - "expr": { - "description": "Inserts a constant resolver for the field.", - "anyOf": [ - { - "$ref": "#/definitions/Expr" - }, - { - "type": "null" - } - ] - }, - "graphql": { - "description": "Inserts a GraphQL resolver for the field.", - "anyOf": [ - { - "$ref": "#/definitions/GraphQL" - }, - { - "type": "null" - } - ] - }, - "grpc": { - "description": "Inserts a GRPC resolver for the field.", - "anyOf": [ - { - "$ref": "#/definitions/Grpc" - }, - { - "type": "null" - } - ] - }, - "http": { - "description": "Inserts an HTTP resolver for the field.", - "anyOf": [ - { - "$ref": "#/definitions/Http" - }, - { - "type": "null" - } - ] - }, "list": { "description": "Flag to indicate the type is a list.", "type": "boolean" @@ -502,17 +521,6 @@ "description": "Flag to indicate the type is required.", "type": "boolean" }, - "script": { - "description": "Inserts a Javascript resolver for the field.", - "anyOf": [ - { - "$ref": "#/definitions/JS" - }, - { - "type": "null" - } - ] - }, "type": { "description": "Refers to the type of the value the field can be resolved to.", "type": "string" diff --git a/src/cli/tc/init.rs b/src/cli/tc/init.rs index 0413ed65e5..e1a2b7672a 100644 --- a/src/cli/tc/init.rs +++ b/src/cli/tc/init.rs @@ -5,7 +5,7 @@ use anyhow::Result; use super::helpers::{FILE_NAME, JSON_FILE_NAME, YML_FILE_NAME}; use crate::cli::runtime::{confirm_and_write, create_directory, select_prompt}; -use crate::core::config::{Config, Expr, Field, RootSchema, Source, Type}; +use crate::core::config::{Config, Expr, Field, Resolver, RootSchema, Source, Type}; use crate::core::merge_right::MergeRight; use crate::core::runtime::TargetRuntime; @@ -75,7 +75,7 @@ fn main_config() -> Config { let field = Field { type_of: "String".to_string(), required: true, - const_field: Some(Expr { body: "Hello, World!".into() }), + resolver: Some(Resolver::Expr(Expr { body: "Hello, World!".into() })), ..Default::default() }; diff --git a/src/core/blueprint/definitions.rs b/src/core/blueprint/definitions.rs index b2e729a29c..d8df056f1f 100644 --- a/src/core/blueprint/definitions.rs +++ b/src/core/blueprint/definitions.rs @@ -102,20 +102,9 @@ fn process_field_within_type(context: ProcessFieldWithinTypeContext) -> Valid add_field.path[1..] .iter() .map(|s| s.to_owned()) @@ -497,15 +486,6 @@ pub fn to_field_definition( type_of: &config::Type, name: &String, ) -> Valid { - let directives = field.resolvable_directives(); - - if directives.len() > 1 { - return Valid::fail(format!( - "Multiple resolvers detected [{}]", - directives.join(", ") - )); - } - update_args() .and(update_http().trace(config::Http::trace_name().as_str())) .and(update_grpc(operation_type).trace(config::Grpc::trace_name().as_str())) diff --git a/src/core/blueprint/from_config.rs b/src/core/blueprint/from_config.rs index 81f3b238c3..fa350ba542 100644 --- a/src/core/blueprint/from_config.rs +++ b/src/core/blueprint/from_config.rs @@ -89,7 +89,7 @@ where let schema = if let Some(type_) = type_ { let mut schema_fields = HashMap::new(); for (name, field) in type_.fields.iter() { - if field.script.is_none() && field.http.is_none() { + if field.resolver.is_none() { schema_fields.insert(name.clone(), to_json_schema_for_field(field, config)); } } diff --git a/src/core/blueprint/operators/call.rs b/src/core/blueprint/operators/call.rs index d820eb3e19..5ea3cf31f3 100644 --- a/src/core/blueprint/operators/call.rs +++ b/src/core/blueprint/operators/call.rs @@ -2,7 +2,7 @@ use serde_json::Value; use crate::core::blueprint::*; use crate::core::config; -use crate::core::config::{Field, GraphQLOperationType}; +use crate::core::config::{Field, GraphQLOperationType, Resolver}; use crate::core::ir::model::IR; use crate::core::try_fold::TryFold; use crate::core::valid::{Valid, ValidationError, Validator}; @@ -14,11 +14,11 @@ pub fn update_call<'a>( { TryFold::<(&ConfigModule, &Field, &config::Type, &str), FieldDefinition, String>::new( move |(config, field, _, name), b_field| { - let Some(ref calls) = field.call else { + let Some(Resolver::Call(call)) = &field.resolver else { return Valid::succeed(b_field); }; - compile_call(config, calls, operation_type, object_name) + compile_call(config, call, operation_type, object_name) .map(|field| b_field.resolver(field.resolver).name(name.to_string())) }, ) diff --git a/src/core/blueprint/operators/expr.rs b/src/core/blueprint/operators/expr.rs index 30c771ed79..a429bf9e3b 100644 --- a/src/core/blueprint/operators/expr.rs +++ b/src/core/blueprint/operators/expr.rs @@ -2,7 +2,7 @@ use async_graphql_value::ConstValue; use crate::core::blueprint::*; use crate::core::config; -use crate::core::config::Field; +use crate::core::config::{Field, Resolver}; use crate::core::ir::model::IR; use crate::core::ir::model::IR::Dynamic; use crate::core::try_fold::TryFold; @@ -64,17 +64,12 @@ pub fn update_const_field<'a>( { TryFold::<(&ConfigModule, &Field, &config::Type, &str), FieldDefinition, String>::new( |(config_module, field, _, _), b_field| { - let Some(const_field) = &field.const_field else { + let Some(Resolver::Expr(expr)) = &field.resolver else { return Valid::succeed(b_field); }; - compile_expr(CompileExpr { - config_module, - field, - value: &const_field.body, - validate: true, - }) - .map(|resolver| b_field.resolver(Some(resolver))) + compile_expr(CompileExpr { config_module, field, value: &expr.body, validate: true }) + .map(|resolver| b_field.resolver(Some(resolver))) }, ) } diff --git a/src/core/blueprint/operators/graphql.rs b/src/core/blueprint/operators/graphql.rs index 74f8282644..bf5f42e9c4 100644 --- a/src/core/blueprint/operators/graphql.rs +++ b/src/core/blueprint/operators/graphql.rs @@ -1,7 +1,9 @@ use std::collections::{HashMap, HashSet}; use crate::core::blueprint::FieldDefinition; -use crate::core::config::{Config, ConfigModule, Field, GraphQL, GraphQLOperationType, Type}; +use crate::core::config::{ + Config, ConfigModule, Field, GraphQL, GraphQLOperationType, Resolver, Type, +}; use crate::core::graphql::RequestTemplate; use crate::core::helpers; use crate::core::ir::model::{IO, IR}; @@ -78,7 +80,7 @@ pub fn update_graphql<'a>( ) -> TryFold<'a, (&'a ConfigModule, &'a Field, &'a Type, &'a str), FieldDefinition, String> { TryFold::<(&ConfigModule, &Field, &Type, &'a str), FieldDefinition, String>::new( |(config, field, type_of, _), b_field| { - let Some(graphql) = &field.graphql else { + let Some(Resolver::Graphql(graphql)) = &field.resolver else { return Valid::succeed(b_field); }; diff --git a/src/core/blueprint/operators/grpc.rs b/src/core/blueprint/operators/grpc.rs index 3ab9bf7334..282a3be12e 100644 --- a/src/core/blueprint/operators/grpc.rs +++ b/src/core/blueprint/operators/grpc.rs @@ -5,7 +5,7 @@ use prost_reflect::FieldDescriptor; use crate::core::blueprint::{FieldDefinition, TypeLike}; use crate::core::config::group_by::GroupBy; -use crate::core::config::{Config, ConfigModule, Field, GraphQLOperationType, Grpc}; +use crate::core::config::{Config, ConfigModule, Field, GraphQLOperationType, Grpc, Resolver}; use crate::core::grpc::protobuf::{ProtobufOperation, ProtobufSet}; use crate::core::grpc::request_template::RequestTemplate; use crate::core::ir::model::{IO, IR}; @@ -216,7 +216,7 @@ pub fn update_grpc<'a>( { TryFold::<(&ConfigModule, &Field, &config::Type, &'a str), FieldDefinition, String>::new( |(config_module, field, type_of, _name), b_field| { - let Some(grpc) = &field.grpc else { + let Some(Resolver::Grpc(grpc)) = &field.resolver else { return Valid::succeed(b_field); }; diff --git a/src/core/blueprint/operators/http.rs b/src/core/blueprint/operators/http.rs index 08fa68f0c8..88910ba912 100644 --- a/src/core/blueprint/operators/http.rs +++ b/src/core/blueprint/operators/http.rs @@ -1,6 +1,6 @@ use crate::core::blueprint::*; use crate::core::config::group_by::GroupBy; -use crate::core::config::Field; +use crate::core::config::{Field, Resolver}; use crate::core::endpoint::Endpoint; use crate::core::http::{HttpFilter, Method, RequestTemplate}; use crate::core::ir::model::{IO, IR}; @@ -87,7 +87,7 @@ pub fn update_http<'a>( { TryFold::<(&ConfigModule, &Field, &config::Type, &'a str), FieldDefinition, String>::new( |(config_module, field, type_of, _), b_field| { - let Some(http) = &field.http else { + let Some(Resolver::Http(http)) = &field.resolver else { return Valid::succeed(b_field); }; diff --git a/src/core/blueprint/operators/js.rs b/src/core/blueprint/operators/js.rs index 0687f15b42..cc6b212c60 100644 --- a/src/core/blueprint/operators/js.rs +++ b/src/core/blueprint/operators/js.rs @@ -1,6 +1,6 @@ use crate::core::blueprint::FieldDefinition; use crate::core::config; -use crate::core::config::{ConfigModule, Field}; +use crate::core::config::{ConfigModule, Field, Resolver}; use crate::core::ir::model::{IO, IR}; use crate::core::try_fold::TryFold; use crate::core::valid::{Valid, Validator}; @@ -21,7 +21,7 @@ pub fn update_js_field<'a>( { TryFold::<(&ConfigModule, &Field, &config::Type, &str), FieldDefinition, String>::new( |(module, field, _, _), b_field| { - let Some(js) = &field.script else { + let Some(Resolver::Js(js)) = &field.resolver else { return Valid::succeed(b_field); }; diff --git a/src/core/config/config.rs b/src/core/config/config.rs index a9dd2aa3c6..9fae797935 100644 --- a/src/core/config/config.rs +++ b/src/core/config/config.rs @@ -3,7 +3,8 @@ use std::fmt::{self, Display}; use std::num::NonZeroU64; use anyhow::Result; -use async_graphql::parser::types::ServiceDocument; +use async_graphql::parser::types::{ConstDirective, ServiceDocument}; +use async_graphql::Positioned; use derive_setters::Setters; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -208,6 +209,85 @@ pub struct RootSchema { /// Used to omit a field from public consumption. pub struct Omit {} +// generate Resolver with macro in order to autogenerate conversion code +// from the underlying directives. +// TODO: replace with derive macro +macro_rules! create_resolver { + ($($var:ident($ty:ty)),+$(,)?) => { + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema)] + #[serde(rename_all = "camelCase")] + pub enum Resolver { + // just specify the same variants + $($var($ty),)+ + } + + impl Resolver { + pub fn from_directives( + directives: &[Positioned], + ) -> Valid, String> { + let mut result = None; + let mut resolvable_directives = Vec::new(); + let mut valid = Valid::succeed(()); + + $( + // try to parse directive from the Resolver variant + valid = valid.and(<$ty>::from_directives(directives.iter()).map(|resolver| { + if let Some(resolver) = resolver { + // on success store it as a result and remember parsed directives + result = Some(Self::$var(resolver)); + resolvable_directives.push(<$ty>::trace_name()); + } + })); + )+ + + valid.and_then(|_| { + if resolvable_directives.len() > 1 { + Valid::fail(format!( + "Multiple resolvers detected [{}]", + resolvable_directives.join(", ") + )) + } else { + Valid::succeed(result) + } + }) + } + } + }; +} + +create_resolver! { + Http(Http), + Grpc(Grpc), + Graphql(GraphQL), + Call(Call), + Js(JS), + Expr(Expr), +} + +impl Resolver { + pub fn to_directive(&self) -> ConstDirective { + match self { + Resolver::Http(d) => d.to_directive(), + Resolver::Grpc(d) => d.to_directive(), + Resolver::Graphql(d) => d.to_directive(), + Resolver::Call(d) => d.to_directive(), + Resolver::Js(d) => d.to_directive(), + Resolver::Expr(d) => d.to_directive(), + } + } + + pub fn directive_name(&self) -> String { + match self { + Resolver::Http(_) => Http::directive_name(), + Resolver::Grpc(_) => Grpc::directive_name(), + Resolver::Graphql(_) => GraphQL::directive_name(), + Resolver::Call(_) => Call::directive_name(), + Resolver::Js(_) => JS::directive_name(), + Resolver::Expr(_) => Expr::directive_name(), + } + } +} + /// /// A field definition containing all the metadata information about resolving a /// field. @@ -257,38 +337,13 @@ pub struct Field { pub omit: Option, /// - /// Inserts an HTTP resolver for the field. - #[serde(default, skip_serializing_if = "is_default")] - pub http: Option, - - /// - /// Inserts a call resolver for the field. - #[serde(default, skip_serializing_if = "is_default")] - pub call: Option, - - /// - /// Inserts a GRPC resolver for the field. - #[serde(default, skip_serializing_if = "is_default")] - pub grpc: Option, - - /// - /// Inserts a Javascript resolver for the field. - #[serde(default, skip_serializing_if = "is_default")] - pub script: Option, - - /// - /// Inserts a constant resolver for the field. - #[serde(rename = "expr", default, skip_serializing_if = "is_default")] - pub const_field: Option, + /// Sets the cache configuration for a field + pub cache: Option, /// - /// Inserts a GraphQL resolver for the field. + /// Stores the default value for the field #[serde(default, skip_serializing_if = "is_default")] - pub graphql: Option, - - /// - /// Sets the cache configuration for a field - pub cache: Option, + pub default_value: Option, /// /// Marks field as protected by auth provider @@ -296,9 +351,9 @@ pub struct Field { pub protected: Option, /// - /// Stores the default value for the field - #[serde(default, skip_serializing_if = "is_default")] - pub default_value: Option, + /// Resolver for the field + #[serde(flatten, default, skip_serializing_if = "is_default")] + pub resolver: Option, } // It's a terminal implementation of MergeRight @@ -310,46 +365,21 @@ impl MergeRight for Field { impl Field { pub fn has_resolver(&self) -> bool { - self.http.is_some() - || self.script.is_some() - || self.const_field.is_some() - || self.graphql.is_some() - || self.grpc.is_some() - || self.call.is_some() - } - - /// Returns a list of resolvable directives for the field. - pub fn resolvable_directives(&self) -> Vec { - let mut directives = Vec::new(); - if self.http.is_some() { - directives.push(Http::trace_name()); - } - if self.graphql.is_some() { - directives.push(GraphQL::trace_name()); - } - if self.script.is_some() { - directives.push(JS::trace_name()); - } - if self.const_field.is_some() { - directives.push(Expr::trace_name()); - } - if self.grpc.is_some() { - directives.push(Grpc::trace_name()); - } - if self.call.is_some() { - directives.push(Call::trace_name()); - } - directives + self.resolver.is_some() } pub fn has_batched_resolver(&self) -> bool { - self.http - .as_ref() - .is_some_and(|http| !http.batch_key.is_empty()) - || self.graphql.as_ref().is_some_and(|graphql| graphql.batch) - || self - .grpc - .as_ref() - .is_some_and(|grpc| !grpc.batch_key.is_empty()) + if let Some(resolver) = &self.resolver { + match resolver { + Resolver::Http(http) => !http.batch_key.is_empty(), + Resolver::Grpc(grpc) => !grpc.batch_key.is_empty(), + Resolver::Graphql(graphql) => graphql.batch, + Resolver::Call(_) => false, + Resolver::Js(_) => false, + Resolver::Expr(_) => false, + } + } else { + false + } } pub fn into_list(mut self) -> Self { self.list = true; @@ -1076,12 +1106,18 @@ mod tests { let f1 = Field { ..Default::default() }; let f2 = Field { - http: Some(Http { batch_key: vec!["id".to_string()], ..Default::default() }), + resolver: Some(Resolver::Http(Http { + batch_key: vec!["id".to_string()], + ..Default::default() + })), ..Default::default() }; let f3 = Field { - http: Some(Http { batch_key: vec![], ..Default::default() }), + resolver: Some(Resolver::Http(Http { + batch_key: vec![], + ..Default::default() + })), ..Default::default() }; diff --git a/src/core/config/from_document.rs b/src/core/config/from_document.rs index 0443b5d444..da6a94847e 100644 --- a/src/core/config/from_document.rs +++ b/src/core/config/from_document.rs @@ -10,10 +10,10 @@ use async_graphql::Name; use async_graphql_value::ConstValue; use super::telemetry::Telemetry; -use super::{Alias, JS}; +use super::Alias; use crate::core::config::{ - self, Cache, Call, Config, Enum, GraphQL, Grpc, Link, Modify, Omit, Protected, RootSchema, - Server, Union, Upstream, Variant, + self, Cache, Config, Enum, Link, Modify, Omit, Protected, RootSchema, Server, Union, Upstream, + Variant, }; use crate::core::directive::DirectiveCodec; use crate::core::valid::{Valid, ValidationError, Validator}; @@ -74,7 +74,7 @@ fn schema_definition(doc: &ServiceDocument) -> Valid<&SchemaDefinition, String> .map_or_else(|| Valid::succeed(DEFAULT_SCHEMA_DEFINITION), Valid::succeed) } -fn process_schema_directives + Default>( +fn process_schema_directives( schema_definition: &SchemaDefinition, directive_name: &str, ) -> Valid { @@ -87,7 +87,7 @@ fn process_schema_directives + Default>( res } -fn process_schema_multiple_directives + Default>( +fn process_schema_multiple_directives( schema_definition: &SchemaDefinition, directive_name: &str, ) -> Valid, String> { @@ -152,12 +152,14 @@ fn to_types( &type_definition.node.description, &type_definition.node.directives, ) + .trace(&type_name) .some(), TypeKind::Interface(interface_type) => to_object_type( &interface_type, &type_definition.node.description, &type_definition.node.directives, ) + .trace(&type_name) .some(), TypeKind::Enum(_) => Valid::none(), TypeKind::InputObject(input_object_type) => to_input_object( @@ -165,6 +167,7 @@ fn to_types( &type_definition.node.description, &type_definition.node.directives, ) + .trace(&type_name) .some(), TypeKind::Union(_) => Valid::none(), TypeKind::Scalar => Valid::succeed(Some(to_scalar_type())), @@ -227,7 +230,6 @@ fn to_enum_types( .map(|values| values.into_iter().flatten().collect()) } -#[allow(clippy::too_many_arguments)] fn to_object_type( object: &T, description: &Option>, @@ -305,7 +307,7 @@ fn to_common_field( default_value: Option, ) -> Valid where - F: FieldLike, + F: FieldLike + HasName, { let type_of = field.type_of(); let base = &type_of.base; @@ -322,40 +324,30 @@ where let list = matches!(&base, BaseType::List(_)); let list_type_required = matches!(&base, BaseType::List(type_of) if !type_of.nullable); let doc = description.to_owned().map(|pos| pos.node); - config::Http::from_directives(directives.iter()) - .fuse(GraphQL::from_directives(directives.iter())) + + config::Resolver::from_directives(directives) .fuse(Cache::from_directives(directives.iter())) - .fuse(Grpc::from_directives(directives.iter())) .fuse(Omit::from_directives(directives.iter())) .fuse(Modify::from_directives(directives.iter())) - .fuse(JS::from_directives(directives.iter())) - .fuse(Call::from_directives(directives.iter())) .fuse(Protected::from_directives(directives.iter())) .fuse(default_value) .map( - |(http, graphql, cache, grpc, omit, modify, script, call, protected, default_value)| { - let const_field = to_const_field(directives); - config::Field { - type_of, - list, - required: !nullable, - list_type_required, - args, - doc, - modify, - omit, - http, - grpc, - script, - const_field, - graphql, - cache, - call, - protected, - default_value, - } + |(resolver, cache, omit, modify, protected, default_value)| config::Field { + type_of, + list, + required: !nullable, + list_type_required, + args, + doc, + modify, + omit, + cache, + protected, + default_value, + resolver, }, ) + .trace(pos_name_to_string(field.name()).as_str()) } fn to_type_of(type_: &Type) -> String { @@ -421,17 +413,6 @@ fn to_enum(enum_type: EnumType, doc: Option) -> Valid { }); variants.map(|v| Enum { variants: v.into_iter().collect::>(), doc }) } -fn to_const_field(directives: &[Positioned]) -> Option { - directives.iter().find_map(|directive| { - if directive.node.name.node == config::Expr::directive_name() { - config::Expr::from_directive(&directive.node) - .to_result() - .ok() - } else { - None - } - }) -} fn to_add_fields_from_directives( directives: &[Positioned], diff --git a/src/core/config/into_document.rs b/src/core/config/into_document.rs index e3e9b1fcf4..a33fe457f3 100644 --- a/src/core/config/into_document.rs +++ b/src/core/config/into_document.rs @@ -265,15 +265,10 @@ fn config_document(config: &Config) -> ServiceDocument { fn get_directives(field: &crate::core::config::Field) -> Vec> { let directives = vec![ - field.http.as_ref().map(|d| pos(d.to_directive())), - field.script.as_ref().map(|d| pos(d.to_directive())), - field.const_field.as_ref().map(|d| pos(d.to_directive())), + field.resolver.as_ref().map(|d| pos(d.to_directive())), field.modify.as_ref().map(|d| pos(d.to_directive())), field.omit.as_ref().map(|d| pos(d.to_directive())), - field.graphql.as_ref().map(|d| pos(d.to_directive())), - field.grpc.as_ref().map(|d| pos(d.to_directive())), field.cache.as_ref().map(|d| pos(d.to_directive())), - field.call.as_ref().map(|d| pos(d.to_directive())), field.protected.as_ref().map(|d| pos(d.to_directive())), ]; diff --git a/src/core/config/transformer/consolidate_url/consolidate_url.rs b/src/core/config/transformer/consolidate_url/consolidate_url.rs index b619c0a4e0..71d90ebb9f 100644 --- a/src/core/config/transformer/consolidate_url/consolidate_url.rs +++ b/src/core/config/transformer/consolidate_url/consolidate_url.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use super::max_value_map::MaxValueMap; -use crate::core::config::Config; +use crate::core::config::{Config, Resolver}; use crate::core::transform::Transform; use crate::core::valid::Valid; @@ -23,9 +23,9 @@ impl UrlTypeMapping { /// Populates the URL type mapping based on the given configuration. fn populate_url_frequency_map(&mut self, config: &Config) { for (type_name, type_) in config.types.iter() { - for field_ in type_.fields.values() { - if let Some(http_directive) = &field_.http { - if let Some(base_url) = &http_directive.base_url { + for field in type_.fields.values() { + if let Some(Resolver::Http(http)) = &field.resolver { + if let Some(base_url) = &http.base_url { self.url_to_frequency_map.increment(base_url.to_owned(), 1); self.visited_type_set.insert(type_name.to_owned()); } @@ -63,11 +63,11 @@ impl ConsolidateURL { for type_name in url_type_mapping.visited_type_set { if let Some(type_) = config.types.get_mut(&type_name) { - for field_ in type_.fields.values_mut() { - if let Some(htto_directive) = &mut field_.http { - if let Some(base_url) = &htto_directive.base_url { - if *base_url == common_url { - htto_directive.base_url = None; + for field in type_.fields.values_mut() { + if let Some(Resolver::Http(http)) = &mut field.resolver { + if let Some(base_url) = &http.base_url { + if base_url == &common_url { + http.base_url = None; } } } diff --git a/src/core/directive.rs b/src/core/directive.rs index bd5a5d3e3d..e10dd8833e 100644 --- a/src/core/directive.rs +++ b/src/core/directive.rs @@ -1,5 +1,3 @@ -use std::slice::Iter; - use async_graphql::parser::types::ConstDirective; use async_graphql::{Name, Pos, Positioned}; use serde::{Deserialize, Serialize}; @@ -25,20 +23,16 @@ fn to_const_directive(directive: &blueprint::Directive) -> Valid { +pub trait DirectiveCodec: Sized { fn directive_name() -> String; - fn from_directive(directive: &ConstDirective) -> Valid; - #[allow(dead_code)] - fn from_blueprint_directive(directive: &blueprint::Directive) -> Valid { - to_const_directive(directive).and_then(|a| Self::from_directive(&a)) - } + fn from_directive(directive: &ConstDirective) -> Valid; fn to_directive(&self) -> ConstDirective; fn trace_name() -> String { format!("@{}", Self::directive_name()) } - fn from_directives( - directives: Iter<'_, Positioned>, - ) -> Valid, String> { + fn from_directives<'a>( + directives: impl Iterator>, + ) -> Valid, String> { for directive in directives { if directive.node.name.node == Self::directive_name() { return Self::from_directive(&directive.node).map(Some); @@ -47,7 +41,7 @@ pub trait DirectiveCodec { Valid::succeed(None) } } -fn lower_case_first_letter(s: String) -> String { +fn lower_case_first_letter(s: &str) -> String { if s.len() <= 2 { s.to_lowercase() } else if let Some(first_char) = s.chars().next() { @@ -57,14 +51,13 @@ fn lower_case_first_letter(s: String) -> String { } } -impl<'a, A: Deserialize<'a> + Serialize + 'a> DirectiveCodec for A { +impl<'a, A: Deserialize<'a> + Serialize + 'a> DirectiveCodec for A { fn directive_name() -> String { lower_case_first_letter( std::any::type_name::() .split("::") .last() - .unwrap_or_default() - .to_string(), + .unwrap_or_default(), ) } diff --git a/src/core/generator/from_proto.rs b/src/core/generator/from_proto.rs index 292fd5b0e4..dabf42b7ae 100644 --- a/src/core/generator/from_proto.rs +++ b/src/core/generator/from_proto.rs @@ -13,7 +13,7 @@ use super::proto::comments_builder::CommentsBuilder; use super::proto::path_builder::PathBuilder; use super::proto::path_field::PathField; use crate::core::config::transformer::{AmbiguousType, TreeShake}; -use crate::core::config::{Arg, Config, Enum, Field, Grpc, Type, Union, Variant}; +use crate::core::config::{Arg, Config, Enum, Field, Grpc, Resolver, Type, Union, Variant}; use crate::core::transform::{Transform, TransformerOps}; use crate::core::valid::Validator; @@ -364,13 +364,13 @@ impl Context { cfg_field.type_of = output_ty; cfg_field.required = true; - cfg_field.grpc = Some(Grpc { + cfg_field.resolver = Some(Resolver::Grpc(Grpc { base_url: None, body, batch_key: vec![], headers: vec![], method: field_name.id(), - }); + })); let method_path = PathBuilder::new(&path).extend(PathField::Method, method_index as i32); diff --git a/src/core/generator/json/field_base_url_generator.rs b/src/core/generator/json/field_base_url_generator.rs index c71bb8d1ab..c152ca5a75 100644 --- a/src/core/generator/json/field_base_url_generator.rs +++ b/src/core/generator/json/field_base_url_generator.rs @@ -1,7 +1,7 @@ use url::Url; use super::url_utils::extract_base_url; -use crate::core::config::Config; +use crate::core::config::{Config, Resolver}; use crate::core::transform::Transform; use crate::core::valid::Valid; @@ -29,14 +29,10 @@ impl Transform for FieldBaseUrlGenerator<'_> { if let Some(query_type) = config.types.get_mut(self.query) { for field in query_type.fields.values_mut() { - field.http = match field.http.clone() { - Some(mut http) => { - if http.base_url.is_none() { - http.base_url = Some(base_url.clone()); - } - Some(http) + if let Some(Resolver::Http(http)) = &mut field.resolver { + if http.base_url.is_none() { + http.base_url = Some(base_url.clone()) } - None => None, } } } @@ -51,7 +47,7 @@ mod test { use url::Url; use super::FieldBaseUrlGenerator; - use crate::core::config::{Config, Field, Http, Type}; + use crate::core::config::{Config, Field, Http, Resolver, Type}; use crate::core::transform::Transform; use crate::core::valid::Validator; @@ -67,7 +63,10 @@ mod test { "f1".to_string(), Field { type_of: "Int".to_string(), - http: Some(Http { path: "/day".to_string(), ..Default::default() }), + resolver: Some(Resolver::Http(Http { + path: "/day".to_string(), + ..Default::default() + })), ..Default::default() }, ); @@ -75,7 +74,10 @@ mod test { "f2".to_string(), Field { type_of: "String".to_string(), - http: Some(Http { path: "/month".to_string(), ..Default::default() }), + resolver: Some(Resolver::Http(Http { + path: "/month".to_string(), + ..Default::default() + })), ..Default::default() }, ); @@ -83,7 +85,10 @@ mod test { "f3".to_string(), Field { type_of: "String".to_string(), - http: Some(Http { path: "/status".to_string(), ..Default::default() }), + resolver: Some(Resolver::Http(Http { + path: "/status".to_string(), + ..Default::default() + })), ..Default::default() }, ); @@ -107,11 +112,11 @@ mod test { "f1".to_string(), Field { type_of: "Int".to_string(), - http: Some(Http { + resolver: Some(Resolver::Http(Http { base_url: Some("https://calender.com/api/v1/".to_string()), path: "/day".to_string(), ..Default::default() - }), + })), ..Default::default() }, ); @@ -119,7 +124,10 @@ mod test { "f2".to_string(), Field { type_of: "String".to_string(), - http: Some(Http { path: "/month".to_string(), ..Default::default() }), + resolver: Some(Resolver::Http(Http { + path: "/month".to_string(), + ..Default::default() + })), ..Default::default() }, ); @@ -127,7 +135,7 @@ mod test { "f3".to_string(), Field { type_of: "String".to_string(), - http: None, + resolver: None, ..Default::default() }, ); diff --git a/src/core/generator/json/query_generator.rs b/src/core/generator/json/query_generator.rs index e3a76b46c9..7030787fce 100644 --- a/src/core/generator/json/query_generator.rs +++ b/src/core/generator/json/query_generator.rs @@ -2,7 +2,7 @@ use url::Url; use super::http_directive_generator::HttpDirectiveGenerator; use super::types_generator::OperationGenerator; -use crate::core::config::{Config, Field, Type}; +use crate::core::config::{Config, Field, Resolver, Type}; use crate::core::valid::Valid; pub struct QueryGenerator<'a> { @@ -28,7 +28,9 @@ impl OperationGenerator for QueryGenerator<'_> { // generate required http directive. let http_directive_gen = HttpDirectiveGenerator::new(self.url); - field.http = Some(http_directive_gen.generate_http_directive(&mut field)); + field.resolver = Some(Resolver::Http( + http_directive_gen.generate_http_directive(&mut field), + )); // if type is already present, then append the new field to it else create one. if let Some(type_) = config.types.get_mut(self.query) { diff --git a/src/core/grpc/data_loader_request.rs b/src/core/grpc/data_loader_request.rs index 39547f3e1a..7179a19005 100644 --- a/src/core/grpc/data_loader_request.rs +++ b/src/core/grpc/data_loader_request.rs @@ -63,7 +63,7 @@ mod tests { use super::DataLoaderRequest; use crate::core::blueprint::GrpcMethod; use crate::core::config::reader::ConfigReader; - use crate::core::config::{Config, Field, Grpc, Link, LinkType, Type}; + use crate::core::config::{Config, Field, Grpc, Link, LinkType, Resolver, Type}; use crate::core::grpc::protobuf::{ProtobufOperation, ProtobufSet}; use crate::core::grpc::request_template::RenderedRequestTemplate; @@ -82,7 +82,10 @@ mod tests { let grpc = Grpc { method: method.to_string(), ..Default::default() }; config.types.insert( "foo".to_string(), - Type::default().fields(vec![("bar", Field::default().grpc(grpc))]), + Type::default().fields(vec![( + "bar", + Field::default().resolver(Resolver::Grpc(grpc)), + )]), ); let runtime = crate::core::runtime::test::init(None); diff --git a/src/core/grpc/protobuf.rs b/src/core/grpc/protobuf.rs index 4e3ffa7d97..adcea67407 100644 --- a/src/core/grpc/protobuf.rs +++ b/src/core/grpc/protobuf.rs @@ -251,7 +251,7 @@ pub mod tests { use super::*; use crate::core::blueprint::GrpcMethod; use crate::core::config::reader::ConfigReader; - use crate::core::config::{Config, Field, Grpc, Link, LinkType, Type}; + use crate::core::config::{Config, Field, Grpc, Link, LinkType, Resolver, Type}; pub async fn get_proto_file(path: &str) -> Result { let runtime = crate::core::runtime::test::init(None); @@ -272,7 +272,10 @@ pub mod tests { let grpc = Grpc { method: method.to_string(), ..Default::default() }; config.types.insert( "foo".to_string(), - Type::default().fields(vec![("bar", Field::default().grpc(grpc))]), + Type::default().fields(vec![( + "bar", + Field::default().resolver(Resolver::Grpc(grpc)), + )]), ); Ok(reader .resolve(config, None) diff --git a/src/core/grpc/request_template.rs b/src/core/grpc/request_template.rs index aff43e137b..53547414ba 100644 --- a/src/core/grpc/request_template.rs +++ b/src/core/grpc/request_template.rs @@ -142,7 +142,9 @@ mod tests { use super::{RequestBody, RequestTemplate}; use crate::core::blueprint::GrpcMethod; use crate::core::config::reader::ConfigReader; - use crate::core::config::{Config, Field, GraphQLOperationType, Grpc, Link, LinkType, Type}; + use crate::core::config::{ + Config, Field, GraphQLOperationType, Grpc, Link, LinkType, Resolver, Type, + }; use crate::core::grpc::protobuf::{ProtobufOperation, ProtobufSet}; use crate::core::ir::model::CacheKey; use crate::core::mustache::Mustache; @@ -167,7 +169,10 @@ mod tests { let grpc = Grpc { method: method.to_string(), ..Default::default() }; config.types.insert( "foo".to_string(), - Type::default().fields(vec![("bar", Field::default().grpc(grpc))]), + Type::default().fields(vec![( + "bar", + Field::default().resolver(Resolver::Grpc(grpc)), + )]), ); let protobuf_set = ProtobufSet::from_proto_file( diff --git a/tests/core/snapshots/test-graphql-with-add-field.md_error.snap b/tests/core/snapshots/test-graphql-with-add-field.md_error.snap new file mode 100644 index 0000000000..af649e47b8 --- /dev/null +++ b/tests/core/snapshots/test-graphql-with-add-field.md_error.snap @@ -0,0 +1,14 @@ +--- +source: tests/core/spec.rs +expression: errors +--- +[ + { + "message": "Cannot add field", + "trace": [ + "Query", + "@addField" + ], + "description": "Path: [post, user, name] contains resolver graphQL at [Post.user]" + } +] diff --git a/tests/core/snapshots/test-invalid-query-in-http.md_error.snap b/tests/core/snapshots/test-invalid-query-in-http.md_error.snap index 0f574af714..7d4320c23c 100644 --- a/tests/core/snapshots/test-invalid-query-in-http.md_error.snap +++ b/tests/core/snapshots/test-invalid-query-in-http.md_error.snap @@ -6,6 +6,8 @@ expression: errors { "message": "Parsing failed because of invalid type: map, expected a sequence", "trace": [ + "Query", + "user", "@http", "query" ], diff --git a/tests/execution/test-all-blueprint-errors.md b/tests/execution/test-all-blueprint-errors.md index 40bcbe1a2d..433c1ebadd 100644 --- a/tests/execution/test-all-blueprint-errors.md +++ b/tests/execution/test-all-blueprint-errors.md @@ -14,7 +14,7 @@ type Mutation { } type Query { foo(inp: B): Foo - bar: String @expr @expr(body: {name: "John"}) + bar: String @expr(body: {name: "John"}) } type Foo { a: String @expr(body: "1") diff --git a/tests/execution/test-graphql-with-add-field.md b/tests/execution/test-graphql-with-add-field.md new file mode 100644 index 0000000000..9c2ec25490 --- /dev/null +++ b/tests/execution/test-graphql-with-add-field.md @@ -0,0 +1,28 @@ +--- +error: true +--- + +# test-graphql-with-add-field + +```graphql @config +schema @server @upstream(baseURL: "http://jsonplaceholder.typicode.com") { + query: Query +} + +type Query @addField(name: "name", path: ["post", "user", "name"]) { + post: Post @graphQL(name: "posts") +} + +type Post { + id: Int + title: String + body: String + userId: Int! + user: User @graphQL(name: "user") +} + +type User { + id: Int + name: String +} +``` From 5b3aec14c3d9554ef6afedbba1baca2d1995db5e Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:24:38 +0000 Subject: [PATCH 02/17] feat(federation): implement entity resolver --- examples/apollo_federation_entities.graphql | 26 +++ generated/.tailcallrc.schema.json | 200 ++++++++++++++++++ src/core/app_context.rs | 12 +- src/core/blueprint/definitions.rs | 1 + src/core/blueprint/operators/call.rs | 15 +- .../blueprint/operators/entity_resolver.rs | 95 +++++++++ src/core/blueprint/operators/expr.rs | 8 +- src/core/blueprint/operators/graphql.rs | 2 +- src/core/blueprint/operators/http.rs | 12 +- src/core/blueprint/operators/js.rs | 8 +- src/core/blueprint/operators/mod.rs | 2 + src/core/config/config.rs | 53 +++-- src/core/config/from_document.rs | 17 +- src/core/config/into_document.rs | 6 +- .../transformer/apollo_federation/entities.rs | 80 +++++++ .../transformer/apollo_federation/mod.rs | 1 + src/core/config/transformer/mod.rs | 2 + src/core/config/transformer/required.rs | 1 + src/core/ir/error.rs | 5 + src/core/ir/eval.rs | 41 ++++ src/core/ir/model.rs | 13 +- ...apollo-federation-entities-batch.md_0.snap | 36 ++++ ...o-federation-entities-batch.md_client.snap | 65 ++++++ ...o-federation-entities-batch.md_merged.snap | 31 +++ .../apollo-federation-entities.md_0.snap | 36 ++++ .../apollo-federation-entities.md_client.snap | 65 ++++++ .../apollo-federation-entities.md_merged.snap | 31 +++ .../apollo-federation-entities-batch.md | 109 ++++++++++ tests/execution/apollo-federation-entities.md | 68 ++++++ 29 files changed, 995 insertions(+), 46 deletions(-) create mode 100644 examples/apollo_federation_entities.graphql create mode 100644 src/core/blueprint/operators/entity_resolver.rs create mode 100644 src/core/config/transformer/apollo_federation/entities.rs create mode 100644 src/core/config/transformer/apollo_federation/mod.rs create mode 100644 tests/core/snapshots/apollo-federation-entities-batch.md_0.snap create mode 100644 tests/core/snapshots/apollo-federation-entities-batch.md_client.snap create mode 100644 tests/core/snapshots/apollo-federation-entities-batch.md_merged.snap create mode 100644 tests/core/snapshots/apollo-federation-entities.md_0.snap create mode 100644 tests/core/snapshots/apollo-federation-entities.md_client.snap create mode 100644 tests/core/snapshots/apollo-federation-entities.md_merged.snap create mode 100644 tests/execution/apollo-federation-entities-batch.md create mode 100644 tests/execution/apollo-federation-entities.md diff --git a/examples/apollo_federation_entities.graphql b/examples/apollo_federation_entities.graphql new file mode 100644 index 0000000000..7f82825983 --- /dev/null +++ b/examples/apollo_federation_entities.graphql @@ -0,0 +1,26 @@ +schema + @server(port: 8000) + @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) { + query: Query +} + +type Query { + user(id: Int!): User @http(path: "/users/{{.args.id}}") +} + +type User @http(path: "/users", query: [{key: "id", value: "{{.value.id}}"}], batchKey: ["id"]) { + id: Int! + name: String! + username: String! + email: String! + phone: String + website: String +} + +type Post @http(path: "/posts", query: [{key: "id", value: "{{.value.id}}"}], batchKey: ["id"]) { + id: Int! + userId: Int! + title: String! + body: String! + user: User @call(steps: [{query: "user", args: {id: "{{.value.userId}}"}}]) +} diff --git a/generated/.tailcallrc.schema.json b/generated/.tailcallrc.schema.json index 468b392e0e..3e9a9554a6 100644 --- a/generated/.tailcallrc.schema.json +++ b/generated/.tailcallrc.schema.json @@ -336,6 +336,20 @@ "ApplicationXWwwFormUrlencoded" ] }, + "EntityResolver": { + "type": "object", + "required": [ + "resolver_by_type" + ], + "properties": { + "resolver_by_type": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Resolver" + } + } + } + }, "Enum": { "description": "Definition of GraphQL enum type", "type": "object", @@ -444,6 +458,18 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "entityResolver" + ], + "properties": { + "entityResolver": { + "$ref": "#/definitions/EntityResolver" + } + }, + "additionalProperties": false } ], "properties": { @@ -940,6 +966,94 @@ } } }, + "Resolver": { + "oneOf": [ + { + "type": "object", + "required": [ + "http" + ], + "properties": { + "http": { + "$ref": "#/definitions/Http" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "grpc" + ], + "properties": { + "grpc": { + "$ref": "#/definitions/Grpc" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "graphql" + ], + "properties": { + "graphql": { + "$ref": "#/definitions/GraphQL" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "call" + ], + "properties": { + "call": { + "$ref": "#/definitions/Call" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "js" + ], + "properties": { + "js": { + "$ref": "#/definitions/JS" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "expr" + ], + "properties": { + "expr": { + "$ref": "#/definitions/Expr" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "entityResolver" + ], + "properties": { + "entityResolver": { + "$ref": "#/definitions/EntityResolver" + } + }, + "additionalProperties": false + } + ] + }, "RootSchema": { "type": "object", "properties": { @@ -1232,6 +1346,92 @@ "Type": { "description": "Represents a GraphQL type. A type can be an object, interface, enum or scalar.", "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "http" + ], + "properties": { + "http": { + "$ref": "#/definitions/Http" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "grpc" + ], + "properties": { + "grpc": { + "$ref": "#/definitions/Grpc" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "graphql" + ], + "properties": { + "graphql": { + "$ref": "#/definitions/GraphQL" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "call" + ], + "properties": { + "call": { + "$ref": "#/definitions/Call" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "js" + ], + "properties": { + "js": { + "$ref": "#/definitions/JS" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "expr" + ], + "properties": { + "expr": { + "$ref": "#/definitions/Expr" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "entityResolver" + ], + "properties": { + "entityResolver": { + "$ref": "#/definitions/EntityResolver" + } + }, + "additionalProperties": false + } + ], "required": [ "fields" ], diff --git a/src/core/app_context.rs b/src/core/app_context.rs index 1f6516be36..fcc628a598 100644 --- a/src/core/app_context.rs +++ b/src/core/app_context.rs @@ -6,7 +6,6 @@ use hyper::body::Bytes; use crate::core::async_graphql_hyper::OperationId; use crate::core::auth::context::GlobalAuthContext; -use crate::core::blueprint::Type::ListType; use crate::core::blueprint::{Blueprint, Definition, SchemaModifiers}; use crate::core::data_loader::{DataLoader, DedupeResult}; use crate::core::graphql::GraphqlDataLoader; @@ -44,16 +43,18 @@ impl AppContext { for def in blueprint.definitions.iter_mut() { if let Definition::Object(def) = def { for field in &mut def.fields { - let of_type = field.of_type.clone(); let upstream_batch = &blueprint.upstream.batch; field.map_expr(|expr| { - expr.modify(|expr| match expr { + expr.modify(&mut |expr| match expr { IR::IO(io) => match io { - IO::Http { req_template, group_by, http_filter, .. } => { + IO::Http { + req_template, group_by, http_filter, is_list, .. + } => { + let is_list = *is_list; let data_loader = HttpDataLoader::new( runtime.clone(), group_by.clone(), - matches!(of_type, ListType { .. }), + is_list, ) .to_data_loader(upstream_batch.clone().unwrap_or_default()); @@ -62,6 +63,7 @@ impl AppContext { group_by: group_by.clone(), dl_id: Some(DataLoaderId::new(http_data_loaders.len())), http_filter: http_filter.clone(), + is_list, })); http_data_loaders.push(data_loader); diff --git a/src/core/blueprint/definitions.rs b/src/core/blueprint/definitions.rs index d8df056f1f..b15e56e7c3 100644 --- a/src/core/blueprint/definitions.rs +++ b/src/core/blueprint/definitions.rs @@ -487,6 +487,7 @@ pub fn to_field_definition( name: &String, ) -> Valid { update_args() + .and(update_entity_resolver(operation_type, object_name).trace("EntityResolver")) .and(update_http().trace(config::Http::trace_name().as_str())) .and(update_grpc(operation_type).trace(config::Grpc::trace_name().as_str())) .and(update_const_field().trace(config::Expr::trace_name().as_str())) diff --git a/src/core/blueprint/operators/call.rs b/src/core/blueprint/operators/call.rs index 5ea3cf31f3..2eeded562f 100644 --- a/src/core/blueprint/operators/call.rs +++ b/src/core/blueprint/operators/call.rs @@ -13,23 +13,23 @@ pub fn update_call<'a>( ) -> TryFold<'a, (&'a ConfigModule, &'a Field, &'a config::Type, &'a str), FieldDefinition, String> { TryFold::<(&ConfigModule, &Field, &config::Type, &str), FieldDefinition, String>::new( - move |(config, field, _, name), b_field| { + move |(config, field, _, _), b_field| { let Some(Resolver::Call(call)) = &field.resolver else { return Valid::succeed(b_field); }; compile_call(config, call, operation_type, object_name) - .map(|field| b_field.resolver(field.resolver).name(name.to_string())) + .map(|resolver| b_field.resolver(Some(resolver))) }, ) } -fn compile_call( +pub fn compile_call( config_module: &ConfigModule, call: &config::Call, operation_type: &GraphQLOperationType, object_name: &str, -) -> Valid { +) -> Valid { Valid::from_iter(call.steps.iter(), |step| { get_field_and_field_name(step, config_module).and_then(|(field, field_name, type_of)| { let args = step.args.iter(); @@ -92,8 +92,6 @@ fn compile_call( .and_then(|b_fields| { Valid::from_option( b_fields.into_iter().reduce(|mut b_field, b_field_next| { - b_field.name = b_field_next.name; - b_field.of_type = b_field_next.of_type; b_field.map_expr(|expr| { b_field_next .resolver @@ -107,9 +105,14 @@ fn compile_call( "Steps can't be empty".to_string(), ) }) + .and_then(|field| { + Valid::from_option(field.resolver, "Result resolver can't be empty".to_string()) + }) } fn get_type_and_field(call: &config::Step) -> Option<(String, String)> { + // TODO: type names for query and mutations should be inferred from the + // config_module and should not be static values if let Some(query) = &call.query { Some(("Query".to_string(), query.clone())) } else { diff --git a/src/core/blueprint/operators/entity_resolver.rs b/src/core/blueprint/operators/entity_resolver.rs new file mode 100644 index 0000000000..f5ee67889f --- /dev/null +++ b/src/core/blueprint/operators/entity_resolver.rs @@ -0,0 +1,95 @@ +use std::collections::HashMap; + +use super::{compile_call, compile_expr, compile_graphql, compile_grpc, compile_http, compile_js}; +use crate::core::blueprint::FieldDefinition; +use crate::core::config::{ + ConfigModule, EntityResolver, Field, GraphQLOperationType, Resolver, Type, +}; +use crate::core::ir::model::IR; +use crate::core::try_fold::TryFold; +use crate::core::valid::{Valid, Validator}; + +pub struct CompileEntityResolver<'a> { + config_module: &'a ConfigModule, + field: &'a Field, + entity_resolver: &'a EntityResolver, + operation_type: &'a GraphQLOperationType, + object_name: &'a str, +} + +pub fn compile_entity_resolver(inputs: CompileEntityResolver<'_>) -> Valid { + let CompileEntityResolver { + config_module, + field, + entity_resolver, + operation_type, + object_name, + } = inputs; + let mut resolver_by_type = HashMap::new(); + + Valid::from_iter( + entity_resolver.resolver_by_type.iter(), + |(type_name, resolver)| { + // TODO: make this code reusable in other operators like call + let ir = match resolver { + // TODO: there are `validate_field` for field, but not for types + // implement validation as shared entity and use it for types + Resolver::Http(http) => compile_http( + config_module, + http, + // inner resolver should resolve only single instance of type, not a list + false, + ), + Resolver::Grpc(grpc) => compile_grpc(super::CompileGrpc { + config_module, + operation_type, + field, + grpc, + validate_with_schema: true, + }), + Resolver::Graphql(graphql) => { + compile_graphql(config_module, operation_type, type_name, graphql) + } + Resolver::Call(call) => { + compile_call(config_module, call, operation_type, object_name) + } + Resolver::Js(js) => { + compile_js(super::CompileJs { js, script: &config_module.extensions().script }) + } + Resolver::Expr(expr) => { + compile_expr(super::CompileExpr { config_module, field, expr, validate: true }) + } + Resolver::EntityResolver(entity_resolver) => { + compile_entity_resolver(CompileEntityResolver { entity_resolver, ..inputs }) + } + }; + + ir.map(|ir| { + resolver_by_type.insert(type_name.to_owned(), ir); + }) + }, + ) + .map_to(IR::EntityResolver(resolver_by_type)) +} + +pub fn update_entity_resolver<'a>( + operation_type: &'a GraphQLOperationType, + object_name: &'a str, +) -> TryFold<'a, (&'a ConfigModule, &'a Field, &'a Type, &'a str), FieldDefinition, String> { + TryFold::<(&ConfigModule, &Field, &Type, &'a str), FieldDefinition, String>::new( + |(config_module, field, _, _), b_field| { + let Some(Resolver::EntityResolver(entity_resolver)) = &field.resolver else { + return Valid::succeed(b_field); + }; + + compile_entity_resolver(CompileEntityResolver { + config_module, + field, + entity_resolver, + operation_type, + object_name, + }) + .map(|resolver| b_field.resolver(Some(resolver))) + }, + ) +} diff --git a/src/core/blueprint/operators/expr.rs b/src/core/blueprint/operators/expr.rs index a429bf9e3b..8f226b77fe 100644 --- a/src/core/blueprint/operators/expr.rs +++ b/src/core/blueprint/operators/expr.rs @@ -2,7 +2,7 @@ use async_graphql_value::ConstValue; use crate::core::blueprint::*; use crate::core::config; -use crate::core::config::{Field, Resolver}; +use crate::core::config::{Expr, Field, Resolver}; use crate::core::ir::model::IR; use crate::core::ir::model::IR::Dynamic; use crate::core::try_fold::TryFold; @@ -25,14 +25,14 @@ fn validate_data_with_schema( pub struct CompileExpr<'a> { pub config_module: &'a config::ConfigModule, pub field: &'a config::Field, - pub value: &'a serde_json::Value, + pub expr: &'a Expr, pub validate: bool, } pub fn compile_expr(inputs: CompileExpr) -> Valid { let config_module = inputs.config_module; let field = inputs.field; - let value = inputs.value; + let value = &inputs.expr.body; let validate = inputs.validate; Valid::from( @@ -68,7 +68,7 @@ pub fn update_const_field<'a>( return Valid::succeed(b_field); }; - compile_expr(CompileExpr { config_module, field, value: &expr.body, validate: true }) + compile_expr(CompileExpr { config_module, field, expr, validate: true }) .map(|resolver| b_field.resolver(Some(resolver))) }, ) diff --git a/src/core/blueprint/operators/graphql.rs b/src/core/blueprint/operators/graphql.rs index bf5f42e9c4..25e0d33e53 100644 --- a/src/core/blueprint/operators/graphql.rs +++ b/src/core/blueprint/operators/graphql.rs @@ -41,7 +41,7 @@ fn create_related_fields( } pub fn compile_graphql( - config: &Config, + config: &ConfigModule, operation_type: &GraphQLOperationType, type_name: &str, graphql: &GraphQL, diff --git a/src/core/blueprint/operators/http.rs b/src/core/blueprint/operators/http.rs index 71e8f28e57..1bf447a264 100644 --- a/src/core/blueprint/operators/http.rs +++ b/src/core/blueprint/operators/http.rs @@ -11,6 +11,7 @@ use crate::core::{config, helpers, Mustache}; pub fn compile_http( config_module: &config::ConfigModule, http: &config::Http, + is_list: bool, ) -> Valid { Valid::<(), String>::fail("GroupBy is only supported for GET requests".to_string()) .when(|| !http.batch_key.is_empty() && http.method != Method::GET) @@ -81,9 +82,16 @@ pub fn compile_http( group_by: Some(GroupBy::new(http.batch_key.clone(), key)), dl_id: None, http_filter, + is_list, }) } else { - IR::IO(IO::Http { req_template, group_by: None, dl_id: None, http_filter }) + IR::IO(IO::Http { + req_template, + group_by: None, + dl_id: None, + http_filter, + is_list, + }) } }) } @@ -97,7 +105,7 @@ pub fn update_http<'a>( return Valid::succeed(b_field); }; - compile_http(config_module, http) + compile_http(config_module, http, field.list) .map(|resolver| b_field.resolver(Some(resolver))) .and_then(|b_field| { b_field diff --git a/src/core/blueprint/operators/js.rs b/src/core/blueprint/operators/js.rs index cc6b212c60..cf61d20e19 100644 --- a/src/core/blueprint/operators/js.rs +++ b/src/core/blueprint/operators/js.rs @@ -1,17 +1,17 @@ use crate::core::blueprint::FieldDefinition; use crate::core::config; -use crate::core::config::{ConfigModule, Field, Resolver}; +use crate::core::config::{ConfigModule, Field, Resolver, JS}; use crate::core::ir::model::{IO, IR}; use crate::core::try_fold::TryFold; use crate::core::valid::{Valid, Validator}; pub struct CompileJs<'a> { - pub name: &'a str, + pub js: &'a JS, pub script: &'a Option, } pub fn compile_js(inputs: CompileJs) -> Valid { - let name = inputs.name; + let name = &inputs.js.name; Valid::from_option(inputs.script.as_ref(), "script is required".to_string()) .map(|_| IR::IO(IO::Js { name: name.to_string() })) } @@ -25,7 +25,7 @@ pub fn update_js_field<'a>( return Valid::succeed(b_field); }; - compile_js(CompileJs { script: &module.extensions().script, name: &js.name }) + compile_js(CompileJs { script: &module.extensions().script, js }) .map(|resolver| b_field.resolver(Some(resolver))) }, ) diff --git a/src/core/blueprint/operators/mod.rs b/src/core/blueprint/operators/mod.rs index 7ca9bf0e1a..6e6964fba1 100644 --- a/src/core/blueprint/operators/mod.rs +++ b/src/core/blueprint/operators/mod.rs @@ -1,4 +1,5 @@ mod call; +mod entity_resolver; mod enum_alias; mod expr; mod graphql; @@ -9,6 +10,7 @@ mod modify; mod protected; pub use call::*; +pub use entity_resolver::*; pub use enum_alias::*; pub use expr::*; pub use graphql::*; diff --git a/src/core/config/config.rs b/src/core/config/config.rs index 8c25dc08be..0fb8375814 100644 --- a/src/core/config/config.rs +++ b/src/core/config/config.rs @@ -115,6 +115,11 @@ pub struct Type { /// Marks field as protected by auth providers #[serde(default)] pub protected: Option, + + /// + /// Apollo federation entity resolver. + #[serde(flatten, default, skip_serializing_if = "is_default")] + pub resolver: Option, } impl Display for Type { @@ -215,7 +220,7 @@ pub struct Omit {} // TODO: replace with derive macro macro_rules! create_resolver { ($($var:ident($ty:ty)),+$(,)?) => { - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema)] + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema, MergeRight)] #[serde(rename_all = "camelCase")] pub enum Resolver { // just specify the same variants @@ -263,18 +268,20 @@ create_resolver! { Call(Call), Js(JS), Expr(Expr), + EntityResolver(EntityResolver), } impl Resolver { - pub fn to_directive(&self) -> ConstDirective { - match self { + pub fn to_directive(&self) -> Option { + Some(match self { Resolver::Http(d) => d.to_directive(), Resolver::Grpc(d) => d.to_directive(), Resolver::Graphql(d) => d.to_directive(), Resolver::Call(d) => d.to_directive(), Resolver::Js(d) => d.to_directive(), Resolver::Expr(d) => d.to_directive(), - } + Resolver::EntityResolver(_) => return None, + }) } pub fn directive_name(&self) -> String { @@ -285,6 +292,22 @@ impl Resolver { Resolver::Call(_) => Call::directive_name(), Resolver::Js(_) => JS::directive_name(), Resolver::Expr(_) => Expr::directive_name(), + Resolver::EntityResolver(_) => EntityResolver::directive_name(), + } + } + + pub fn is_batched(&self) -> bool { + match self { + Resolver::Http(http) => !http.batch_key.is_empty(), + Resolver::Grpc(grpc) => !grpc.batch_key.is_empty(), + Resolver::Graphql(graphql) => graphql.batch, + Resolver::Call(_) => false, + Resolver::Js(_) => false, + Resolver::Expr(_) => false, + Resolver::EntityResolver(entity_resolver) => entity_resolver + .resolver_by_type + .values() + .any(Resolver::is_batched), } } } @@ -368,19 +391,12 @@ impl Field { pub fn has_resolver(&self) -> bool { self.resolver.is_some() } + pub fn has_batched_resolver(&self) -> bool { - if let Some(resolver) = &self.resolver { - match resolver { - Resolver::Http(http) => !http.batch_key.is_empty(), - Resolver::Grpc(grpc) => !grpc.batch_key.is_empty(), - Resolver::Graphql(graphql) => graphql.batch, - Resolver::Call(_) => false, - Resolver::Js(_) => false, - Resolver::Expr(_) => false, - } - } else { - false - } + self.resolver + .as_ref() + .map(Resolver::is_batched) + .unwrap_or(false) } pub fn into_list(mut self) -> Self { self.list = true; @@ -635,6 +651,11 @@ pub struct Call { pub steps: Vec, } +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] +pub struct EntityResolver { + pub resolver_by_type: BTreeMap, +} + /// /// Provides the ability to refer to a field defined in the root Query or /// Mutation. diff --git a/src/core/config/from_document.rs b/src/core/config/from_document.rs index da6a94847e..24ffb593a2 100644 --- a/src/core/config/from_document.rs +++ b/src/core/config/from_document.rs @@ -10,7 +10,7 @@ use async_graphql::Name; use async_graphql_value::ConstValue; use super::telemetry::Telemetry; -use super::Alias; +use super::{Alias, Resolver}; use crate::core::config::{ self, Cache, Config, Enum, Link, Modify, Omit, Protected, RootSchema, Server, Union, Upstream, Variant, @@ -241,14 +241,23 @@ where let fields = object.fields(); let implements = object.implements(); - Cache::from_directives(directives.iter()) + Resolver::from_directives(directives) + .fuse(Cache::from_directives(directives.iter())) .fuse(to_fields(fields)) .fuse(Protected::from_directives(directives.iter())) - .map(|(cache, fields, protected)| { + .map(|(resolver, cache, fields, protected)| { let doc = description.to_owned().map(|pos| pos.node); let implements = implements.iter().map(|pos| pos.node.to_string()).collect(); let added_fields = to_add_fields_from_directives(directives); - config::Type { fields, added_fields, doc, implements, cache, protected } + config::Type { + fields, + added_fields, + doc, + implements, + cache, + protected, + resolver, + } }) } fn to_input_object( diff --git a/src/core/config/into_document.rs b/src/core/config/into_document.rs index a33fe457f3..4183ff92d0 100644 --- a/src/core/config/into_document.rs +++ b/src/core/config/into_document.rs @@ -265,7 +265,11 @@ fn config_document(config: &Config) -> ServiceDocument { fn get_directives(field: &crate::core::config::Field) -> Vec> { let directives = vec![ - field.resolver.as_ref().map(|d| pos(d.to_directive())), + field + .resolver + .as_ref() + .and_then(|d| d.to_directive()) + .map(pos), field.modify.as_ref().map(|d| pos(d.to_directive())), field.omit.as_ref().map(|d| pos(d.to_directive())), field.cache.as_ref().map(|d| pos(d.to_directive())), diff --git a/src/core/config/transformer/apollo_federation/entities.rs b/src/core/config/transformer/apollo_federation/entities.rs new file mode 100644 index 0000000000..8cdb101736 --- /dev/null +++ b/src/core/config/transformer/apollo_federation/entities.rs @@ -0,0 +1,80 @@ +use std::collections::BTreeMap; + +use crate::core::config::{config, Arg, Config, Field, Resolver, Type, Union}; +use crate::core::valid::Valid; +use crate::core::Transform; + +const ROOT_FIELD_NAME: &str = "_entities"; +const UNION_ENTITIES_NAME: &str = "_Entity"; +const ARG_NAME: &str = "representations"; +const ARG_TYPE_NAME: &str = "_Any"; + +pub struct EntityResolver; + +impl Transform for EntityResolver { + type Value = Config; + + type Error = String; + + fn transform(&self, mut config: Self::Value) -> Valid { + let mut resolver_by_type = BTreeMap::new(); + + for (type_name, ty) in &config.types { + if let Some(resolver) = &ty.resolver { + resolver_by_type.insert(type_name.clone(), resolver.clone()); + } + } + + if resolver_by_type.is_empty() { + return Valid::succeed(config); + } + + let entity_union = Union { + types: resolver_by_type.keys().cloned().collect(), + ..Default::default() + }; + + // union that wraps any possible types for entities + config + .unions + .insert(UNION_ENTITIES_NAME.to_owned(), entity_union); + // any scalar for argument `representations` + config + .types + .insert(ARG_TYPE_NAME.to_owned(), Type::default()); + + let entity_resolver = config::EntityResolver { resolver_by_type }; + + let query_type = match config.schema.query.as_ref() { + Some(name) => name, + None => { + config.schema.query = Some("Query".to_string()); + "Query" + } + }; + + let query_type = config.types.entry(query_type.to_owned()).or_default(); + + let arg = Arg { + type_of: ARG_TYPE_NAME.to_string(), + list: true, + required: true, + ..Default::default() + }; + + query_type.fields.insert( + ROOT_FIELD_NAME.to_string(), + Field { + type_of: UNION_ENTITIES_NAME.to_owned(), + list: true, + required: true, + args: [(ARG_NAME.to_owned(), arg)].into_iter().collect(), + doc: Some("Apollo federation Query._entities resolver".to_string()), + resolver: Some(Resolver::EntityResolver(entity_resolver)), + ..Default::default() + }, + ); + + Valid::succeed(config) + } +} diff --git a/src/core/config/transformer/apollo_federation/mod.rs b/src/core/config/transformer/apollo_federation/mod.rs new file mode 100644 index 0000000000..0b8f0b5a5a --- /dev/null +++ b/src/core/config/transformer/apollo_federation/mod.rs @@ -0,0 +1 @@ +pub mod entities; diff --git a/src/core/config/transformer/mod.rs b/src/core/config/transformer/mod.rs index bf22d6730c..e6f2cc6593 100644 --- a/src/core/config/transformer/mod.rs +++ b/src/core/config/transformer/mod.rs @@ -1,4 +1,5 @@ mod ambiguous_type; +mod apollo_federation; mod consolidate_url; mod flatten_single_field; mod improve_type_names; @@ -11,6 +12,7 @@ mod tree_shake; mod union_input_type; pub use ambiguous_type::{AmbiguousType, Resolution}; +pub use apollo_federation::entities::EntityResolver; pub use consolidate_url::ConsolidateURL; pub use flatten_single_field::FlattenSingleField; pub use improve_type_names::ImproveTypeNames; diff --git a/src/core/config/transformer/required.rs b/src/core/config/transformer/required.rs index 4d38070a5c..bc5f910483 100644 --- a/src/core/config/transformer/required.rs +++ b/src/core/config/transformer/required.rs @@ -15,6 +15,7 @@ impl Transform for Required { config: Self::Value, ) -> crate::core::valid::Valid { transform::default() + .pipe(super::EntityResolver) .pipe(super::NestedUnions) .pipe(super::UnionInputType) .pipe(super::AmbiguousType::default()) diff --git a/src/core/ir/error.rs b/src/core/ir/error.rs index e08523bb71..3a55b77c31 100644 --- a/src/core/ir/error.rs +++ b/src/core/ir/error.rs @@ -5,6 +5,7 @@ use derive_more::From; use thiserror::Error; use crate::core::{auth, cache, worker}; + #[derive(From, Debug, Error, Clone)] pub enum Error { #[error("IOException: {0}")] @@ -37,6 +38,10 @@ pub enum Error { #[error("Cache Error: {0}")] CacheError(cache::Error), + + #[error("Entity Resolver Error: {0}")] + #[from(ignore)] + EntityResolverError(String), } impl ErrorExtensions for Error { diff --git a/src/core/ir/eval.rs b/src/core/ir/eval.rs index 595cb57dbd..ec308088f8 100644 --- a/src/core/ir/eval.rs +++ b/src/core/ir/eval.rs @@ -2,6 +2,7 @@ use std::future::Future; use std::ops::Deref; use async_graphql_value::ConstValue; +use futures_util::future::join_all; use super::eval_io::eval_io; use super::model::{Cache, CacheKey, Map, IR}; @@ -89,12 +90,52 @@ impl IR { second.eval(ctx).await } IR::Discriminate(discriminator, expr) => expr.eval(ctx).await.and_then(|value| { + // TODO: in some cases __typename could be already present or could be inferred + // by some way like for EntityResolver let type_name = discriminator.resolve_type(&value)?; ctx.set_type_name(type_name); Ok(value) }), + IR::EntityResolver(map) => { + let representations = ctx.path_arg(&["representations"]); + + let representations = representations + .as_ref() + .and_then(|repr| repr.as_array()) + .ok_or(Error::EntityResolverError( + "expected `representations` arg as an array of _Any".to_string(), + ))?; + + let mut tasks = Vec::with_capacity(representations.len()); + + for repr in representations { + // TODO: combine errors, instead of fail fast? + let typename = repr.get_key("__typename").and_then(|t| t.as_str()).ok_or( + Error::EntityResolverError( + "expected __typename to be the part of the representation" + .to_string(), + ), + )?; + + let ir = map.get(typename).ok_or(Error::EntityResolverError(format!( + "Cannot find a resolver for type: `{typename}`" + )))?; + + // pass the input for current representation as value in context + // TODO: can we drop clone? + let mut ctx = ctx.with_value(repr.clone()); + + tasks.push(async move { ir.eval(&mut ctx).await }); + } + + let result = join_all(tasks).await; + + let entities = result.into_iter().collect::>()?; + + Ok(ConstValue::List(entities)) + } } }) } diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index 89d38ee317..6693480a31 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -26,6 +26,7 @@ pub enum IR { Map(Map), Pipe(Box, Box), Discriminate(Discriminator, Box), + EntityResolver(HashMap), } #[derive(Clone, Debug)] @@ -42,6 +43,7 @@ pub enum IO { group_by: Option, dl_id: Option, http_filter: Option, + is_list: bool, }, GraphQL { req_template: graphql::RequestTemplate, @@ -101,7 +103,7 @@ impl Cache { /// Performance DFS on the cache on the expression and identifies all the IO /// nodes. Then wraps each IO node with the cache primitive. pub fn wrap(max_age: NonZeroU64, expr: IR) -> IR { - expr.modify(move |expr| match expr { + expr.modify(&mut move |expr| match expr { IR::IO(io) => Some(IR::Cache(Cache { max_age, io: Box::new(io.to_owned()) })), _ => None, }) @@ -113,8 +115,8 @@ impl IR { IR::Pipe(Box::new(self), Box::new(next)) } - pub fn modify(self, mut f: impl FnMut(&IR) -> Option) -> IR { - self.modify_inner(&mut f) + pub fn modify Option>(self, modifier: &mut F) -> IR { + self.modify_inner(modifier) } fn modify_box Option>(self, modifier: &mut F) -> Box { @@ -149,6 +151,11 @@ impl IR { IR::Discriminate(discriminator, expr) => { IR::Discriminate(discriminator, expr.modify_box(modifier)) } + IR::EntityResolver(map) => IR::EntityResolver( + map.into_iter() + .map(|(k, v)| (k, v.modify(modifier))) + .collect(), + ), } } } diff --git a/tests/core/snapshots/apollo-federation-entities-batch.md_0.snap b/tests/core/snapshots/apollo-federation-entities-batch.md_0.snap new file mode 100644 index 0000000000..278e3447b2 --- /dev/null +++ b/tests/core/snapshots/apollo-federation-entities-batch.md_0.snap @@ -0,0 +1,36 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "_entities": [ + { + "__typename": "User", + "id": 1, + "name": "Leanne Graham" + }, + { + "__typename": "User", + "id": 2, + "name": "Ervin Howell" + }, + { + "__typename": "Post", + "id": 3, + "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut" + }, + { + "__typename": "Post", + "id": 5, + "title": "nesciunt quas odio" + } + ] + } + } +} diff --git a/tests/core/snapshots/apollo-federation-entities-batch.md_client.snap b/tests/core/snapshots/apollo-federation-entities-batch.md_client.snap new file mode 100644 index 0000000000..bf8395d5a1 --- /dev/null +++ b/tests/core/snapshots/apollo-federation-entities-batch.md_client.snap @@ -0,0 +1,65 @@ +--- +source: tests/core/spec.rs +expression: formatted +--- +scalar Bytes + +scalar Date + +scalar DateTime + +scalar Email + +scalar Empty + +scalar Int128 + +scalar Int16 + +scalar Int32 + +scalar Int64 + +scalar Int8 + +scalar JSON + +scalar PhoneNumber + +type Post { + id: Int! + title: String! +} + +type Query { + """ + Apollo federation Query._entities resolver + """ + _entities(representations: [_Any]!): [_Entity]! + user(id: Int!): User +} + +scalar UInt128 + +scalar UInt16 + +scalar UInt32 + +scalar UInt64 + +scalar UInt8 + +scalar Url + +type User { + id: Int! + name: String! +} + +scalar _Any + +union _Entity = Post | User + +schema { + query: Query +} diff --git a/tests/core/snapshots/apollo-federation-entities-batch.md_merged.snap b/tests/core/snapshots/apollo-federation-entities-batch.md_merged.snap new file mode 100644 index 0000000000..1ca73a2540 --- /dev/null +++ b/tests/core/snapshots/apollo-federation-entities-batch.md_merged.snap @@ -0,0 +1,31 @@ +--- +source: tests/core/spec.rs +expression: formatter +--- +schema + @server(port: 8000) + @upstream(baseURL: "http://jsonplaceholder.typicode.com", batch: {delay: 100, headers: []}, httpCache: 42) { + query: Query +} + +scalar _Any + +union _Entity = Post | User + +type Post { + id: Int! + title: String! +} + +type Query { + """ + Apollo federation Query._entities resolver + """ + _entities(representations: [_Any]!): [_Entity]! + user(id: Int!): User @http(path: "/users/{{.args.id}}") +} + +type User { + id: Int! + name: String! +} diff --git a/tests/core/snapshots/apollo-federation-entities.md_0.snap b/tests/core/snapshots/apollo-federation-entities.md_0.snap new file mode 100644 index 0000000000..071a6837f3 --- /dev/null +++ b/tests/core/snapshots/apollo-federation-entities.md_0.snap @@ -0,0 +1,36 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "_entities": [ + { + "__typename": "User", + "id": 1, + "name": "Leanne Graham" + }, + { + "__typename": "User", + "id": 2, + "name": "Ervin Howell" + }, + { + "__typename": "Post", + "id": 3, + "title": "post-title-3" + }, + { + "__typename": "Post", + "id": 5, + "title": "post-title-5" + } + ] + } + } +} diff --git a/tests/core/snapshots/apollo-federation-entities.md_client.snap b/tests/core/snapshots/apollo-federation-entities.md_client.snap new file mode 100644 index 0000000000..bf8395d5a1 --- /dev/null +++ b/tests/core/snapshots/apollo-federation-entities.md_client.snap @@ -0,0 +1,65 @@ +--- +source: tests/core/spec.rs +expression: formatted +--- +scalar Bytes + +scalar Date + +scalar DateTime + +scalar Email + +scalar Empty + +scalar Int128 + +scalar Int16 + +scalar Int32 + +scalar Int64 + +scalar Int8 + +scalar JSON + +scalar PhoneNumber + +type Post { + id: Int! + title: String! +} + +type Query { + """ + Apollo federation Query._entities resolver + """ + _entities(representations: [_Any]!): [_Entity]! + user(id: Int!): User +} + +scalar UInt128 + +scalar UInt16 + +scalar UInt32 + +scalar UInt64 + +scalar UInt8 + +scalar Url + +type User { + id: Int! + name: String! +} + +scalar _Any + +union _Entity = Post | User + +schema { + query: Query +} diff --git a/tests/core/snapshots/apollo-federation-entities.md_merged.snap b/tests/core/snapshots/apollo-federation-entities.md_merged.snap new file mode 100644 index 0000000000..1ca73a2540 --- /dev/null +++ b/tests/core/snapshots/apollo-federation-entities.md_merged.snap @@ -0,0 +1,31 @@ +--- +source: tests/core/spec.rs +expression: formatter +--- +schema + @server(port: 8000) + @upstream(baseURL: "http://jsonplaceholder.typicode.com", batch: {delay: 100, headers: []}, httpCache: 42) { + query: Query +} + +scalar _Any + +union _Entity = Post | User + +type Post { + id: Int! + title: String! +} + +type Query { + """ + Apollo federation Query._entities resolver + """ + _entities(representations: [_Any]!): [_Entity]! + user(id: Int!): User @http(path: "/users/{{.args.id}}") +} + +type User { + id: Int! + name: String! +} diff --git a/tests/execution/apollo-federation-entities-batch.md b/tests/execution/apollo-federation-entities-batch.md new file mode 100644 index 0000000000..800927ec4a --- /dev/null +++ b/tests/execution/apollo-federation-entities-batch.md @@ -0,0 +1,109 @@ +# Apollo federation \_entities query + +```graphql @config +schema + @server(port: 8000) + @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) { + query: Query +} + +type Query { + user(id: Int!): User @http(path: "/users/{{.args.id}}") +} + +type User @http(path: "/users", query: [{key: "id", value: "{{.value.id}}"}], batchKey: ["id"]) { + id: Int! + name: String! +} + +type Post + @graphQL(baseURL: "http://upstream/graphql", batch: true, name: "post", args: [{key: "id", value: "{{.value.id}}"}]) { + id: Int! + title: String! +} +``` + +```yml @mock +- request: + method: GET + url: http://jsonplaceholder.typicode.com/users?id=1&id=2 + assertHits: false + response: + status: 200 + body: + - id: 1 + name: Leanne Graham + - id: 2 + name: Ervin Howell + +- request: + method: GET + url: http://jsonplaceholder.typicode.com/users?id=2&id=1 + assertHits: false + response: + status: 200 + body: + - id: 2 + name: Ervin Howell + - id: 1 + name: Leanne Graham + +- request: + method: POST + url: http://upstream/graphql + textBody: '[{ "query": "query { post(id: 3) { id id title } }" },{ "query": "query { post(id: 5) { id id title } }" }]' + assertHits: false + response: + status: 200 + body: + - data: + post: + id: 3 + title: ea molestias quasi exercitationem repellat qui ipsa sit aut + - data: + post: + id: 5 + title: nesciunt quas odio + +- request: + method: POST + url: http://upstream/graphql + textBody: '[{ "query": "query { post(id: 5) { id id title } }" },{ "query": "query { post(id: 3) { id id title } }" }]' + assertHits: false + response: + status: 200 + body: + - data: + post: + id: 5 + title: nesciunt quas odio + - data: + post: + id: 3 + title: ea molestias quasi exercitationem repellat qui ipsa sit aut +``` + +```yml @test +- method: POST + url: http://localhost:8080/graphql + body: + query: > + { + _entities(representations: [ + {id: 1, __typename: "User"} + {id: 2, __typename: "User"} + {id: 3, __typename: "Post"} + {id: 5, __typename: "Post"} + ]) { + __typename + ...on User { + id + name + } + ...on Post { + id + title + } + } + } +``` diff --git a/tests/execution/apollo-federation-entities.md b/tests/execution/apollo-federation-entities.md new file mode 100644 index 0000000000..93b8388412 --- /dev/null +++ b/tests/execution/apollo-federation-entities.md @@ -0,0 +1,68 @@ +# Apollo federation \_entities query + +```graphql @config +schema + @server(port: 8000) + @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) { + query: Query +} + +type Query { + user(id: Int!): User @http(path: "/users/{{.args.id}}") +} + +type User @call(steps: [{query: "user", args: {id: "{{.value.id}}"}}]) { + id: Int! + name: String! +} + +type Post @expr(body: {id: "{{.value.id}}", title: "post-title-{{.value.id}}"}) { + id: Int! + title: String! +} +``` + +```yml @mock +- request: + method: GET + url: http://jsonplaceholder.typicode.com/users/1 + response: + status: 200 + body: + id: 1 + name: Leanne Graham + +- request: + method: GET + url: http://jsonplaceholder.typicode.com/users/2 + response: + status: 200 + body: + id: 2 + name: Ervin Howell +``` + +```yml @test +- method: POST + url: http://localhost:8080/graphql + body: + query: > + { + _entities(representations: [ + {id: 1, __typename: "User"} + {id: 2, __typename: "User"} + {id: 3, __typename: "Post"} + {id: 5, __typename: "Post"} + ]) { + __typename + ...on User { + id + name + } + ...on Post { + id + title + } + } + } +``` From 78605c91e55c750d96b253e2b21a67e9a386da5b Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Tue, 20 Aug 2024 10:01:52 +0000 Subject: [PATCH 03/17] move directives definitions to separate modules --- src/core/config/config.rs | 284 ++--------------------- src/core/config/directives/call.rs | 46 ++++ src/core/config/directives/expr.rs | 24 ++ src/core/config/directives/federation.rs | 19 ++ src/core/config/directives/graphql.rs | 51 ++++ src/core/config/directives/grpc.rs | 53 +++++ src/core/config/directives/http.rs | 94 ++++++++ src/core/config/directives/js.rs | 19 ++ src/core/config/directives/mod.rs | 15 ++ src/core/config/from_document.rs | 1 + 10 files changed, 336 insertions(+), 270 deletions(-) create mode 100644 src/core/config/directives/call.rs create mode 100644 src/core/config/directives/expr.rs create mode 100644 src/core/config/directives/federation.rs create mode 100644 src/core/config/directives/graphql.rs create mode 100644 src/core/config/directives/grpc.rs create mode 100644 src/core/config/directives/http.rs create mode 100644 src/core/config/directives/js.rs create mode 100644 src/core/config/directives/mod.rs diff --git a/src/core/config/config.rs b/src/core/config/config.rs index 469b210f73..e698da6432 100644 --- a/src/core/config/config.rs +++ b/src/core/config/config.rs @@ -3,8 +3,8 @@ use std::fmt::{self, Display}; use std::num::NonZeroU64; use anyhow::Result; -use async_graphql::parser::types::{ConstDirective, ServiceDocument}; use async_graphql::Positioned; +use async_graphql::parser::types::{ConstDirective, ServiceDocument}; use derive_setters::Setters; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -13,16 +13,14 @@ use tailcall_typedefs_common::directive_definition::DirectiveDefinition; use tailcall_typedefs_common::input_definition::InputDefinition; use tailcall_typedefs_common::ServiceDocumentBuilder; -use super::telemetry::Telemetry; -use super::{KeyValue, Link, Server, Upstream}; -use crate::core::config::from_document::from_document; +use super::{ + directives::{Call, EntityResolver, Expr, GraphQL, Grpc, Http, Key, JS}, from_document::from_document, telemetry::Telemetry +}; +use super::{Link, Server, Upstream}; use crate::core::config::npo::QueryPath; use crate::core::config::source::Source; -use crate::core::config::url_query::URLQuery; use crate::core::directive::DirectiveCodec; -use crate::core::http::Method; use crate::core::is_default; -use crate::core::json::JsonSchema; use crate::core::macros::MergeRight; use crate::core::merge_right::MergeRight; use crate::core::scalar::Scalar; @@ -115,10 +113,17 @@ pub struct Type { /// Marks field as protected by auth providers #[serde(default)] pub protected: Option, + /// /// Apollo federation entity resolver. #[serde(flatten, default, skip_serializing_if = "is_default")] pub resolver: Option, + + /// + /// Apollo federation key directive. + /// skip since it's set automatically by config transformer + #[serde(skip_serializing)] + pub key: Option, } impl Display for Type { @@ -233,6 +238,7 @@ pub enum Resolver { Call(Call), Js(JS), Expr(Expr), + #[serde(skip)] #[resolver(skip_to_directive)] EntityResolver(EntityResolver), } @@ -253,6 +259,7 @@ impl Resolver { } } } + /// /// A field definition containing all the metadata information about resolving a /// field. @@ -374,22 +381,6 @@ impl Field { } } -#[derive( - Serialize, - Deserialize, - Clone, - Debug, - PartialEq, - Eq, - schemars::JsonSchema, - DirectiveDefinition, - InputDefinition, -)] -#[directive_definition(locations = "FieldDefinition", lowercase_name)] -pub struct JS { - pub name: String, -} - #[derive( Serialize, Deserialize, @@ -484,233 +475,6 @@ pub struct Alias { pub options: BTreeSet, } -#[derive( - Serialize, - Deserialize, - Clone, - Debug, - Default, - PartialEq, - Eq, - schemars::JsonSchema, - DirectiveDefinition, - InputDefinition, -)] -#[directive_definition(locations = "FieldDefinition")] -#[serde(deny_unknown_fields)] -/// The @http operator indicates that a field or node is backed by a REST API. -/// -/// For instance, if you add the @http operator to the `users` field of the -/// Query type with a path argument of `"/users"`, it signifies that the `users` -/// field is backed by a REST API. The path argument specifies the path of the -/// REST API. In this scenario, the GraphQL server will make a GET request to -/// the API endpoint specified when the `users` field is queried. -pub struct Http { - #[serde(rename = "onRequest", default, skip_serializing_if = "is_default")] - /// onRequest field in @http directive gives the ability to specify the - /// request interception handler. - pub on_request: Option, - - #[serde(rename = "baseURL", default, skip_serializing_if = "is_default")] - /// This refers to the base URL of the API. If not specified, the default - /// base URL is the one specified in the `@upstream` operator. - pub base_url: Option, - - #[serde(default, skip_serializing_if = "is_default")] - /// The body of the API call. It's used for methods like POST or PUT that - /// send data to the server. You can pass it as a static object or use a - /// Mustache template to substitute variables from the GraphQL variables. - pub body: Option, - - #[serde(default, skip_serializing_if = "is_default")] - /// The `encoding` parameter specifies the encoding of the request body. It - /// can be `ApplicationJson` or `ApplicationXWwwFormUrlEncoded`. @default - /// `ApplicationJson`. - pub encoding: Encoding, - - #[serde(rename = "batchKey", default, skip_serializing_if = "is_default")] - /// The `batchKey` dictates the path Tailcall will follow to group the returned items from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching). - pub batch_key: Vec, - - #[serde(default, skip_serializing_if = "is_default")] - /// The `headers` parameter allows you to customize the headers of the HTTP - /// request made by the `@http` operator. It is used by specifying a - /// key-value map of header names and their values. - pub headers: Vec, - - #[serde(default, skip_serializing_if = "is_default")] - /// Schema of the input of the API call. It is automatically inferred in - /// most cases. - pub input: Option, - - #[serde(default, skip_serializing_if = "is_default")] - /// This refers to the HTTP method of the API call. Commonly used methods - /// include `GET`, `POST`, `PUT`, `DELETE` etc. @default `GET`. - pub method: Method, - - /// This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`. - /// - /// For dynamic segments in your API endpoint, use Mustache templates for - /// variable substitution. For instance, to fetch a specific user, use - /// `/users/{{args.id}}`. - pub path: String, - - #[serde(default, skip_serializing_if = "is_default")] - /// Schema of the output of the API call. It is automatically inferred in - /// most cases. - pub output: Option, - - #[serde(default, skip_serializing_if = "is_default")] - /// This represents the query parameters of your API call. You can pass it - /// as a static object or use Mustache template for dynamic parameters. - /// These parameters will be added to the URL. - /// NOTE: Query parameter order is critical for batching in Tailcall. The - /// first parameter referencing a field in the current value using mustache - /// syntax is automatically selected as the batching parameter. - pub query: Vec, -} - -/// -/// Provides the ability to refer to multiple fields in the Query or -/// Mutation root. -#[derive( - Serialize, - Deserialize, - Clone, - Debug, - Default, - PartialEq, - Eq, - schemars::JsonSchema, - DirectiveDefinition, -)] -#[directive_definition(locations = "FieldDefinition")] -pub struct Call { - /// Steps are composed together to form a call. - /// If you have multiple steps, the output of the previous step is passed as - /// input to the next step. - pub steps: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] -pub struct EntityResolver { - pub resolver_by_type: BTreeMap, -} - -/// -/// Provides the ability to refer to a field defined in the root Query or -/// Mutation. -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] -pub struct Step { - #[serde(default, skip_serializing_if = "is_default")] - /// The name of the field on the `Query` type that you want to call. - pub query: Option, - - #[serde(default, skip_serializing_if = "is_default")] - /// The name of the field on the `Mutation` type that you want to call. - pub mutation: Option, - - /// The arguments that will override the actual arguments of the field. - #[serde(default, skip_serializing_if = "is_default")] - pub args: BTreeMap, -} - -#[derive( - Serialize, - Deserialize, - Clone, - Debug, - Default, - PartialEq, - Eq, - schemars::JsonSchema, - InputDefinition, - DirectiveDefinition, -)] -#[directive_definition(locations = "FieldDefinition")] -#[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] -/// The @grpc operator indicates that a field or node is backed by a gRPC API. -/// -/// For instance, if you add the @grpc operator to the `users` field of the -/// Query type with a service argument of `NewsService` and method argument of -/// `GetAllNews`, it signifies that the `users` field is backed by a gRPC API. -/// The `service` argument specifies the name of the gRPC service. -/// The `method` argument specifies the name of the gRPC method. -/// In this scenario, the GraphQL server will make a gRPC request to the gRPC -/// endpoint specified when the `users` field is queried. -pub struct Grpc { - #[serde(rename = "baseURL", default, skip_serializing_if = "is_default")] - /// This refers to the base URL of the API. If not specified, the default - /// base URL is the one specified in the `@upstream` operator. - pub base_url: Option, - #[serde(default, skip_serializing_if = "is_default")] - /// This refers to the arguments of your gRPC call. You can pass it as a - /// static object or use Mustache template for dynamic parameters. These - /// parameters will be added in the body in `protobuf` format. - pub body: Option, - #[serde(rename = "batchKey", default, skip_serializing_if = "is_default")] - /// The `batchKey` dictates the path Tailcall will follow to group the returned items from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching). - pub batch_key: Vec, - #[serde(default, skip_serializing_if = "is_default")] - /// The `headers` parameter allows you to customize the headers of the HTTP - /// request made by the `@grpc` operator. It is used by specifying a - /// key-value map of header names and their values. Note: content-type is - /// automatically set to application/grpc - pub headers: Vec, - /// This refers to the gRPC method you're going to call. For instance - /// `GetAllNews`. - pub method: String, -} - -#[derive( - Serialize, - Deserialize, - Clone, - Debug, - Default, - PartialEq, - Eq, - schemars::JsonSchema, - DirectiveDefinition, - InputDefinition, -)] -#[directive_definition(locations = "FieldDefinition")] -#[serde(deny_unknown_fields)] -/// The @graphQL operator allows to specify GraphQL API server request to fetch -/// data from. -pub struct GraphQL { - #[serde(default, skip_serializing_if = "is_default")] - /// Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args) - pub args: Option>, - - #[serde(rename = "baseURL", default, skip_serializing_if = "is_default")] - /// This refers to the base URL of the API. If not specified, the default - /// base URL is the one specified in the `@upstream` operator. - pub base_url: Option, - - #[serde(default, skip_serializing_if = "is_default")] - /// If the upstream GraphQL server supports request batching, you can - /// specify the 'batch' argument to batch several requests into a single - /// batch request. - /// - /// Make sure you have also specified batch settings to the `@upstream` and - /// to the `@graphQL` operator. - pub batch: bool, - - #[serde(default, skip_serializing_if = "is_default")] - /// The headers parameter allows you to customize the headers of the GraphQL - /// request made by the `@graphQL` operator. It is used by specifying a - /// key-value map of header names and their values. - pub headers: Vec, - - /// Specifies the root field on the upstream to request data from. This maps - /// a field in your schema to a field in the upstream schema. When a query - /// is received for this field, Tailcall requests data from the - /// corresponding upstream field. - pub name: String, -} - #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum GraphQLOperationType { @@ -728,26 +492,6 @@ impl Display for GraphQLOperationType { } } -#[derive( - Serialize, - Deserialize, - Clone, - Debug, - PartialEq, - Eq, - schemars::JsonSchema, - DirectiveDefinition, - InputDefinition, -)] -#[directive_definition(locations = "FieldDefinition")] -#[serde(deny_unknown_fields)] -/// The `@expr` operators allows you to specify an expression that can evaluate -/// to a value. The expression can be a static value or built form a Mustache -/// template. schema. -pub struct Expr { - pub body: Value, -} - #[derive( Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema, DirectiveDefinition, )] diff --git a/src/core/config/directives/call.rs b/src/core/config/directives/call.rs new file mode 100644 index 0000000000..0ab52a018f --- /dev/null +++ b/src/core/config/directives/call.rs @@ -0,0 +1,46 @@ +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; +use crate::core::is_default; +use serde_json::Value; +use tailcall_macros::DirectiveDefinition; + +/// +/// Provides the ability to refer to a field defined in the root Query or +/// Mutation. +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] +pub struct Step { + #[serde(default, skip_serializing_if = "is_default")] + /// The name of the field on the `Query` type that you want to call. + pub query: Option, + + #[serde(default, skip_serializing_if = "is_default")] + /// The name of the field on the `Mutation` type that you want to call. + pub mutation: Option, + + /// The arguments that will override the actual arguments of the field. + #[serde(default, skip_serializing_if = "is_default")] + pub args: BTreeMap, +} + +/// +/// Provides the ability to refer to multiple fields in the Query or +/// Mutation root. +#[derive( + Serialize, + Deserialize, + Clone, + Debug, + Default, + PartialEq, + Eq, + schemars::JsonSchema, + DirectiveDefinition, +)] +#[directive_definition(locations = "FieldDefinition")] +pub struct Call { + /// Steps are composed together to form a call. + /// If you have multiple steps, the output of the previous step is passed as + /// input to the next step. + pub steps: Vec, +} diff --git a/src/core/config/directives/expr.rs b/src/core/config/directives/expr.rs new file mode 100644 index 0000000000..bd224f46dc --- /dev/null +++ b/src/core/config/directives/expr.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; +use tailcall_macros::{DirectiveDefinition, InputDefinition}; +use serde_json::Value; + + +#[derive( + Serialize, + Deserialize, + Clone, + Debug, + PartialEq, + Eq, + schemars::JsonSchema, + DirectiveDefinition, + InputDefinition, +)] +#[directive_definition(locations = "FieldDefinition")] +#[serde(deny_unknown_fields)] +/// The `@expr` operators allows you to specify an expression that can evaluate +/// to a value. The expression can be a static value or built form a Mustache +/// template. schema. +pub struct Expr { + pub body: Value, +} diff --git a/src/core/config/directives/federation.rs b/src/core/config/directives/federation.rs new file mode 100644 index 0000000000..d8476f5410 --- /dev/null +++ b/src/core/config/directives/federation.rs @@ -0,0 +1,19 @@ +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; +use tailcall_macros::MergeRight; +use crate::core::merge_right::MergeRight; + +use crate::core::config::Resolver; + +/// Directive `@key` for Apollo Federation +#[derive(Serialize, Deserialize,Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight)] +pub struct Key { + pub fields: String, +} + +/// Resolver for `_entities` field for Apollo Federation +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] +pub struct EntityResolver { + pub resolver_by_type: BTreeMap, +} diff --git a/src/core/config/directives/graphql.rs b/src/core/config/directives/graphql.rs new file mode 100644 index 0000000000..fa6d7c7ff6 --- /dev/null +++ b/src/core/config/directives/graphql.rs @@ -0,0 +1,51 @@ +use serde::{Deserialize, Serialize}; +use crate::core::{config::KeyValue, is_default}; +use tailcall_macros::{DirectiveDefinition, InputDefinition}; + +#[derive( + Serialize, + Deserialize, + Clone, + Debug, + Default, + PartialEq, + Eq, + schemars::JsonSchema, + DirectiveDefinition, + InputDefinition, +)] +#[directive_definition(locations = "FieldDefinition")] +#[serde(deny_unknown_fields)] +/// The @graphQL operator allows to specify GraphQL API server request to fetch +/// data from. +pub struct GraphQL { + #[serde(default, skip_serializing_if = "is_default")] + /// Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args) + pub args: Option>, + + #[serde(rename = "baseURL", default, skip_serializing_if = "is_default")] + /// This refers to the base URL of the API. If not specified, the default + /// base URL is the one specified in the `@upstream` operator. + pub base_url: Option, + + #[serde(default, skip_serializing_if = "is_default")] + /// If the upstream GraphQL server supports request batching, you can + /// specify the 'batch' argument to batch several requests into a single + /// batch request. + /// + /// Make sure you have also specified batch settings to the `@upstream` and + /// to the `@graphQL` operator. + pub batch: bool, + + #[serde(default, skip_serializing_if = "is_default")] + /// The headers parameter allows you to customize the headers of the GraphQL + /// request made by the `@graphQL` operator. It is used by specifying a + /// key-value map of header names and their values. + pub headers: Vec, + + /// Specifies the root field on the upstream to request data from. This maps + /// a field in your schema to a field in the upstream schema. When a query + /// is received for this field, Tailcall requests data from the + /// corresponding upstream field. + pub name: String, +} diff --git a/src/core/config/directives/grpc.rs b/src/core/config/directives/grpc.rs new file mode 100644 index 0000000000..30097b1592 --- /dev/null +++ b/src/core/config/directives/grpc.rs @@ -0,0 +1,53 @@ +use crate::core::config::KeyValue; +use crate::core::is_default; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tailcall_macros::{DirectiveDefinition, InputDefinition}; + +#[derive( + Serialize, + Deserialize, + Clone, + Debug, + Default, + PartialEq, + Eq, + schemars::JsonSchema, + InputDefinition, + DirectiveDefinition, +)] +#[directive_definition(locations = "FieldDefinition")] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +/// The @grpc operator indicates that a field or node is backed by a gRPC API. +/// +/// For instance, if you add the @grpc operator to the `users` field of the +/// Query type with a service argument of `NewsService` and method argument of +/// `GetAllNews`, it signifies that the `users` field is backed by a gRPC API. +/// The `service` argument specifies the name of the gRPC service. +/// The `method` argument specifies the name of the gRPC method. +/// In this scenario, the GraphQL server will make a gRPC request to the gRPC +/// endpoint specified when the `users` field is queried. +pub struct Grpc { + #[serde(rename = "baseURL", default, skip_serializing_if = "is_default")] + /// This refers to the base URL of the API. If not specified, the default + /// base URL is the one specified in the `@upstream` operator. + pub base_url: Option, + #[serde(default, skip_serializing_if = "is_default")] + /// This refers to the arguments of your gRPC call. You can pass it as a + /// static object or use Mustache template for dynamic parameters. These + /// parameters will be added in the body in `protobuf` format. + pub body: Option, + #[serde(rename = "batchKey", default, skip_serializing_if = "is_default")] + /// The `batchKey` dictates the path Tailcall will follow to group the returned items from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching). + pub batch_key: Vec, + #[serde(default, skip_serializing_if = "is_default")] + /// The `headers` parameter allows you to customize the headers of the HTTP + /// request made by the `@grpc` operator. It is used by specifying a + /// key-value map of header names and their values. Note: content-type is + /// automatically set to application/grpc + pub headers: Vec, + /// This refers to the gRPC method you're going to call. For instance + /// `GetAllNews`. + pub method: String, +} diff --git a/src/core/config/directives/http.rs b/src/core/config/directives/http.rs new file mode 100644 index 0000000000..ee38379aa5 --- /dev/null +++ b/src/core/config/directives/http.rs @@ -0,0 +1,94 @@ +use crate::core::{ + config::{Encoding, KeyValue, URLQuery}, + http::Method, + is_default, + json::JsonSchema, +}; +use serde::{Deserialize, Serialize}; +use tailcall_macros::{DirectiveDefinition, InputDefinition}; + +#[derive( + Serialize, + Deserialize, + Clone, + Debug, + Default, + PartialEq, + Eq, + schemars::JsonSchema, + DirectiveDefinition, + InputDefinition, +)] +#[directive_definition(locations = "FieldDefinition")] +#[serde(deny_unknown_fields)] +/// The @http operator indicates that a field or node is backed by a REST API. +/// +/// For instance, if you add the @http operator to the `users` field of the +/// Query type with a path argument of `"/users"`, it signifies that the `users` +/// field is backed by a REST API. The path argument specifies the path of the +/// REST API. In this scenario, the GraphQL server will make a GET request to +/// the API endpoint specified when the `users` field is queried. +pub struct Http { + #[serde(rename = "onRequest", default, skip_serializing_if = "is_default")] + /// onRequest field in @http directive gives the ability to specify the + /// request interception handler. + pub on_request: Option, + + #[serde(rename = "baseURL", default, skip_serializing_if = "is_default")] + /// This refers to the base URL of the API. If not specified, the default + /// base URL is the one specified in the `@upstream` operator. + pub base_url: Option, + + #[serde(default, skip_serializing_if = "is_default")] + /// The body of the API call. It's used for methods like POST or PUT that + /// send data to the server. You can pass it as a static object or use a + /// Mustache template to substitute variables from the GraphQL variables. + pub body: Option, + + #[serde(default, skip_serializing_if = "is_default")] + /// The `encoding` parameter specifies the encoding of the request body. It + /// can be `ApplicationJson` or `ApplicationXWwwFormUrlEncoded`. @default + /// `ApplicationJson`. + pub encoding: Encoding, + + #[serde(rename = "batchKey", default, skip_serializing_if = "is_default")] + /// The `batchKey` dictates the path Tailcall will follow to group the returned items from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching). + pub batch_key: Vec, + + #[serde(default, skip_serializing_if = "is_default")] + /// The `headers` parameter allows you to customize the headers of the HTTP + /// request made by the `@http` operator. It is used by specifying a + /// key-value map of header names and their values. + pub headers: Vec, + + #[serde(default, skip_serializing_if = "is_default")] + /// Schema of the input of the API call. It is automatically inferred in + /// most cases. + pub input: Option, + + #[serde(default, skip_serializing_if = "is_default")] + /// This refers to the HTTP method of the API call. Commonly used methods + /// include `GET`, `POST`, `PUT`, `DELETE` etc. @default `GET`. + pub method: Method, + + /// This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`. + /// + /// For dynamic segments in your API endpoint, use Mustache templates for + /// variable substitution. For instance, to fetch a specific user, use + /// `/users/{{args.id}}`. + pub path: String, + + #[serde(default, skip_serializing_if = "is_default")] + /// Schema of the output of the API call. It is automatically inferred in + /// most cases. + pub output: Option, + + #[serde(default, skip_serializing_if = "is_default")] + /// This represents the query parameters of your API call. You can pass it + /// as a static object or use Mustache template for dynamic parameters. + /// These parameters will be added to the URL. + /// NOTE: Query parameter order is critical for batching in Tailcall. The + /// first parameter referencing a field in the current value using mustache + /// syntax is automatically selected as the batching parameter. + pub query: Vec, +} diff --git a/src/core/config/directives/js.rs b/src/core/config/directives/js.rs new file mode 100644 index 0000000000..12269f16f9 --- /dev/null +++ b/src/core/config/directives/js.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; +use tailcall_macros::{DirectiveDefinition, InputDefinition}; + + +#[derive( + Serialize, + Deserialize, + Clone, + Debug, + PartialEq, + Eq, + schemars::JsonSchema, + DirectiveDefinition, + InputDefinition, +)] +#[directive_definition(locations = "FieldDefinition", lowercase_name)] +pub struct JS { + pub name: String, +} diff --git a/src/core/config/directives/mod.rs b/src/core/config/directives/mod.rs new file mode 100644 index 0000000000..c328ed06e7 --- /dev/null +++ b/src/core/config/directives/mod.rs @@ -0,0 +1,15 @@ +mod federation; +mod http; +mod grpc; +mod graphql; +mod call; +mod expr; +mod js; + +pub use expr::*; +pub use federation::*; +pub use http::*; +pub use grpc::*; +pub use graphql::*; +pub use call::*; +pub use js::*; diff --git a/src/core/config/from_document.rs b/src/core/config/from_document.rs index 24ffb593a2..0be2d9be22 100644 --- a/src/core/config/from_document.rs +++ b/src/core/config/from_document.rs @@ -257,6 +257,7 @@ where cache, protected, resolver, + key: None, } }) } From dc57283dcc076cb93ecc15b21cfac4fd8b7441ae Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:20:59 +0000 Subject: [PATCH 04/17] extend merge_right macro to support other types of struct --- tailcall-macros/src/merge_right.rs | 69 ++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/tailcall-macros/src/merge_right.rs b/tailcall-macros/src/merge_right.rs index dce5c428f5..260aa992c9 100644 --- a/tailcall-macros/src/merge_right.rs +++ b/tailcall-macros/src/merge_right.rs @@ -2,7 +2,7 @@ extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; -use syn::spanned::Spanned; +use syn::{spanned::Spanned, Index}; use syn::{parse_macro_input, Data, DeriveInput, Fields}; const MERGE_RIGHT_FN: &str = "merge_right_fn"; @@ -59,29 +59,54 @@ pub fn expand_merge_right_derive(input: TokenStream) -> TokenStream { let gen = match input.data { // Implement for structs Data::Struct(data) => { - let fields = if let Fields::Named(fields) = data.fields { - fields.named - } else { - // Adjust this match arm to handle other kinds of struct fields (unnamed/tuple - // structs, unit structs) - unimplemented!() + let fields = match &data.fields { + Fields::Named(fields) => &fields.named, + Fields::Unnamed(fields) => &fields.unnamed, + Fields::Unit => { + return quote! { + impl MergeRight for #name { + fn merge_right(self, other: Self) -> Self { + other + } + } + } + .into() + } }; - let merge_logic = fields.iter().map(|f| { + let merge_logic = fields.iter().enumerate().map(|(i, f)| { let attrs = get_attrs(&f.attrs); if let Err(err) = attrs { panic!("{}", err); } let attrs = attrs.unwrap(); let name = &f.ident; - if let Some(merge_right_fn) = attrs.merge_right_fn { - quote! { - #name: #merge_right_fn(self.#name, other.#name), + + match &data.fields { + Fields::Named(_) => { + if let Some(merge_right_fn) = attrs.merge_right_fn { + quote! { + #name: #merge_right_fn(self.#name, other.#name), + } + } else { + quote! { + #name: self.#name.merge_right(other.#name), + } + } } - } else { - quote! { - #name: self.#name.merge_right(other.#name), + Fields::Unnamed(_) => { + let name = Index::from(i); + if let Some(merge_right_fn) = attrs.merge_right_fn { + quote! { + #merge_right_fn(self.#name, other.#name), + } + } else { + quote! { + self.#name.merge_right(other.#name), + } + } } + Fields::Unit => unreachable!(), } }); @@ -93,12 +118,22 @@ pub fn expand_merge_right_derive(input: TokenStream) -> TokenStream { #generics_lt #generics_params #generics_gt }; + let initializer = match data.fields { + Fields::Named(_) => quote! { + Self { + #(#merge_logic)* + } + }, + Fields::Unnamed(_) => quote! { + Self(#(#merge_logic)*) + }, + Fields::Unit => unreachable!(), + }; + quote! { impl #generics_del MergeRight for #name #generics_del { fn merge_right(self, other: Self) -> Self { - Self { - #(#merge_logic)* - } + #initializer } } } From 9e76cb3977891813283717ed2af0a2f123676693 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Tue, 20 Aug 2024 15:15:46 +0000 Subject: [PATCH 05/17] add federation _service support --- src/core/blueprint/definitions.rs | 2 +- ...ntity_resolver.rs => apollo_federation.rs} | 68 ++- src/core/blueprint/operators/mod.rs | 4 +- src/core/config/config.rs | 30 +- src/core/config/directives/federation.rs | 2 +- src/core/config/into_document.rs | 45 +- src/core/config/mod.rs | 2 + .../transformer/apollo_federation/entities.rs | 80 --- .../transformer/apollo_federation/mod.rs | 1 - .../transformer/apollo_federation_subgraph.rs | 501 ++++++++++++++++++ src/core/config/transformer/mod.rs | 4 +- src/core/config/transformer/required.rs | 2 +- ...graph__tests__extractor__extract_call.snap | 11 + ...graph__tests__extractor__extract_expr.snap | 11 + ...ph__tests__extractor__extract_graphql.snap | 11 + ...graph__tests__extractor__extract_grpc.snap | 11 + ...graph__tests__extractor__extract_http.snap | 11 + ..._tests__extractor__non_value_template.snap | 28 + ...ion_subgraph__tests__keys__keys_merge.snap | 32 ++ ...ation_subgraph__tests__keys__keys_set.snap | 32 ++ src/core/helpers/body.rs | 1 + src/core/ir/eval.rs | 10 +- src/core/ir/model.rs | 4 + src/core/mustache/parse.rs | 1 - src/core/valid/valid.rs | 9 + tailcall-macros/src/resolver.rs | 32 +- ...apollo-federation-entities-batch.md_1.snap | 17 + ...o-federation-entities-batch.md_client.snap | 8 + ...o-federation-entities-batch.md_merged.snap | 16 +- ...deration-entities-validation.md_error.snap | 23 + .../apollo-federation-entities.md_1.snap | 17 + .../apollo-federation-entities.md_client.snap | 8 + .../apollo-federation-entities.md_merged.snap | 12 +- .../apollo-federation-entities-batch.md | 14 +- .../apollo-federation-entities-validation.md | 78 +++ tests/execution/apollo-federation-entities.md | 14 +- 36 files changed, 987 insertions(+), 165 deletions(-) rename src/core/blueprint/operators/{entity_resolver.rs => apollo_federation.rs} (56%) delete mode 100644 src/core/config/transformer/apollo_federation/entities.rs delete mode 100644 src/core/config/transformer/apollo_federation/mod.rs create mode 100644 src/core/config/transformer/apollo_federation_subgraph.rs create mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_call.snap create mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_expr.snap create mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_graphql.snap create mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_grpc.snap create mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_http.snap create mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__non_value_template.snap create mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_merge.snap create mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_set.snap create mode 100644 tests/core/snapshots/apollo-federation-entities-batch.md_1.snap create mode 100644 tests/core/snapshots/apollo-federation-entities-validation.md_error.snap create mode 100644 tests/core/snapshots/apollo-federation-entities.md_1.snap create mode 100644 tests/execution/apollo-federation-entities-validation.md diff --git a/src/core/blueprint/definitions.rs b/src/core/blueprint/definitions.rs index b15e56e7c3..8f7341a4d4 100644 --- a/src/core/blueprint/definitions.rs +++ b/src/core/blueprint/definitions.rs @@ -487,7 +487,7 @@ pub fn to_field_definition( name: &String, ) -> Valid { update_args() - .and(update_entity_resolver(operation_type, object_name).trace("EntityResolver")) + .and(update_apollo_federation(operation_type).trace("EntityResolver")) .and(update_http().trace(config::Http::trace_name().as_str())) .and(update_grpc(operation_type).trace(config::Grpc::trace_name().as_str())) .and(update_const_field().trace(config::Expr::trace_name().as_str())) diff --git a/src/core/blueprint/operators/entity_resolver.rs b/src/core/blueprint/operators/apollo_federation.rs similarity index 56% rename from src/core/blueprint/operators/entity_resolver.rs rename to src/core/blueprint/operators/apollo_federation.rs index f5ee67889f..555ad1fd5c 100644 --- a/src/core/blueprint/operators/entity_resolver.rs +++ b/src/core/blueprint/operators/apollo_federation.rs @@ -1,35 +1,33 @@ use std::collections::HashMap; +use std::fmt::Write; use super::{compile_call, compile_expr, compile_graphql, compile_grpc, compile_http, compile_js}; -use crate::core::blueprint::FieldDefinition; use crate::core::config::{ ConfigModule, EntityResolver, Field, GraphQLOperationType, Resolver, Type, }; use crate::core::ir::model::IR; use crate::core::try_fold::TryFold; use crate::core::valid::{Valid, Validator}; +use crate::core::{blueprint::FieldDefinition, config::ApolloFederation}; pub struct CompileEntityResolver<'a> { config_module: &'a ConfigModule, - field: &'a Field, entity_resolver: &'a EntityResolver, operation_type: &'a GraphQLOperationType, - object_name: &'a str, } pub fn compile_entity_resolver(inputs: CompileEntityResolver<'_>) -> Valid { - let CompileEntityResolver { - config_module, - field, - entity_resolver, - operation_type, - object_name, - } = inputs; + let CompileEntityResolver { config_module, entity_resolver, operation_type } = inputs; let mut resolver_by_type = HashMap::new(); Valid::from_iter( entity_resolver.resolver_by_type.iter(), |(type_name, resolver)| { + // Fake field that is required for validation in some cases + // TODO: should be a proper way to run the validation both + // on types and fields + let field = &Field { type_of: type_name.clone(), ..Default::default() }; + // TODO: make this code reusable in other operators like call let ir = match resolver { // TODO: there are `validate_field` for field, but not for types @@ -51,7 +49,7 @@ pub fn compile_entity_resolver(inputs: CompileEntityResolver<'_>) -> Valid { - compile_call(config_module, call, operation_type, object_name) + compile_call(config_module, call, operation_type, type_name) } Resolver::Js(js) => { compile_js(super::CompileJs { js, script: &config_module.extensions().script }) @@ -59,9 +57,15 @@ pub fn compile_entity_resolver(inputs: CompileEntityResolver<'_>) -> Valid { compile_expr(super::CompileExpr { config_module, field, expr, validate: true }) } - Resolver::EntityResolver(entity_resolver) => { - compile_entity_resolver(CompileEntityResolver { entity_resolver, ..inputs }) - } + Resolver::ApolloFederation(federation) => match federation { + ApolloFederation::EntityResolver(entity_resolver) => { + compile_entity_resolver(CompileEntityResolver { entity_resolver, ..inputs }) + } + ApolloFederation::Service => Valid::fail( + "Apollo federation resolvers can't be a part of entity resolver" + .to_string(), + ), + }, }; ir.map(|ir| { @@ -72,23 +76,39 @@ pub fn compile_entity_resolver(inputs: CompileEntityResolver<'_>) -> Valid( +pub fn compile_service(config: &ConfigModule) -> Valid { + let mut sdl = config.to_sdl(); + + writeln!(sdl).ok(); + // Mark subgraph as Apollo federation v2 compatible according to [docs](https://www.apollographql.com/docs/apollo-server/using-federation/apollo-subgraph-setup/#2-opt-in-to-federation-2) + // (borrowed from async_graphql) + writeln!(sdl, "extend schema @link(").ok(); + writeln!(sdl, "\turl: \"https://specs.apollo.dev/federation/v2.3\",").ok(); + writeln!(sdl, "\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]").ok(); + writeln!(sdl, ")").ok(); + + Valid::succeed(IR::Service(sdl)) +} + +pub fn update_apollo_federation<'a>( operation_type: &'a GraphQLOperationType, - object_name: &'a str, ) -> TryFold<'a, (&'a ConfigModule, &'a Field, &'a Type, &'a str), FieldDefinition, String> { TryFold::<(&ConfigModule, &Field, &Type, &'a str), FieldDefinition, String>::new( |(config_module, field, _, _), b_field| { - let Some(Resolver::EntityResolver(entity_resolver)) = &field.resolver else { + let Some(Resolver::ApolloFederation(federation)) = &field.resolver else { return Valid::succeed(b_field); }; - compile_entity_resolver(CompileEntityResolver { - config_module, - field, - entity_resolver, - operation_type, - object_name, - }) + match federation { + ApolloFederation::EntityResolver(entity_resolver) => { + compile_entity_resolver(CompileEntityResolver { + config_module, + entity_resolver, + operation_type, + }) + } + ApolloFederation::Service => compile_service(config_module), + } .map(|resolver| b_field.resolver(Some(resolver))) }, ) diff --git a/src/core/blueprint/operators/mod.rs b/src/core/blueprint/operators/mod.rs index 6e6964fba1..c927df111e 100644 --- a/src/core/blueprint/operators/mod.rs +++ b/src/core/blueprint/operators/mod.rs @@ -1,5 +1,5 @@ mod call; -mod entity_resolver; +mod apollo_federation; mod enum_alias; mod expr; mod graphql; @@ -10,7 +10,7 @@ mod modify; mod protected; pub use call::*; -pub use entity_resolver::*; +pub use apollo_federation::*; pub use enum_alias::*; pub use expr::*; pub use graphql::*; diff --git a/src/core/config/config.rs b/src/core/config/config.rs index e698da6432..f0aea92bf9 100644 --- a/src/core/config/config.rs +++ b/src/core/config/config.rs @@ -3,8 +3,8 @@ use std::fmt::{self, Display}; use std::num::NonZeroU64; use anyhow::Result; -use async_graphql::Positioned; use async_graphql::parser::types::{ConstDirective, ServiceDocument}; +use async_graphql::Positioned; use derive_setters::Setters; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -14,7 +14,9 @@ use tailcall_typedefs_common::input_definition::InputDefinition; use tailcall_typedefs_common::ServiceDocumentBuilder; use super::{ - directives::{Call, EntityResolver, Expr, GraphQL, Grpc, Http, Key, JS}, from_document::from_document, telemetry::Telemetry + directives::{Call, EntityResolver, Expr, GraphQL, Grpc, Http, Key, JS}, + from_document::from_document, + telemetry::Telemetry, }; use super::{Link, Server, Upstream}; use crate::core::config::npo::QueryPath; @@ -219,6 +221,12 @@ pub struct RootSchema { /// Used to omit a field from public consumption. pub struct Omit {} +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ApolloFederation { + EntityResolver(EntityResolver), + Service, +} + #[derive( Serialize, Deserialize, @@ -239,8 +247,8 @@ pub enum Resolver { Js(JS), Expr(Expr), #[serde(skip)] - #[resolver(skip_to_directive)] - EntityResolver(EntityResolver), + #[resolver(skip_directive)] + ApolloFederation(ApolloFederation), } impl Resolver { @@ -249,13 +257,13 @@ impl Resolver { Resolver::Http(http) => !http.batch_key.is_empty(), Resolver::Grpc(grpc) => !grpc.batch_key.is_empty(), Resolver::Graphql(graphql) => graphql.batch, - Resolver::Call(_) => false, - Resolver::Js(_) => false, - Resolver::Expr(_) => false, - Resolver::EntityResolver(entity_resolver) => entity_resolver - .resolver_by_type - .values() - .any(Resolver::is_batched), + Resolver::ApolloFederation(ApolloFederation::EntityResolver(entity_resolver)) => { + entity_resolver + .resolver_by_type + .values() + .any(Resolver::is_batched) + } + _ => false, } } } diff --git a/src/core/config/directives/federation.rs b/src/core/config/directives/federation.rs index d8476f5410..7015eb38dc 100644 --- a/src/core/config/directives/federation.rs +++ b/src/core/config/directives/federation.rs @@ -13,7 +13,7 @@ pub struct Key { } /// Resolver for `_entities` field for Apollo Federation -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct EntityResolver { pub resolver_by_type: BTreeMap, } diff --git a/src/core/config/into_document.rs b/src/core/config/into_document.rs index 4183ff92d0..3400ab5150 100644 --- a/src/core/config/into_document.rs +++ b/src/core/config/into_document.rs @@ -195,27 +195,38 @@ fn config_document(config: &Config) -> ServiceDocument { .collect::>>(), }) }; + + let directives = type_def + .added_fields + .iter() + .map(|added_field: &super::AddField| pos(added_field.to_directive())) + .chain( + type_def + .cache + .as_ref() + .map(|cache| pos(cache.to_directive())), + ) + .chain( + type_def + .protected + .as_ref() + .map(|protected| pos(protected.to_directive())), + ) + .chain( + type_def + .resolver + .as_ref() + .and_then(|resolver| resolver.to_directive()) + .map(pos), + ) + .chain(type_def.key.as_ref().map(|key| pos(key.to_directive()))) + .collect::>(); + definitions.push(TypeSystemDefinition::Type(pos(TypeDefinition { extend: false, description: type_def.doc.clone().map(pos), name: pos(Name::new(type_name.clone())), - directives: type_def - .added_fields - .iter() - .map(|added_field: &super::AddField| pos(added_field.to_directive())) - .chain( - type_def - .cache - .as_ref() - .map(|cache| pos(cache.to_directive())), - ) - .chain( - type_def - .protected - .as_ref() - .map(|protected| pos(protected.to_directive())), - ) - .collect::>(), + directives, kind, }))); } diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 6135656368..3dbd445be6 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -10,6 +10,7 @@ pub use source::*; pub use telemetry::*; pub use upstream::*; pub use url_query::*; +pub use directives::*; mod apollo; mod config; mod config_module; @@ -29,3 +30,4 @@ mod telemetry; pub mod transformer; mod upstream; mod url_query; +pub mod directives; diff --git a/src/core/config/transformer/apollo_federation/entities.rs b/src/core/config/transformer/apollo_federation/entities.rs deleted file mode 100644 index 8cdb101736..0000000000 --- a/src/core/config/transformer/apollo_federation/entities.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::collections::BTreeMap; - -use crate::core::config::{config, Arg, Config, Field, Resolver, Type, Union}; -use crate::core::valid::Valid; -use crate::core::Transform; - -const ROOT_FIELD_NAME: &str = "_entities"; -const UNION_ENTITIES_NAME: &str = "_Entity"; -const ARG_NAME: &str = "representations"; -const ARG_TYPE_NAME: &str = "_Any"; - -pub struct EntityResolver; - -impl Transform for EntityResolver { - type Value = Config; - - type Error = String; - - fn transform(&self, mut config: Self::Value) -> Valid { - let mut resolver_by_type = BTreeMap::new(); - - for (type_name, ty) in &config.types { - if let Some(resolver) = &ty.resolver { - resolver_by_type.insert(type_name.clone(), resolver.clone()); - } - } - - if resolver_by_type.is_empty() { - return Valid::succeed(config); - } - - let entity_union = Union { - types: resolver_by_type.keys().cloned().collect(), - ..Default::default() - }; - - // union that wraps any possible types for entities - config - .unions - .insert(UNION_ENTITIES_NAME.to_owned(), entity_union); - // any scalar for argument `representations` - config - .types - .insert(ARG_TYPE_NAME.to_owned(), Type::default()); - - let entity_resolver = config::EntityResolver { resolver_by_type }; - - let query_type = match config.schema.query.as_ref() { - Some(name) => name, - None => { - config.schema.query = Some("Query".to_string()); - "Query" - } - }; - - let query_type = config.types.entry(query_type.to_owned()).or_default(); - - let arg = Arg { - type_of: ARG_TYPE_NAME.to_string(), - list: true, - required: true, - ..Default::default() - }; - - query_type.fields.insert( - ROOT_FIELD_NAME.to_string(), - Field { - type_of: UNION_ENTITIES_NAME.to_owned(), - list: true, - required: true, - args: [(ARG_NAME.to_owned(), arg)].into_iter().collect(), - doc: Some("Apollo federation Query._entities resolver".to_string()), - resolver: Some(Resolver::EntityResolver(entity_resolver)), - ..Default::default() - }, - ); - - Valid::succeed(config) - } -} diff --git a/src/core/config/transformer/apollo_federation/mod.rs b/src/core/config/transformer/apollo_federation/mod.rs deleted file mode 100644 index 0b8f0b5a5a..0000000000 --- a/src/core/config/transformer/apollo_federation/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod entities; diff --git a/src/core/config/transformer/apollo_federation_subgraph.rs b/src/core/config/transformer/apollo_federation_subgraph.rs new file mode 100644 index 0000000000..da12dddd3d --- /dev/null +++ b/src/core/config/transformer/apollo_federation_subgraph.rs @@ -0,0 +1,501 @@ +use std::{ + borrow::Borrow, + collections::BTreeMap, + convert::identity, + fmt::{Display, Write}, + ops::Deref, +}; + +use crate::core::{ + config::{self, Arg, Config, Field, Grpc, Key, KeyValue, Resolver, Type, Union}, + directive::DirectiveCodec, +}; +use crate::core::{ + config::{ApolloFederation, Http}, + merge_right::MergeRight, +}; +use crate::core::{ + config::{Call, GraphQL}, + valid::Valid, +}; +use crate::core::{mustache::Segment, valid::Validator, Mustache, Transform}; + +use tailcall_macros::MergeRight; + +const ENTITIES_FIELD_NAME: &str = "_entities"; +const SERVICE_FIELD_NAME: &str = "_service"; +const SERVICE_TYPE_NAME: &str = "_Service"; +const UNION_ENTITIES_NAME: &str = "_Entity"; +const ENTITIES_ARG_NAME: &str = "representations"; +const ENTITIES_TYPE_NAME: &str = "_Any"; + +/// Adds compatibility layer for Apollo Federation v2 +/// so tailcall may act as a Federation Subgraph. +/// Followed by [spec](https://www.apollographql.com/docs/federation/subgraph-spec/) +pub struct ApolloFederationSubgraph; + +impl Transform for ApolloFederationSubgraph { + type Value = Config; + + type Error = String; + + fn transform(&self, mut config: Self::Value) -> Valid { + let mut resolver_by_type = BTreeMap::new(); + + let valid = Valid::from_iter(config.types.iter_mut(), |(type_name, ty)| { + if let Some(resolver) = &ty.resolver { + resolver_by_type.insert(type_name.clone(), resolver.clone()); + + KeysExtractor::extract_keys(resolver).map(|fields| { + fields.map(|fields| { + ty.key = Some(Key { fields }); + }) + }) + } else { + Valid::succeed(None) + } + .trace(type_name) + }); + + if valid.is_fail() { + return valid.map_to(config); + } + + if resolver_by_type.is_empty() { + return Valid::succeed(config); + } + + let entity_union = Union { + types: resolver_by_type.keys().cloned().collect(), + ..Default::default() + }; + + let entity_resolver = config::EntityResolver { resolver_by_type }; + + // union that wraps any possible types for entities + config + .unions + .insert(UNION_ENTITIES_NAME.to_owned(), entity_union); + // any scalar for argument `representations` + config + .types + .insert(ENTITIES_TYPE_NAME.to_owned(), Type::default()); + + let service_field = Field { type_of: "String".to_string(), ..Default::default() }; + + let service_type = Type { + fields: [("sdl".to_string(), service_field)].into_iter().collect(), + ..Default::default() + }; + + // type that represents the response for service + config + .types + .insert(SERVICE_TYPE_NAME.to_owned(), service_type); + + let query_type = match config.schema.query.as_ref() { + Some(name) => name, + None => { + config.schema.query = Some("Query".to_string()); + "Query" + } + }; + + let query_type = config.types.entry(query_type.to_owned()).or_default(); + + let arg = Arg { + type_of: ENTITIES_TYPE_NAME.to_string(), + list: true, + required: true, + ..Default::default() + }; + + query_type.fields.insert( + ENTITIES_FIELD_NAME.to_string(), + Field { + type_of: UNION_ENTITIES_NAME.to_owned(), + list: true, + required: true, + args: [(ENTITIES_ARG_NAME.to_owned(), arg)].into_iter().collect(), + doc: Some("Apollo federation Query._entities resolver".to_string()), + resolver: Some(Resolver::ApolloFederation( + ApolloFederation::EntityResolver(entity_resolver), + )), + ..Default::default() + }, + ); + + query_type.fields.insert( + SERVICE_FIELD_NAME.to_string(), + Field { + type_of: SERVICE_TYPE_NAME.to_owned(), + required: true, + doc: Some("Apollo federation Query._service resolver".to_string()), + resolver: Some(Resolver::ApolloFederation(ApolloFederation::Service)), + ..Default::default() + }, + ); + + Valid::succeed(config) + } +} + +#[derive(Default, Clone, Debug, MergeRight)] +struct Keys(BTreeMap); + +impl Keys { + fn new() -> Self { + Self::default() + } + + fn set_path(&mut self, path: impl Iterator) { + let mut map = &mut self.0; + + for part in path { + map = &mut map.entry(part).or_default().0; + } + } + + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl Display for Keys { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (i, (key, value)) in self.0.iter().enumerate() { + f.write_str(key)?; + + if !value.0.is_empty() { + write!(f, " {{ {} }}", value)?; + } + + if i < self.0.len() - 1 { + f.write_char(' ')?; + } + } + + Ok(()) + } +} + +fn combine_keys(v: Vec) -> Keys { + v.into_iter() + .fold(Keys::new(), |acc, keys| acc.merge_right(keys)) +} + +struct KeysExtractor; + +impl KeysExtractor { + fn extract_keys(resolver: &Resolver) -> Valid, String> { + // TODO: add validation for available fields from the type + match resolver { + Resolver::Http(http) => { + Valid::from_iter( + [ + Self::parse_str_option(http.base_url.as_deref()).trace("base_url"), + Self::parse_str(&http.path).trace("path"), + Self::parse_str_option(http.body.as_deref()).trace("body"), + Self::parse_key_value_iter(http.headers.iter()).trace("headers"), + Self::parse_key_value_iter(http.query.iter().map(|q| KeyValue { + key: q.key.to_string(), + value: q.value.to_string(), + })) + .trace("query"), + ], + identity, + ) + .trace(Http::directive_name().as_str()) + } + Resolver::Grpc(grpc) => Valid::from_iter( + [ + Self::parse_str_option(grpc.base_url.as_deref()), + Self::parse_str(&grpc.method), + Self::parse_value_option(&grpc.body), + Self::parse_key_value_iter(grpc.headers.iter()), + ], + identity, + ) + .trace(Grpc::directive_name().as_str()), + Resolver::Graphql(graphql) => Valid::from_iter( + [ + Self::parse_key_value_iter(graphql.headers.iter()), + Self::parse_key_value_iter_option(graphql.args.as_ref().map(|v| v.iter())), + ], + identity, + ) + .trace(GraphQL::directive_name().as_str()), + Resolver::Call(call) => Valid::from_option( + call.steps.first(), + "Call should define at least one step".to_string(), + ) + .and_then(|step| { + Valid::from_iter(step.args.iter(), |(key, value)| { + Valid::from_iter([Self::parse_str(key), Self::parse_value(value)], identity) + }) + }) + .map(|v| v.into_iter().flatten().collect()) + .trace(Call::directive_name().as_str()), + Resolver::Expr(expr) => Valid::from_iter([Self::parse_value(&expr.body)], identity) + .trace(Call::directive_name().as_str()), + _ => return Valid::succeed(None), + } + .map(|keys| { + let keys = combine_keys(keys); + + if keys.is_empty() { + None + } else { + Some(keys.to_string()) + } + }) + } + + fn parse_str(s: &str) -> Valid { + let mustache = Mustache::parse(s); + let mut keys = Keys::new(); + + Valid::from_iter(mustache.segments().iter(), |segment| { + if let Segment::Expression(expr) = segment { + match expr.first().map(Deref::deref) { + Some("value") => { + keys.set_path(expr[1..].iter().map(String::to_string)); + } + Some("args") => { + return Valid::fail( + "Type resolver can't use `.args`, use `.value` instead".to_string(), + ); + } + _ => {} + } + } + + Valid::succeed(()) + }) + .map_to(keys) + } + + fn parse_str_option(s: Option<&str>) -> Valid { + if let Some(s) = s { + Self::parse_str(s) + } else { + Valid::succeed(Keys::new()) + } + } + + fn parse_key_value_iter<'a, T: Borrow>( + it: impl Iterator, + ) -> Valid { + let mut keys = Keys::new(); + + Valid::from_iter(it, |key_value| { + let key_value = key_value.borrow(); + + Self::parse_str(&key_value.key) + .zip(Self::parse_str(&key_value.value)) + .map(|(key, value)| keys = keys.clone().merge_right(key).merge_right(value)) + }) + .map_to(keys) + } + + fn parse_key_value_iter_option<'a, T: Borrow>( + it: Option>, + ) -> Valid { + if let Some(it) = it { + Self::parse_key_value_iter(it) + } else { + Valid::succeed(Keys::new()) + } + } + + fn parse_value(value: &serde_json::Value) -> Valid { + match value { + serde_json::Value::String(s) => return Self::parse_str(s), + serde_json::Value::Array(v) => Valid::from_iter(v.iter(), |v| Self::parse_value(v)), + serde_json::Value::Object(map) => Valid::from_iter(map.iter(), |(k, v)| { + Self::parse_str(k) + .zip(Self::parse_value(v)) + .map(|(k, v)| k.merge_right(v)) + }), + _ => return Valid::succeed(Keys::new()), + } + .map(|keys_vec| { + keys_vec + .into_iter() + .fold(Keys::new(), |acc, keys| acc.merge_right(keys)) + }) + } + + fn parse_value_option(value: &Option) -> Valid { + if let Some(value) = value { + Self::parse_value(value) + } else { + Valid::succeed(Keys::new()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + mod keys { + use insta::assert_debug_snapshot; + + use super::*; + + #[test] + fn test_keys_set() { + let mut keys = Keys::new(); + + keys.set_path(["a", "b", "c"].into_iter().map(str::to_string)); + keys.set_path(["a", "d"].into_iter().map(str::to_string)); + keys.set_path(["e"].into_iter().map(str::to_string)); + keys.set_path(["f", "g"].into_iter().map(str::to_string)); + + assert_debug_snapshot!(keys); + } + + #[test] + fn test_keys_merge() { + let mut keys_a = Keys::new(); + + keys_a.set_path(["a", "b"].into_iter().map(str::to_string)); + keys_a.set_path(["c"].into_iter().map(str::to_string)); + + let mut keys_b = Keys::new(); + + keys_b.set_path(["a", "1"].into_iter().map(str::to_string)); + keys_b.set_path(["c", "2"].into_iter().map(str::to_string)); + keys_b.set_path(["d", "3"].into_iter().map(str::to_string)); + + assert_debug_snapshot!(keys_a.merge_right(keys_b)); + } + } + + mod extractor { + use insta::assert_debug_snapshot; + use serde_json::json; + + use crate::core::{ + config::{Call, Expr, GraphQL, Grpc, Step, URLQuery}, + http::Method, + }; + + use super::{config::Http, KeyValue, KeysExtractor, Resolver}; + + #[test] + fn test_non_value_template() { + let http = Http { + base_url: Some("http://tailcall.run".to_string()), + path: "users/{{.args.id}}".to_string(), + query: vec![URLQuery { + key: "{{.env.query.key}}".to_string(), + value: "{{.args.query.value}}".to_string(), + ..Default::default() + }], + ..Default::default() + }; + let resolver = Resolver::Http(http); + let keys = KeysExtractor::extract_keys(&resolver); + + assert_debug_snapshot!(keys); + } + + #[test] + fn test_extract_http() { + let http = Http { + base_url: Some("http://tailcall.run".to_string()), + body: Some(r#"{ "obj": "{{.value.obj}}"} "#.to_string()), + headers: vec![KeyValue { + key: "{{.value.header.key}}".to_string(), + value: "{{.value.header.value}}".to_string(), + }], + method: Method::POST, + path: "users/{{.value.id}}".to_string(), + query: vec![URLQuery { + key: "{{.value.query_key}}".to_string(), + value: "{{.value.query_value}}".to_string(), + ..Default::default() + }], + ..Default::default() + }; + let resolver = Resolver::Http(http); + let keys = KeysExtractor::extract_keys(&resolver); + + assert_debug_snapshot!(keys); + } + + #[test] + fn test_extract_grpc() { + let grpc = Grpc { + base_url: Some("http://localhost:5051/{{.env.target}}".to_string()), + body: Some(json!({ "a": "{{.value.body.a}}", "b": "{{.value.body.b}}"})), + headers: vec![KeyValue { + key: "test".to_string(), + value: "{{.value.header_test}}".to_string(), + }], + method: "test_{{.value.method}}".to_string(), + ..Default::default() + }; + + let resolver = Resolver::Grpc(grpc); + let keys = KeysExtractor::extract_keys(&resolver); + + assert_debug_snapshot!(keys); + } + + #[test] + fn test_extract_graphql() { + let graphql = GraphQL { + base_url: Some("http://localhost:5051/{{.env.target}}".to_string()), + headers: vec![KeyValue { + key: "test".to_string(), + value: "{{.value.header_test}}".to_string(), + }], + args: Some(vec![KeyValue { + key: "key".to_string(), + value: "test-{{.value.input.key}}".to_string(), + }]), + ..Default::default() + }; + + let resolver = Resolver::Graphql(graphql); + let keys = KeysExtractor::extract_keys(&resolver); + + assert_debug_snapshot!(keys); + } + + #[test] + fn test_extract_call() { + let call = Call { + steps: vec![Step { + query: Some("field".to_string()), + args: [( + "arg".to_string(), + json!(json!({ "a": "{{.value.arg.a}}", "b": "{{.value.arg.b}}"})), + )] + .into_iter() + .collect(), + ..Default::default() + }], + }; + + let resolver = Resolver::Call(call); + let keys = KeysExtractor::extract_keys(&resolver); + + assert_debug_snapshot!(keys); + } + + #[test] + fn test_extract_expr() { + let expr = Expr { + body: json!({ "a": "{{.value.body.a}}", "b": "{{.value.body.b}}"}), + }; + + let resolver = Resolver::Expr(expr); + let keys = KeysExtractor::extract_keys(&resolver); + + assert_debug_snapshot!(keys); + } + } +} diff --git a/src/core/config/transformer/mod.rs b/src/core/config/transformer/mod.rs index e6f2cc6593..b4fe637fe4 100644 --- a/src/core/config/transformer/mod.rs +++ b/src/core/config/transformer/mod.rs @@ -1,5 +1,5 @@ mod ambiguous_type; -mod apollo_federation; +mod apollo_federation_subgraph; mod consolidate_url; mod flatten_single_field; mod improve_type_names; @@ -12,7 +12,7 @@ mod tree_shake; mod union_input_type; pub use ambiguous_type::{AmbiguousType, Resolution}; -pub use apollo_federation::entities::EntityResolver; +pub use apollo_federation_subgraph::ApolloFederationSubgraph; pub use consolidate_url::ConsolidateURL; pub use flatten_single_field::FlattenSingleField; pub use improve_type_names::ImproveTypeNames; diff --git a/src/core/config/transformer/required.rs b/src/core/config/transformer/required.rs index bc5f910483..3fc25e313f 100644 --- a/src/core/config/transformer/required.rs +++ b/src/core/config/transformer/required.rs @@ -15,7 +15,7 @@ impl Transform for Required { config: Self::Value, ) -> crate::core::valid::Valid { transform::default() - .pipe(super::EntityResolver) + .pipe(super::ApolloFederationSubgraph) .pipe(super::NestedUnions) .pipe(super::UnionInputType) .pipe(super::AmbiguousType::default()) diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_call.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_call.snap new file mode 100644 index 0000000000..26e8567487 --- /dev/null +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_call.snap @@ -0,0 +1,11 @@ +--- +source: src/core/config/transformer/apollo_federation_subgraph.rs +expression: keys +--- +Valid( + Ok( + Some( + "arg { a b }", + ), + ), +) diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_expr.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_expr.snap new file mode 100644 index 0000000000..54d1422762 --- /dev/null +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_expr.snap @@ -0,0 +1,11 @@ +--- +source: src/core/config/transformer/apollo_federation_subgraph.rs +expression: keys +--- +Valid( + Ok( + Some( + "body { a b }", + ), + ), +) diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_graphql.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_graphql.snap new file mode 100644 index 0000000000..7f3fc05246 --- /dev/null +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_graphql.snap @@ -0,0 +1,11 @@ +--- +source: src/core/config/transformer/apollo_federation_subgraph.rs +expression: keys +--- +Valid( + Ok( + Some( + "header_test input { key }", + ), + ), +) diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_grpc.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_grpc.snap new file mode 100644 index 0000000000..66f34d0706 --- /dev/null +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_grpc.snap @@ -0,0 +1,11 @@ +--- +source: src/core/config/transformer/apollo_federation_subgraph.rs +expression: keys +--- +Valid( + Ok( + Some( + "body { a b } header_test method", + ), + ), +) diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_http.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_http.snap new file mode 100644 index 0000000000..21904a81cf --- /dev/null +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_http.snap @@ -0,0 +1,11 @@ +--- +source: src/core/config/transformer/apollo_federation_subgraph.rs +expression: keys +--- +Valid( + Ok( + Some( + "header { key value } id obj query_key query_value", + ), + ), +) diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__non_value_template.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__non_value_template.snap new file mode 100644 index 0000000000..9c9cec60b3 --- /dev/null +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__non_value_template.snap @@ -0,0 +1,28 @@ +--- +source: src/core/config/transformer/apollo_federation_subgraph.rs +expression: keys +--- +Valid( + Err( + ValidationError( + [ + Cause { + message: "Type resolver can't use `.args`, use `.value` instead", + description: None, + trace: [ + "http", + "path", + ], + }, + Cause { + message: "Type resolver can't use `.args`, use `.value` instead", + description: None, + trace: [ + "http", + "query", + ], + }, + ], + ), + ), +) diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_merge.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_merge.snap new file mode 100644 index 0000000000..09ddecb5f2 --- /dev/null +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_merge.snap @@ -0,0 +1,32 @@ +--- +source: src/core/config/transformer/apollo_federation_subgraph.rs +expression: keys_a.merge_right(keys_b) +--- +Keys( + { + "a": Keys( + { + "1": Keys( + {}, + ), + "b": Keys( + {}, + ), + }, + ), + "c": Keys( + { + "2": Keys( + {}, + ), + }, + ), + "d": Keys( + { + "3": Keys( + {}, + ), + }, + ), + }, +) diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_set.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_set.snap new file mode 100644 index 0000000000..85fc734916 --- /dev/null +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_set.snap @@ -0,0 +1,32 @@ +--- +source: src/core/config/transformer/apollo_federation_subgraph.rs +expression: keys +--- +Keys( + { + "a": Keys( + { + "b": Keys( + { + "c": Keys( + {}, + ), + }, + ), + "d": Keys( + {}, + ), + }, + ), + "e": Keys( + {}, + ), + "f": Keys( + { + "g": Keys( + {}, + ), + }, + ), + }, +) diff --git a/src/core/helpers/body.rs b/src/core/helpers/body.rs index aa498644d5..0f2b582577 100644 --- a/src/core/helpers/body.rs +++ b/src/core/helpers/body.rs @@ -13,6 +13,7 @@ pub fn to_body(body: Option<&Value>) -> Valid, String> { let value = body.to_string(); let mustache = Mustache::parse(&value); + // TODO: req_body.mustache is always set making req_body.value useless req_body = req_body.mustache(Some(mustache)); Valid::succeed(Some(req_body.value(value))) diff --git a/src/core/ir/eval.rs b/src/core/ir/eval.rs index ec308088f8..e8eb1027ff 100644 --- a/src/core/ir/eval.rs +++ b/src/core/ir/eval.rs @@ -3,11 +3,12 @@ use std::ops::Deref; use async_graphql_value::ConstValue; use futures_util::future::join_all; +use indexmap::IndexMap; use super::eval_io::eval_io; use super::model::{Cache, CacheKey, Map, IR}; use super::{Error, EvalContext, ResolverContextLike}; -use crate::core::json::JsonLike; +use crate::core::json::{JsonLike, JsonObjectLike}; use crate::core::serde_value_ext::ValueExt; // Fake trait to capture proper lifetimes. @@ -136,6 +137,13 @@ impl IR { Ok(ConstValue::List(entities)) } + IR::Service(sdl) => { + let mut obj = IndexMap::new(); + + obj.insert_key("sdl", ConstValue::string(sdl.into())); + + Ok(ConstValue::object(obj)) + } } }) } diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index 6693480a31..79b2ae4baf 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -26,7 +26,10 @@ pub enum IR { Map(Map), Pipe(Box, Box), Discriminate(Discriminator, Box), + /// Apollo Federation _entities resolver EntityResolver(HashMap), + /// Apollo Federation _service resolver + Service(String) } #[derive(Clone, Debug)] @@ -156,6 +159,7 @@ impl IR { .map(|(k, v)| (k, v.modify(modifier))) .collect(), ), + IR::Service(sdl) => IR::Service(sdl), } } } diff --git a/src/core/mustache/parse.rs b/src/core/mustache/parse.rs index 099bb47f5a..a814e22f53 100644 --- a/src/core/mustache/parse.rs +++ b/src/core/mustache/parse.rs @@ -9,7 +9,6 @@ use nom::{Finish, IResult}; use super::*; impl Mustache { - // TODO: infallible function, no need to return Result pub fn parse(str: &str) -> Mustache { let result = parse_mustache(str).finish(); match result { diff --git a/src/core/valid/valid.rs b/src/core/valid/valid.rs index 173897cf85..cda47c82db 100644 --- a/src/core/valid/valid.rs +++ b/src/core/valid/valid.rs @@ -25,6 +25,8 @@ pub trait Validator: Sized { fn is_succeed(&self) -> bool; + fn is_fail(&self) -> bool; + fn and(self, other: Valid) -> Valid { self.zip(other).map(|(_, a1)| a1) } @@ -165,6 +167,10 @@ impl Validator for Valid { fn is_succeed(&self) -> bool { self.0.is_ok() } + + fn is_fail(&self) -> bool { + self.0.is_err() + } } pub struct Fusion(Valid); @@ -184,6 +190,9 @@ impl Validator for Fusion { fn is_succeed(&self) -> bool { self.0.is_succeed() } + fn is_fail(&self) -> bool { + self.0.is_fail() + } } impl From>> for Valid { diff --git a/tailcall-macros/src/resolver.rs b/tailcall-macros/src/resolver.rs index d917a0ad34..99cd446c4f 100644 --- a/tailcall-macros/src/resolver.rs +++ b/tailcall-macros/src/resolver.rs @@ -3,11 +3,11 @@ use quote::quote; use syn::{Attribute, Data, DeriveInput, Fields}; const ATTR_NAMESPACE: &str = "resolver"; -const ATTR_SKIP_TO_DIRECTIVE: &str = "skip_to_directive"; +const ATTR_SKIP_DIRECTIVE: &str = "skip_directive"; #[derive(Default)] struct Attrs { - skip_to_directive: bool, + skip_directive: bool, } fn parse_attrs(attributes: &Vec) -> syn::Result { @@ -16,8 +16,8 @@ fn parse_attrs(attributes: &Vec) -> syn::Result { for attr in attributes { if attr.path().is_ident(ATTR_NAMESPACE) { attr.parse_nested_meta(|meta| { - if meta.path.is_ident(ATTR_SKIP_TO_DIRECTIVE) { - result.skip_to_directive = true; + if meta.path.is_ident(ATTR_SKIP_DIRECTIVE) { + result.skip_directive = true; return Ok(()); } @@ -54,8 +54,12 @@ pub fn expand_resolver_derive(input: DeriveInput) -> syn::Result { panic!("Resolver can only be derived for enums"); }; - let variant_parsers = variants.iter().map(|(variant_name, ty, _attrs)| { - quote! { + let variant_parsers = variants.iter().filter_map(|(variant_name, ty, attrs)| { + if attrs.skip_directive { + return None; + } + + Some(quote! { valid = valid.and(<#ty>::from_directives(directives.iter()).map(|resolver| { if let Some(resolver) = resolver { let directive_name = <#ty>::trace_name(); @@ -65,11 +69,11 @@ pub fn expand_resolver_derive(input: DeriveInput) -> syn::Result { result = Some(Self::#variant_name(resolver)); } })); - } + }) }); let match_arms_to_directive = variants.iter().map(|(variant_name, _ty, attrs)| { - if attrs.skip_to_directive { + if attrs.skip_directive { quote! { Self::#variant_name(d) => None, } @@ -80,9 +84,15 @@ pub fn expand_resolver_derive(input: DeriveInput) -> syn::Result { } }); - let match_arms_directive_name = variants.iter().map(|(variant_name, ty, _attrs)| { - quote! { - Self::#variant_name(_) => <#ty>::directive_name(), + let match_arms_directive_name = variants.iter().map(|(variant_name, ty, attrs)| { + if attrs.skip_directive { + quote! { + Self::#variant_name(_) => String::new(), + } + } else { + quote! { + Self::#variant_name(_) => <#ty>::directive_name(), + } } }); diff --git a/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap b/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap new file mode 100644 index 0000000000..bfa96d6fc7 --- /dev/null +++ b/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap @@ -0,0 +1,17 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "_service": { + "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @graphQL(args: [{key: \"id\", value: \"{{.value.id}}\"}], baseURL: \"http://upstream/graphql\", batch: true, name: \"post\") @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @http(batchKey: [\"id\"], path: \"/users\", query: [{key: \"id\", value: \"{{.value.user.id}}\"}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" + } + } + } +} diff --git a/tests/core/snapshots/apollo-federation-entities-batch.md_client.snap b/tests/core/snapshots/apollo-federation-entities-batch.md_client.snap index bf8395d5a1..1567d25142 100644 --- a/tests/core/snapshots/apollo-federation-entities-batch.md_client.snap +++ b/tests/core/snapshots/apollo-federation-entities-batch.md_client.snap @@ -36,6 +36,10 @@ type Query { Apollo federation Query._entities resolver """ _entities(representations: [_Any]!): [_Entity]! + """ + Apollo federation Query._service resolver + """ + _service: _Service! user(id: Int!): User } @@ -60,6 +64,10 @@ scalar _Any union _Entity = Post | User +type _Service { + sdl: String +} + schema { query: Query } diff --git a/tests/core/snapshots/apollo-federation-entities-batch.md_merged.snap b/tests/core/snapshots/apollo-federation-entities-batch.md_merged.snap index 1ca73a2540..1195b755ff 100644 --- a/tests/core/snapshots/apollo-federation-entities-batch.md_merged.snap +++ b/tests/core/snapshots/apollo-federation-entities-batch.md_merged.snap @@ -12,7 +12,9 @@ scalar _Any union _Entity = Post | User -type Post { +type Post + @graphQL(args: [{key: "id", value: "{{.value.id}}"}], baseURL: "http://upstream/graphql", batch: true, name: "post") + @key(fields: "id") { id: Int! title: String! } @@ -22,10 +24,20 @@ type Query { Apollo federation Query._entities resolver """ _entities(representations: [_Any]!): [_Entity]! + """ + Apollo federation Query._service resolver + """ + _service: _Service! user(id: Int!): User @http(path: "/users/{{.args.id}}") } -type User { +type User + @http(batchKey: ["id"], path: "/users", query: [{key: "id", value: "{{.value.user.id}}"}]) + @key(fields: "user { id }") { id: Int! name: String! } + +type _Service { + sdl: String +} diff --git a/tests/core/snapshots/apollo-federation-entities-validation.md_error.snap b/tests/core/snapshots/apollo-federation-entities-validation.md_error.snap new file mode 100644 index 0000000000..876c8e3ed9 --- /dev/null +++ b/tests/core/snapshots/apollo-federation-entities-validation.md_error.snap @@ -0,0 +1,23 @@ +--- +source: tests/core/spec.rs +expression: errors +--- +[ + { + "message": "Type resolver can't use `.args`, use `.value` instead", + "trace": [ + "Post", + "http", + "query" + ], + "description": null + }, + { + "message": "Type resolver can't use `.args`, use `.value` instead", + "trace": [ + "User", + "call" + ], + "description": null + } +] diff --git a/tests/core/snapshots/apollo-federation-entities.md_1.snap b/tests/core/snapshots/apollo-federation-entities.md_1.snap new file mode 100644 index 0000000000..b516301809 --- /dev/null +++ b/tests/core/snapshots/apollo-federation-entities.md_1.snap @@ -0,0 +1,17 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "_service": { + "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @expr(body: {id: \"{{.value.id}}\", title: \"post-title-{{.value.id}}\"}) @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @call(steps: [{query: \"user\", args: {id: \"{{.value.user.id}}\"}}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" + } + } + } +} diff --git a/tests/core/snapshots/apollo-federation-entities.md_client.snap b/tests/core/snapshots/apollo-federation-entities.md_client.snap index bf8395d5a1..1567d25142 100644 --- a/tests/core/snapshots/apollo-federation-entities.md_client.snap +++ b/tests/core/snapshots/apollo-federation-entities.md_client.snap @@ -36,6 +36,10 @@ type Query { Apollo federation Query._entities resolver """ _entities(representations: [_Any]!): [_Entity]! + """ + Apollo federation Query._service resolver + """ + _service: _Service! user(id: Int!): User } @@ -60,6 +64,10 @@ scalar _Any union _Entity = Post | User +type _Service { + sdl: String +} + schema { query: Query } diff --git a/tests/core/snapshots/apollo-federation-entities.md_merged.snap b/tests/core/snapshots/apollo-federation-entities.md_merged.snap index 1ca73a2540..c65c5d2ad8 100644 --- a/tests/core/snapshots/apollo-federation-entities.md_merged.snap +++ b/tests/core/snapshots/apollo-federation-entities.md_merged.snap @@ -12,7 +12,7 @@ scalar _Any union _Entity = Post | User -type Post { +type Post @expr(body: {id: "{{.value.id}}", title: "post-title-{{.value.id}}"}) @key(fields: "id") { id: Int! title: String! } @@ -22,10 +22,18 @@ type Query { Apollo federation Query._entities resolver """ _entities(representations: [_Any]!): [_Entity]! + """ + Apollo federation Query._service resolver + """ + _service: _Service! user(id: Int!): User @http(path: "/users/{{.args.id}}") } -type User { +type User @call(steps: [{query: "user", args: {id: "{{.value.user.id}}"}}]) @key(fields: "user { id }") { id: Int! name: String! } + +type _Service { + sdl: String +} diff --git a/tests/execution/apollo-federation-entities-batch.md b/tests/execution/apollo-federation-entities-batch.md index 800927ec4a..5f0ff16bc2 100644 --- a/tests/execution/apollo-federation-entities-batch.md +++ b/tests/execution/apollo-federation-entities-batch.md @@ -1,4 +1,4 @@ -# Apollo federation \_entities query +# Apollo federation query for batching resolvers ```graphql @config schema @@ -11,7 +11,7 @@ type Query { user(id: Int!): User @http(path: "/users/{{.args.id}}") } -type User @http(path: "/users", query: [{key: "id", value: "{{.value.id}}"}], batchKey: ["id"]) { +type User @http(path: "/users", query: [{key: "id", value: "{{.value.user.id}}"}], batchKey: ["id"]) { id: Int! name: String! } @@ -90,8 +90,8 @@ type Post query: > { _entities(representations: [ - {id: 1, __typename: "User"} - {id: 2, __typename: "User"} + {user: { id: 1 }, __typename: "User"} + {user: { id: 2 }, __typename: "User"} {id: 3, __typename: "Post"} {id: 5, __typename: "Post"} ]) { @@ -106,4 +106,10 @@ type Post } } } + +- method: POST + url: http://localhost:8080/graphql + body: + query: > + { _service { sdl } } ``` diff --git a/tests/execution/apollo-federation-entities-validation.md b/tests/execution/apollo-federation-entities-validation.md new file mode 100644 index 0000000000..2d43c04af4 --- /dev/null +++ b/tests/execution/apollo-federation-entities-validation.md @@ -0,0 +1,78 @@ +--- +error: true +--- + +# Apollo federation query validation + +```graphql @config +schema + @server(port: 8000) + @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) { + query: Query +} + +type Query { + user(id: Int!): User @http(path: "/users/{{.args.id}}") +} + +type User @call(steps: [{query: "user", args: {id: "{{.args.id}}"}}]) { + id: Int! + name: String! +} + +type Post @http(path: "/users", query: [{key: "id", value: "{{.args.user.id}}"}]) { + id: Int! + title: String! +} +``` + +```yml @mock +- request: + method: GET + url: http://jsonplaceholder.typicode.com/users/1 + response: + status: 200 + body: + id: 1 + name: Leanne Graham + +- request: + method: GET + url: http://jsonplaceholder.typicode.com/users/2 + response: + status: 200 + body: + id: 2 + name: Ervin Howell +``` + +```yml @test +- method: POST + url: http://localhost:8080/graphql + body: + query: > + { + _entities(representations: [ + {user: { id: 1 }, __typename: "User"} + {user: { id: 2 }, __typename: "User"} + {user: { id: 3 }, __typename: "Post"} + {user: { id: 5 }, __typename: "Post"} + ]) { + __typename + ...on User { + id + name + } + ...on Post { + id + title + } + } + } + +- method: POST + url: http://localhost:8080/graphql + body: + query: > + { _service { sdl } } +``` diff --git a/tests/execution/apollo-federation-entities.md b/tests/execution/apollo-federation-entities.md index 93b8388412..ea5e4eabf9 100644 --- a/tests/execution/apollo-federation-entities.md +++ b/tests/execution/apollo-federation-entities.md @@ -1,4 +1,4 @@ -# Apollo federation \_entities query +# Apollo federation query ```graphql @config schema @@ -11,7 +11,7 @@ type Query { user(id: Int!): User @http(path: "/users/{{.args.id}}") } -type User @call(steps: [{query: "user", args: {id: "{{.value.id}}"}}]) { +type User @call(steps: [{query: "user", args: {id: "{{.value.user.id}}"}}]) { id: Int! name: String! } @@ -49,8 +49,8 @@ type Post @expr(body: {id: "{{.value.id}}", title: "post-title-{{.value.id}}"}) query: > { _entities(representations: [ - {id: 1, __typename: "User"} - {id: 2, __typename: "User"} + {user: { id: 1 }, __typename: "User"} + {user: { id: 2 }, __typename: "User"} {id: 3, __typename: "Post"} {id: 5, __typename: "Post"} ]) { @@ -65,4 +65,10 @@ type Post @expr(body: {id: "{{.value.id}}", title: "post-title-{{.value.id}}"}) } } } + +- method: POST + url: http://localhost:8080/graphql + body: + query: > + { _service { sdl } } ``` From 701e37b6548897862f6cbbfa9ebfa3d95399c161 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Wed, 21 Aug 2024 07:29:09 +0000 Subject: [PATCH 06/17] fix lint --- generated/.tailcallrc.schema.json | 150 +++--------------- .../blueprint/operators/apollo_federation.rs | 4 +- src/core/blueprint/operators/mod.rs | 4 +- src/core/config/config.rs | 8 +- src/core/config/directives/call.rs | 3 +- src/core/config/directives/expr.rs | 23 ++- src/core/config/directives/federation.rs | 6 +- src/core/config/directives/graphql.rs | 4 +- src/core/config/directives/grpc.rs | 5 +- src/core/config/directives/http.rs | 11 +- src/core/config/directives/js.rs | 21 ++- src/core/config/directives/mod.rs | 14 +- src/core/config/mod.rs | 4 +- .../transformer/apollo_federation_subgraph.rs | 52 +++--- src/core/ir/model.rs | 2 +- tailcall-macros/src/merge_right.rs | 4 +- 16 files changed, 103 insertions(+), 212 deletions(-) diff --git a/generated/.tailcallrc.schema.json b/generated/.tailcallrc.schema.json index 3e9a9554a6..64f0e59c63 100644 --- a/generated/.tailcallrc.schema.json +++ b/generated/.tailcallrc.schema.json @@ -336,20 +336,6 @@ "ApplicationXWwwFormUrlencoded" ] }, - "EntityResolver": { - "type": "object", - "required": [ - "resolver_by_type" - ], - "properties": { - "resolver_by_type": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Resolver" - } - } - } - }, "Enum": { "description": "Definition of GraphQL enum type", "type": "object", @@ -458,18 +444,6 @@ } }, "additionalProperties": false - }, - { - "type": "object", - "required": [ - "entityResolver" - ], - "properties": { - "entityResolver": { - "$ref": "#/definitions/EntityResolver" - } - }, - "additionalProperties": false } ], "properties": { @@ -817,6 +791,18 @@ "title": "JSON", "description": "Field whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259)." }, + "Key": { + "description": "Directive `@key` for Apollo Federation", + "type": "object", + "required": [ + "fields" + ], + "properties": { + "fields": { + "type": "string" + } + } + }, "KeyValue": { "type": "object", "required": [ @@ -966,94 +952,6 @@ } } }, - "Resolver": { - "oneOf": [ - { - "type": "object", - "required": [ - "http" - ], - "properties": { - "http": { - "$ref": "#/definitions/Http" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "grpc" - ], - "properties": { - "grpc": { - "$ref": "#/definitions/Grpc" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "graphql" - ], - "properties": { - "graphql": { - "$ref": "#/definitions/GraphQL" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "call" - ], - "properties": { - "call": { - "$ref": "#/definitions/Call" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "js" - ], - "properties": { - "js": { - "$ref": "#/definitions/JS" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "expr" - ], - "properties": { - "expr": { - "$ref": "#/definitions/Expr" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "entityResolver" - ], - "properties": { - "entityResolver": { - "$ref": "#/definitions/EntityResolver" - } - }, - "additionalProperties": false - } - ] - }, "RootSchema": { "type": "object", "properties": { @@ -1418,18 +1316,6 @@ } }, "additionalProperties": false - }, - { - "type": "object", - "required": [ - "entityResolver" - ], - "properties": { - "entityResolver": { - "$ref": "#/definitions/EntityResolver" - } - }, - "additionalProperties": false } ], "required": [ @@ -1476,6 +1362,18 @@ }, "uniqueItems": true }, + "key": { + "description": "Apollo federation key directive. skip since it's set automatically by config transformer", + "writeOnly": true, + "anyOf": [ + { + "$ref": "#/definitions/Key" + }, + { + "type": "null" + } + ] + }, "protected": { "description": "Marks field as protected by auth providers", "default": null, diff --git a/src/core/blueprint/operators/apollo_federation.rs b/src/core/blueprint/operators/apollo_federation.rs index 555ad1fd5c..19c04abe57 100644 --- a/src/core/blueprint/operators/apollo_federation.rs +++ b/src/core/blueprint/operators/apollo_federation.rs @@ -2,13 +2,13 @@ use std::collections::HashMap; use std::fmt::Write; use super::{compile_call, compile_expr, compile_graphql, compile_grpc, compile_http, compile_js}; +use crate::core::blueprint::FieldDefinition; use crate::core::config::{ - ConfigModule, EntityResolver, Field, GraphQLOperationType, Resolver, Type, + ApolloFederation, ConfigModule, EntityResolver, Field, GraphQLOperationType, Resolver, Type, }; use crate::core::ir::model::IR; use crate::core::try_fold::TryFold; use crate::core::valid::{Valid, Validator}; -use crate::core::{blueprint::FieldDefinition, config::ApolloFederation}; pub struct CompileEntityResolver<'a> { config_module: &'a ConfigModule, diff --git a/src/core/blueprint/operators/mod.rs b/src/core/blueprint/operators/mod.rs index c927df111e..2b4842899e 100644 --- a/src/core/blueprint/operators/mod.rs +++ b/src/core/blueprint/operators/mod.rs @@ -1,5 +1,5 @@ -mod call; mod apollo_federation; +mod call; mod enum_alias; mod expr; mod graphql; @@ -9,8 +9,8 @@ mod js; mod modify; mod protected; -pub use call::*; pub use apollo_federation::*; +pub use call::*; pub use enum_alias::*; pub use expr::*; pub use graphql::*; diff --git a/src/core/config/config.rs b/src/core/config/config.rs index f0aea92bf9..c1782e6af5 100644 --- a/src/core/config/config.rs +++ b/src/core/config/config.rs @@ -13,11 +13,9 @@ use tailcall_typedefs_common::directive_definition::DirectiveDefinition; use tailcall_typedefs_common::input_definition::InputDefinition; use tailcall_typedefs_common::ServiceDocumentBuilder; -use super::{ - directives::{Call, EntityResolver, Expr, GraphQL, Grpc, Http, Key, JS}, - from_document::from_document, - telemetry::Telemetry, -}; +use super::directives::{Call, EntityResolver, Expr, GraphQL, Grpc, Http, Key, JS}; +use super::from_document::from_document; +use super::telemetry::Telemetry; use super::{Link, Server, Upstream}; use crate::core::config::npo::QueryPath; use crate::core::config::source::Source; diff --git a/src/core/config/directives/call.rs b/src/core/config/directives/call.rs index 0ab52a018f..a973dabdf0 100644 --- a/src/core/config/directives/call.rs +++ b/src/core/config/directives/call.rs @@ -1,10 +1,11 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; -use crate::core::is_default; use serde_json::Value; use tailcall_macros::DirectiveDefinition; +use crate::core::is_default; + /// /// Provides the ability to refer to a field defined in the root Query or /// Mutation. diff --git a/src/core/config/directives/expr.rs b/src/core/config/directives/expr.rs index bd224f46dc..c5c7c694d0 100644 --- a/src/core/config/directives/expr.rs +++ b/src/core/config/directives/expr.rs @@ -1,18 +1,17 @@ use serde::{Deserialize, Serialize}; -use tailcall_macros::{DirectiveDefinition, InputDefinition}; use serde_json::Value; - +use tailcall_macros::{DirectiveDefinition, InputDefinition}; #[derive( - Serialize, - Deserialize, - Clone, - Debug, - PartialEq, - Eq, - schemars::JsonSchema, - DirectiveDefinition, - InputDefinition, + Serialize, + Deserialize, + Clone, + Debug, + PartialEq, + Eq, + schemars::JsonSchema, + DirectiveDefinition, + InputDefinition, )] #[directive_definition(locations = "FieldDefinition")] #[serde(deny_unknown_fields)] @@ -20,5 +19,5 @@ use serde_json::Value; /// to a value. The expression can be a static value or built form a Mustache /// template. schema. pub struct Expr { - pub body: Value, + pub body: Value, } diff --git a/src/core/config/directives/federation.rs b/src/core/config/directives/federation.rs index 7015eb38dc..d956e9b6b6 100644 --- a/src/core/config/directives/federation.rs +++ b/src/core/config/directives/federation.rs @@ -2,12 +2,14 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; use tailcall_macros::MergeRight; -use crate::core::merge_right::MergeRight; use crate::core::config::Resolver; +use crate::core::merge_right::MergeRight; /// Directive `@key` for Apollo Federation -#[derive(Serialize, Deserialize,Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight)] +#[derive( + Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight, +)] pub struct Key { pub fields: String, } diff --git a/src/core/config/directives/graphql.rs b/src/core/config/directives/graphql.rs index fa6d7c7ff6..8161aecf6f 100644 --- a/src/core/config/directives/graphql.rs +++ b/src/core/config/directives/graphql.rs @@ -1,7 +1,9 @@ use serde::{Deserialize, Serialize}; -use crate::core::{config::KeyValue, is_default}; use tailcall_macros::{DirectiveDefinition, InputDefinition}; +use crate::core::config::KeyValue; +use crate::core::is_default; + #[derive( Serialize, Deserialize, diff --git a/src/core/config/directives/grpc.rs b/src/core/config/directives/grpc.rs index 30097b1592..573f663377 100644 --- a/src/core/config/directives/grpc.rs +++ b/src/core/config/directives/grpc.rs @@ -1,9 +1,10 @@ -use crate::core::config::KeyValue; -use crate::core::is_default; use serde::{Deserialize, Serialize}; use serde_json::Value; use tailcall_macros::{DirectiveDefinition, InputDefinition}; +use crate::core::config::KeyValue; +use crate::core::is_default; + #[derive( Serialize, Deserialize, diff --git a/src/core/config/directives/http.rs b/src/core/config/directives/http.rs index ee38379aa5..5a19116d84 100644 --- a/src/core/config/directives/http.rs +++ b/src/core/config/directives/http.rs @@ -1,12 +1,11 @@ -use crate::core::{ - config::{Encoding, KeyValue, URLQuery}, - http::Method, - is_default, - json::JsonSchema, -}; use serde::{Deserialize, Serialize}; use tailcall_macros::{DirectiveDefinition, InputDefinition}; +use crate::core::config::{Encoding, KeyValue, URLQuery}; +use crate::core::http::Method; +use crate::core::is_default; +use crate::core::json::JsonSchema; + #[derive( Serialize, Deserialize, diff --git a/src/core/config/directives/js.rs b/src/core/config/directives/js.rs index 12269f16f9..3b15bb4e87 100644 --- a/src/core/config/directives/js.rs +++ b/src/core/config/directives/js.rs @@ -1,19 +1,18 @@ use serde::{Deserialize, Serialize}; use tailcall_macros::{DirectiveDefinition, InputDefinition}; - #[derive( - Serialize, - Deserialize, - Clone, - Debug, - PartialEq, - Eq, - schemars::JsonSchema, - DirectiveDefinition, - InputDefinition, + Serialize, + Deserialize, + Clone, + Debug, + PartialEq, + Eq, + schemars::JsonSchema, + DirectiveDefinition, + InputDefinition, )] #[directive_definition(locations = "FieldDefinition", lowercase_name)] pub struct JS { - pub name: String, + pub name: String, } diff --git a/src/core/config/directives/mod.rs b/src/core/config/directives/mod.rs index c328ed06e7..820286d2c3 100644 --- a/src/core/config/directives/mod.rs +++ b/src/core/config/directives/mod.rs @@ -1,15 +1,15 @@ -mod federation; -mod http; -mod grpc; -mod graphql; mod call; mod expr; +mod federation; +mod graphql; +mod grpc; +mod http; mod js; +pub use call::*; pub use expr::*; pub use federation::*; -pub use http::*; -pub use grpc::*; pub use graphql::*; -pub use call::*; +pub use grpc::*; +pub use http::*; pub use js::*; diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 3dbd445be6..7b8ae927a0 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -1,6 +1,7 @@ pub use apollo::*; pub use config::*; pub use config_module::*; +pub use directives::*; pub use key_values::*; pub use link::*; pub use npo::QueryPath; @@ -10,11 +11,11 @@ pub use source::*; pub use telemetry::*; pub use upstream::*; pub use url_query::*; -pub use directives::*; mod apollo; mod config; mod config_module; pub mod cors; +pub mod directives; mod from_document; pub mod group_by; mod headers; @@ -30,4 +31,3 @@ mod telemetry; pub mod transformer; mod upstream; mod url_query; -pub mod directives; diff --git a/src/core/config/transformer/apollo_federation_subgraph.rs b/src/core/config/transformer/apollo_federation_subgraph.rs index da12dddd3d..d29c4eaf08 100644 --- a/src/core/config/transformer/apollo_federation_subgraph.rs +++ b/src/core/config/transformer/apollo_federation_subgraph.rs @@ -1,27 +1,21 @@ -use std::{ - borrow::Borrow, - collections::BTreeMap, - convert::identity, - fmt::{Display, Write}, - ops::Deref, -}; - -use crate::core::{ - config::{self, Arg, Config, Field, Grpc, Key, KeyValue, Resolver, Type, Union}, - directive::DirectiveCodec, -}; -use crate::core::{ - config::{ApolloFederation, Http}, - merge_right::MergeRight, -}; -use crate::core::{ - config::{Call, GraphQL}, - valid::Valid, -}; -use crate::core::{mustache::Segment, valid::Validator, Mustache, Transform}; +use std::borrow::Borrow; +use std::collections::BTreeMap; +use std::convert::identity; +use std::fmt::{Display, Write}; +use std::ops::Deref; use tailcall_macros::MergeRight; +use crate::core::config::{ + self, ApolloFederation, Arg, Call, Config, Field, GraphQL, Grpc, Http, Key, KeyValue, Resolver, + Type, Union, +}; +use crate::core::directive::DirectiveCodec; +use crate::core::merge_right::MergeRight; +use crate::core::mustache::Segment; +use crate::core::valid::{Valid, Validator}; +use crate::core::{Mustache, Transform}; + const ENTITIES_FIELD_NAME: &str = "_entities"; const SERVICE_FIELD_NAME: &str = "_service"; const SERVICE_TYPE_NAME: &str = "_Service"; @@ -283,7 +277,7 @@ impl KeysExtractor { } } - fn parse_key_value_iter<'a, T: Borrow>( + fn parse_key_value_iter>( it: impl Iterator, ) -> Valid { let mut keys = Keys::new(); @@ -298,7 +292,7 @@ impl KeysExtractor { .map_to(keys) } - fn parse_key_value_iter_option<'a, T: Borrow>( + fn parse_key_value_iter_option>( it: Option>, ) -> Valid { if let Some(it) = it { @@ -311,7 +305,7 @@ impl KeysExtractor { fn parse_value(value: &serde_json::Value) -> Valid { match value { serde_json::Value::String(s) => return Self::parse_str(s), - serde_json::Value::Array(v) => Valid::from_iter(v.iter(), |v| Self::parse_value(v)), + serde_json::Value::Array(v) => Valid::from_iter(v.iter(), Self::parse_value), serde_json::Value::Object(map) => Valid::from_iter(map.iter(), |(k, v)| { Self::parse_str(k) .zip(Self::parse_value(v)) @@ -376,12 +370,10 @@ mod tests { use insta::assert_debug_snapshot; use serde_json::json; - use crate::core::{ - config::{Call, Expr, GraphQL, Grpc, Step, URLQuery}, - http::Method, - }; - - use super::{config::Http, KeyValue, KeysExtractor, Resolver}; + use super::config::Http; + use super::{KeyValue, KeysExtractor, Resolver}; + use crate::core::config::{Call, Expr, GraphQL, Grpc, Step, URLQuery}; + use crate::core::http::Method; #[test] fn test_non_value_template() { diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index 79b2ae4baf..a3642a8e17 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -29,7 +29,7 @@ pub enum IR { /// Apollo Federation _entities resolver EntityResolver(HashMap), /// Apollo Federation _service resolver - Service(String) + Service(String), } #[derive(Clone, Debug)] diff --git a/tailcall-macros/src/merge_right.rs b/tailcall-macros/src/merge_right.rs index 260aa992c9..03c8556ca0 100644 --- a/tailcall-macros/src/merge_right.rs +++ b/tailcall-macros/src/merge_right.rs @@ -2,8 +2,8 @@ extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; -use syn::{spanned::Spanned, Index}; -use syn::{parse_macro_input, Data, DeriveInput, Fields}; +use syn::spanned::Spanned; +use syn::{parse_macro_input, Data, DeriveInput, Fields, Index}; const MERGE_RIGHT_FN: &str = "merge_right_fn"; const MERGE_RIGHT: &str = "merge_right"; From eb5e2b5edee987620157f44b997718bdab480025 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Wed, 21 Aug 2024 07:35:34 +0000 Subject: [PATCH 07/17] rename example --- ...ration_entities.graphql => apollo_federation_subgraph.graphql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{apollo_federation_entities.graphql => apollo_federation_subgraph.graphql} (100%) diff --git a/examples/apollo_federation_entities.graphql b/examples/apollo_federation_subgraph.graphql similarity index 100% rename from examples/apollo_federation_entities.graphql rename to examples/apollo_federation_subgraph.graphql From 7ea9b467c5bde2d7d26d8c23fec082d3d147d137 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:28:41 +0000 Subject: [PATCH 08/17] allow resolvable directives on types --- generated/.tailcallrc.graphql | 12 ++++++------ src/core/config/directives/call.rs | 2 +- src/core/config/directives/expr.rs | 2 +- src/core/config/directives/graphql.rs | 2 +- src/core/config/directives/grpc.rs | 2 +- src/core/config/directives/http.rs | 2 +- src/core/config/directives/js.rs | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/generated/.tailcallrc.graphql b/generated/.tailcallrc.graphql index a2ba1acf44..cba1d003f8 100644 --- a/generated/.tailcallrc.graphql +++ b/generated/.tailcallrc.graphql @@ -40,7 +40,7 @@ directive @call( of the previous step is passed as input to the next step. """ steps: [Step] -) on FIELD_DEFINITION +) on FIELD_DEFINITION | OBJECT """ The `@expr` operators allows you to specify an expression that can evaluate to a @@ -48,7 +48,7 @@ value. The expression can be a static value or built form a Mustache template. s """ directive @expr( body: JSON -) on FIELD_DEFINITION +) on FIELD_DEFINITION | OBJECT """ The @graphQL operator allows to specify GraphQL API server request to fetch data @@ -82,7 +82,7 @@ directive @graphQL( field, Tailcall requests data from the corresponding upstream field. """ name: String! -) on FIELD_DEFINITION +) on FIELD_DEFINITION | OBJECT """ The @grpc operator indicates that a field or node is backed by a gRPC API.For instance, @@ -120,7 +120,7 @@ directive @grpc( This refers to the gRPC method you're going to call. For instance `GetAllNews`. """ method: String! -) on FIELD_DEFINITION +) on FIELD_DEFINITION | OBJECT """ The @http operator indicates that a field or node is backed by a REST API.For instance, @@ -190,11 +190,11 @@ directive @http( is automatically selected as the batching parameter. """ query: [URLQuery] -) on FIELD_DEFINITION +) on FIELD_DEFINITION | OBJECT directive @js( name: String! -) on FIELD_DEFINITION +) on FIELD_DEFINITION | OBJECT """ The @link directive allows you to import external resources, such as configuration diff --git a/src/core/config/directives/call.rs b/src/core/config/directives/call.rs index a973dabdf0..250f7bebf2 100644 --- a/src/core/config/directives/call.rs +++ b/src/core/config/directives/call.rs @@ -38,7 +38,7 @@ pub struct Step { schemars::JsonSchema, DirectiveDefinition, )] -#[directive_definition(locations = "FieldDefinition")] +#[directive_definition(locations = "FieldDefinition, Object")] pub struct Call { /// Steps are composed together to form a call. /// If you have multiple steps, the output of the previous step is passed as diff --git a/src/core/config/directives/expr.rs b/src/core/config/directives/expr.rs index c5c7c694d0..39dcced1ce 100644 --- a/src/core/config/directives/expr.rs +++ b/src/core/config/directives/expr.rs @@ -13,7 +13,7 @@ use tailcall_macros::{DirectiveDefinition, InputDefinition}; DirectiveDefinition, InputDefinition, )] -#[directive_definition(locations = "FieldDefinition")] +#[directive_definition(locations = "FieldDefinition, Object")] #[serde(deny_unknown_fields)] /// The `@expr` operators allows you to specify an expression that can evaluate /// to a value. The expression can be a static value or built form a Mustache diff --git a/src/core/config/directives/graphql.rs b/src/core/config/directives/graphql.rs index 8161aecf6f..e9bef555d1 100644 --- a/src/core/config/directives/graphql.rs +++ b/src/core/config/directives/graphql.rs @@ -16,7 +16,7 @@ use crate::core::is_default; DirectiveDefinition, InputDefinition, )] -#[directive_definition(locations = "FieldDefinition")] +#[directive_definition(locations = "FieldDefinition, Object")] #[serde(deny_unknown_fields)] /// The @graphQL operator allows to specify GraphQL API server request to fetch /// data from. diff --git a/src/core/config/directives/grpc.rs b/src/core/config/directives/grpc.rs index 573f663377..9ee9a6a126 100644 --- a/src/core/config/directives/grpc.rs +++ b/src/core/config/directives/grpc.rs @@ -17,7 +17,7 @@ use crate::core::is_default; InputDefinition, DirectiveDefinition, )] -#[directive_definition(locations = "FieldDefinition")] +#[directive_definition(locations = "FieldDefinition, Object")] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] /// The @grpc operator indicates that a field or node is backed by a gRPC API. diff --git a/src/core/config/directives/http.rs b/src/core/config/directives/http.rs index 5a19116d84..35e4ff3ca4 100644 --- a/src/core/config/directives/http.rs +++ b/src/core/config/directives/http.rs @@ -18,7 +18,7 @@ use crate::core::json::JsonSchema; DirectiveDefinition, InputDefinition, )] -#[directive_definition(locations = "FieldDefinition")] +#[directive_definition(locations = "FieldDefinition, Object")] #[serde(deny_unknown_fields)] /// The @http operator indicates that a field or node is backed by a REST API. /// diff --git a/src/core/config/directives/js.rs b/src/core/config/directives/js.rs index 3b15bb4e87..60f307befc 100644 --- a/src/core/config/directives/js.rs +++ b/src/core/config/directives/js.rs @@ -12,7 +12,7 @@ use tailcall_macros::{DirectiveDefinition, InputDefinition}; DirectiveDefinition, InputDefinition, )] -#[directive_definition(locations = "FieldDefinition", lowercase_name)] +#[directive_definition(locations = "FieldDefinition, Object", lowercase_name)] pub struct JS { pub name: String, } From e3b0df33bf654c2ab4bb02ce5b6e24cef85793a3 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:00:41 +0000 Subject: [PATCH 09/17] provide tailcall specific directives to sdl --- src/core/blueprint/operators/apollo_federation.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/blueprint/operators/apollo_federation.rs b/src/core/blueprint/operators/apollo_federation.rs index 19c04abe57..fdc4eb1041 100644 --- a/src/core/blueprint/operators/apollo_federation.rs +++ b/src/core/blueprint/operators/apollo_federation.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::fmt::Write; use super::{compile_call, compile_expr, compile_graphql, compile_grpc, compile_http, compile_js}; -use crate::core::blueprint::FieldDefinition; +use crate::core::{blueprint::FieldDefinition, config::Config}; use crate::core::config::{ ApolloFederation, ConfigModule, EntityResolver, Field, GraphQLOperationType, Resolver, Type, }; @@ -79,6 +79,9 @@ pub fn compile_entity_resolver(inputs: CompileEntityResolver<'_>) -> Valid Valid { let mut sdl = config.to_sdl(); + writeln!(sdl).ok(); + // Add tailcall specific definitions to the sdl output + writeln!(sdl, "{}", crate::core::document::print(Config::graphql_schema())).ok(); writeln!(sdl).ok(); // Mark subgraph as Apollo federation v2 compatible according to [docs](https://www.apollographql.com/docs/apollo-server/using-federation/apollo-subgraph-setup/#2-opt-in-to-federation-2) // (borrowed from async_graphql) From f7dd6e9ee4afe9c6cd911714b8cf08432576e074 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:00:47 +0000 Subject: [PATCH 10/17] update example --- ...> apollo_federation_subgraph_post.graphql} | 11 +- .../apollo_federation_subgraph_user.graphql | 18 + examples/federation/README.md | 25 + examples/federation/gateway.js | 20 + examples/federation/package-lock.json | 3117 +++++++++++++++++ examples/federation/package.json | 18 + examples/federation/rover.sh | 18 + 7 files changed, 3219 insertions(+), 8 deletions(-) rename examples/{apollo_federation_subgraph.graphql => apollo_federation_subgraph_post.graphql} (62%) create mode 100644 examples/apollo_federation_subgraph_user.graphql create mode 100644 examples/federation/README.md create mode 100644 examples/federation/gateway.js create mode 100644 examples/federation/package-lock.json create mode 100644 examples/federation/package.json create mode 100755 examples/federation/rover.sh diff --git a/examples/apollo_federation_subgraph.graphql b/examples/apollo_federation_subgraph_post.graphql similarity index 62% rename from examples/apollo_federation_subgraph.graphql rename to examples/apollo_federation_subgraph_post.graphql index 7f82825983..52d055d7fb 100644 --- a/examples/apollo_federation_subgraph.graphql +++ b/examples/apollo_federation_subgraph_post.graphql @@ -1,20 +1,15 @@ schema - @server(port: 8000) + @server(port: 8001) @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) { query: Query } type Query { - user(id: Int!): User @http(path: "/users/{{.args.id}}") + posts: [Post] @http(path: "/posts") } type User @http(path: "/users", query: [{key: "id", value: "{{.value.id}}"}], batchKey: ["id"]) { id: Int! - name: String! - username: String! - email: String! - phone: String - website: String } type Post @http(path: "/posts", query: [{key: "id", value: "{{.value.id}}"}], batchKey: ["id"]) { @@ -22,5 +17,5 @@ type Post @http(path: "/posts", query: [{key: "id", value: "{{.value.id}}"}], ba userId: Int! title: String! body: String! - user: User @call(steps: [{query: "user", args: {id: "{{.value.userId}}"}}]) + user: User @expr(body: { id: "{{.value.userId}}" }) } diff --git a/examples/apollo_federation_subgraph_user.graphql b/examples/apollo_federation_subgraph_user.graphql new file mode 100644 index 0000000000..f904e58781 --- /dev/null +++ b/examples/apollo_federation_subgraph_user.graphql @@ -0,0 +1,18 @@ +schema + @server(port: 8002) + @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) { + query: Query +} + +type Query { + user(id: Int!): User @http(path: "/users/{{.args.id}}") +} + +type User @http(path: "/users", query: [{key: "id", value: "{{.value.id}}"}], batchKey: ["id"]) { + id: Int! + name: String + username: String + email: String + phone: String + website: String +} diff --git a/examples/federation/README.md b/examples/federation/README.md new file mode 100644 index 0000000000..664ff8d689 --- /dev/null +++ b/examples/federation/README.md @@ -0,0 +1,25 @@ +# Apollo Federation example + +1. Start tailcall subgraph examples: + - `cargo run -- start examples/apollo_federation_subgraph_post.graphql` + - `cargo run -- start examples/apollo_federation_subgraph_user.graphql` +2. Run Apollo router by one of the following methods: + - run `@apollo/gateway` with `npm start` (with `npm install` for the first time) from "examples/federation" folder + - start apollo router with `rover.sh` script (install [apollo rover](https://www.apollographql.com/docs/rover) first) +3. Navigate to `http://localhost:4000` and execute supergraph queries, see [examples](#query-examples) + +# Query examples + +```graphql +{ + posts { + id + title + user { + id + name + } + } +} + +``` diff --git a/examples/federation/gateway.js b/examples/federation/gateway.js new file mode 100644 index 0000000000..0c7bdd565a --- /dev/null +++ b/examples/federation/gateway.js @@ -0,0 +1,20 @@ +import { ApolloServer } from '@apollo/server'; +import { startStandaloneServer } from '@apollo/server/standalone'; +import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway'; + +const gateway = new ApolloGateway({ + supergraphSdl: new IntrospectAndCompose({ + subgraphs: [ + { name: 'post', url: 'http://localhost:8001/graphql' }, + { name: 'user', url: 'http://localhost:8002/graphql' }, + ], + }), +}); + +const server = new ApolloServer({ + gateway, + introspection: true, +}); + +const { url } = await startStandaloneServer(server); +console.log(`šŸš€ Server ready at ${url}`); diff --git a/examples/federation/package-lock.json b/examples/federation/package-lock.json new file mode 100644 index 0000000000..c225c43e3f --- /dev/null +++ b/examples/federation/package-lock.json @@ -0,0 +1,3117 @@ +{ + "name": "federation", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "federation", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@apollo/gateway": "^2.8.4", + "@apollo/server": "^4.11.0", + "graphql": "^16.9.0" + } + }, + "node_modules/@apollo/cache-control-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz", + "integrity": "sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==", + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/composition": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@apollo/composition/-/composition-2.8.4.tgz", + "integrity": "sha512-x8USTfHAtauW2BrXZo10gFV9yoF3TIfKUu+s5tJVm/vX/zgw3OG54TC34v5c/5pMsStcZu/aYFnMkeZ54Ay7tQ==", + "dependencies": { + "@apollo/federation-internals": "2.8.4", + "@apollo/query-graphs": "2.8.4" + }, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "graphql": "^16.5.0" + } + }, + "node_modules/@apollo/federation-internals": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@apollo/federation-internals/-/federation-internals-2.8.4.tgz", + "integrity": "sha512-m/vFu5btNfmvxZfe8B1m8jjCN/NxCYctxjdhXgQD4WGbDwtUk59+i7NuVMtX5IfmFMKycwqnbihkv5w2E00XDA==", + "dependencies": { + "@types/uuid": "^9.0.0", + "chalk": "^4.1.0", + "js-levenshtein": "^1.1.6", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "graphql": "^16.5.0" + } + }, + "node_modules/@apollo/gateway": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@apollo/gateway/-/gateway-2.8.4.tgz", + "integrity": "sha512-KxYRvRfSLDMqyOlUu9ZuOaqZLvKwGHR3F6XSw8JmHvMUjcRzVeAY6U9821ruM9g+SdYYdm0q2jYJf4/iYPAcow==", + "dependencies": { + "@apollo/composition": "2.8.4", + "@apollo/federation-internals": "2.8.4", + "@apollo/query-planner": "2.8.4", + "@apollo/server-gateway-interface": "^1.1.0", + "@apollo/usage-reporting-protobuf": "^4.1.0", + "@apollo/utils.createhash": "^2.0.0", + "@apollo/utils.fetcher": "^2.0.0", + "@apollo/utils.isnodelike": "^2.0.0", + "@apollo/utils.keyvaluecache": "^2.1.0", + "@apollo/utils.logger": "^2.0.0", + "@josephg/resolvable": "^1.0.1", + "@opentelemetry/api": "^1.0.1", + "@types/node-fetch": "^2.6.2", + "async-retry": "^1.3.3", + "loglevel": "^1.6.1", + "make-fetch-happen": "^11.0.0", + "node-abort-controller": "^3.0.1", + "node-fetch": "^2.6.7" + }, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "graphql": "^16.5.0" + } + }, + "node_modules/@apollo/protobufjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.7.tgz", + "integrity": "sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "long": "^4.0.0" + }, + "bin": { + "apollo-pbjs": "bin/pbjs", + "apollo-pbts": "bin/pbts" + } + }, + "node_modules/@apollo/query-graphs": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@apollo/query-graphs/-/query-graphs-2.8.4.tgz", + "integrity": "sha512-X2Y79efZh1RQ8aNi9iI+3ePWvyBm+oNW83P1xyxy3qBGFOBFne+YnoADK9tHM1FXLJ34cK03xxTdxY76V2Tteg==", + "dependencies": { + "@apollo/federation-internals": "2.8.4", + "deep-equal": "^2.0.5", + "ts-graphviz": "^1.5.4", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "graphql": "^16.5.0" + } + }, + "node_modules/@apollo/query-planner": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@apollo/query-planner/-/query-planner-2.8.4.tgz", + "integrity": "sha512-HNIyYeaIvj9/5Qem7p7w5we3SEaNJeTO77tpP7jkaaYxd+FOHtlwfMM0/5X814kl2eiaMposz78Kv+JF2582Gg==", + "dependencies": { + "@apollo/federation-internals": "2.8.4", + "@apollo/query-graphs": "2.8.4", + "@apollo/utils.keyvaluecache": "^2.1.0", + "chalk": "^4.1.0", + "deep-equal": "^2.0.5", + "pretty-format": "^29.0.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "graphql": "^16.5.0" + } + }, + "node_modules/@apollo/server": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-4.11.0.tgz", + "integrity": "sha512-SWDvbbs0wl2zYhKG6aGLxwTJ72xpqp0awb2lotNpfezd9VcAvzaUizzKQqocephin2uMoaA8MguoyBmgtPzNWw==", + "dependencies": { + "@apollo/cache-control-types": "^1.0.3", + "@apollo/server-gateway-interface": "^1.1.1", + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.createhash": "^2.0.0", + "@apollo/utils.fetcher": "^2.0.0", + "@apollo/utils.isnodelike": "^2.0.0", + "@apollo/utils.keyvaluecache": "^2.1.0", + "@apollo/utils.logger": "^2.0.0", + "@apollo/utils.usagereporting": "^2.1.0", + "@apollo/utils.withrequired": "^2.0.0", + "@graphql-tools/schema": "^9.0.0", + "@types/express": "^4.17.13", + "@types/express-serve-static-core": "^4.17.30", + "@types/node-fetch": "^2.6.1", + "async-retry": "^1.2.1", + "cors": "^2.8.5", + "express": "^4.17.1", + "loglevel": "^1.6.8", + "lru-cache": "^7.10.1", + "negotiator": "^0.6.3", + "node-abort-controller": "^3.1.1", + "node-fetch": "^2.6.7", + "uuid": "^9.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=14.16.0" + }, + "peerDependencies": { + "graphql": "^16.6.0" + } + }, + "node_modules/@apollo/server-gateway-interface": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@apollo/server-gateway-interface/-/server-gateway-interface-1.1.1.tgz", + "integrity": "sha512-pGwCl/po6+rxRmDMFgozKQo2pbsSwE91TpsDBAOgf74CRDPXHHtM88wbwjab0wMMZh95QfR45GGyDIdhY24bkQ==", + "dependencies": { + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.fetcher": "^2.0.0", + "@apollo/utils.keyvaluecache": "^2.1.0", + "@apollo/utils.logger": "^2.0.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/usage-reporting-protobuf": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz", + "integrity": "sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA==", + "dependencies": { + "@apollo/protobufjs": "1.2.7" + } + }, + "node_modules/@apollo/utils.createhash": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.createhash/-/utils.createhash-2.0.1.tgz", + "integrity": "sha512-fQO4/ZOP8LcXWvMNhKiee+2KuKyqIcfHrICA+M4lj/h/Lh1H10ICcUtk6N/chnEo5HXu0yejg64wshdaiFitJg==", + "dependencies": { + "@apollo/utils.isnodelike": "^2.0.1", + "sha.js": "^2.4.11" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@apollo/utils.dropunuseddefinitions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-2.0.1.tgz", + "integrity": "sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.fetcher": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.fetcher/-/utils.fetcher-2.0.1.tgz", + "integrity": "sha512-jvvon885hEyWXd4H6zpWeN3tl88QcWnHp5gWF5OPF34uhvoR+DFqcNxs9vrRaBBSY3qda3Qe0bdud7tz2zGx1A==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@apollo/utils.isnodelike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.isnodelike/-/utils.isnodelike-2.0.1.tgz", + "integrity": "sha512-w41XyepR+jBEuVpoRM715N2ZD0xMD413UiJx8w5xnAZD2ZkSJnMJBoIzauK83kJpSgNuR6ywbV29jG9NmxjK0Q==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@apollo/utils.keyvaluecache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-2.1.1.tgz", + "integrity": "sha512-qVo5PvUUMD8oB9oYvq4ViCjYAMWnZ5zZwEjNF37L2m1u528x5mueMlU+Cr1UinupCgdB78g+egA1G98rbJ03Vw==", + "dependencies": { + "@apollo/utils.logger": "^2.0.1", + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@apollo/utils.logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-2.0.1.tgz", + "integrity": "sha512-YuplwLHaHf1oviidB7MxnCXAdHp3IqYV8n0momZ3JfLniae92eYqMIx+j5qJFX6WKJPs6q7bczmV4lXIsTu5Pg==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@apollo/utils.printwithreducedwhitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-2.0.1.tgz", + "integrity": "sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.removealiases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.removealiases/-/utils.removealiases-2.0.1.tgz", + "integrity": "sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.sortast": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.sortast/-/utils.sortast-2.0.1.tgz", + "integrity": "sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw==", + "dependencies": { + "lodash.sortby": "^4.7.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.stripsensitiveliterals": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-2.0.1.tgz", + "integrity": "sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.usagereporting": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.usagereporting/-/utils.usagereporting-2.1.0.tgz", + "integrity": "sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ==", + "dependencies": { + "@apollo/usage-reporting-protobuf": "^4.1.0", + "@apollo/utils.dropunuseddefinitions": "^2.0.1", + "@apollo/utils.printwithreducedwhitespace": "^2.0.1", + "@apollo/utils.removealiases": "2.0.1", + "@apollo/utils.sortast": "^2.0.1", + "@apollo/utils.stripsensitiveliterals": "^2.0.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/@apollo/utils.withrequired": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.withrequired/-/utils.withrequired-2.0.1.tgz", + "integrity": "sha512-YBDiuAX9i1lLc6GeTy1m7DGLFn/gMnvXqlalOIMjM7DeOgIacEjjfwPqb0M1CQ2v11HhR15d1NmxJoRCfrNqcA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@graphql-tools/merge": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz", + "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==", + "dependencies": { + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/schema": { + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz", + "integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==", + "dependencies": { + "@graphql-tools/merge": "^8.4.1", + "@graphql-tools/utils": "^9.2.1", + "tslib": "^2.4.0", + "value-or-promise": "^1.0.12" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-tools/utils": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", + "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@josephg/resolvable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz", + "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==" + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "node_modules/@types/node": { + "version": "22.4.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.2.tgz", + "integrity": "sha512-nAvM3Ey230/XzxtyDcJ+VjvlzpzoHwLsF7JaDRfoI0ytO0mVheerNmM45CtA0yOILXwXXxOrcUWH3wltX+7PSw==", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "17.1.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", + "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^7.7.1", + "minipass": "^7.0.3", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphql": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "node_modules/loglevel": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-graphviz": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/ts-graphviz/-/ts-graphviz-1.8.2.tgz", + "integrity": "sha512-5YhbFoHmjxa7pgQLkB07MtGnGJ/yhvjmc9uhsnDBEICME6gkPf83SBwLDQqGDoCa3XzUMWLk1AU2Wn1u1naDtA==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/value-or-promise": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz", + "integrity": "sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/examples/federation/package.json b/examples/federation/package.json new file mode 100644 index 0000000000..410141cfa2 --- /dev/null +++ b/examples/federation/package.json @@ -0,0 +1,18 @@ +{ + "name": "federation", + "version": "1.0.0", + "main": "gateway.js", + "type": "module", + "scripts": { + "start": "node gateway.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@apollo/gateway": "^2.8.4", + "@apollo/server": "^4.11.0", + "graphql": "^16.9.0" + } +} diff --git a/examples/federation/rover.sh b/examples/federation/rover.sh new file mode 100755 index 0000000000..c4c100a2fe --- /dev/null +++ b/examples/federation/rover.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -eumo pipefail + +function cleanup { + for pid in "${PRODUCTS_ROVER_PID:-}" "${REVIEWS_ROVER_PID:-}" "${ACCOUNTS_PID:-}" "${PRODUCTS_PID:-}" "${REVIEWS_PID:-}"; do + # try kill all registered pids + [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && kill "$pid" || echo "Could not kill $pid" + done +} +trap cleanup EXIT + +rover dev --url http://localhost:8001/graphql --name post & +sleep 1 +rover dev --url http://localhost:8002/graphql --name user & +PRODUCTS_ROVER_PID=$! +sleep 1 +fg %1 From 857e61137b5bfe70cfd051da1877e8b406456115 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:01:40 +0000 Subject: [PATCH 11/17] fix example --- examples/federation/rover.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/federation/rover.sh b/examples/federation/rover.sh index c4c100a2fe..b56e0c96d6 100755 --- a/examples/federation/rover.sh +++ b/examples/federation/rover.sh @@ -3,7 +3,7 @@ set -eumo pipefail function cleanup { - for pid in "${PRODUCTS_ROVER_PID:-}" "${REVIEWS_ROVER_PID:-}" "${ACCOUNTS_PID:-}" "${PRODUCTS_PID:-}" "${REVIEWS_PID:-}"; do + for pid in "${USER_ROVER_PID:-}"; do # try kill all registered pids [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && kill "$pid" || echo "Could not kill $pid" done @@ -13,6 +13,6 @@ trap cleanup EXIT rover dev --url http://localhost:8001/graphql --name post & sleep 1 rover dev --url http://localhost:8002/graphql --name user & -PRODUCTS_ROVER_PID=$! +USER_ROVER_PID=$! sleep 1 fg %1 From 1baa0c7564896fee7e14d7a8448eb53d9c752b43 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:08:22 +0000 Subject: [PATCH 12/17] fix lint --- .../apollo_federation_subgraph_post.graphql | 2 +- examples/federation/README.md | 13 ++++++++----- examples/federation/gateway.js | 18 +++++++++--------- .../blueprint/operators/apollo_federation.rs | 12 +++++++++--- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/examples/apollo_federation_subgraph_post.graphql b/examples/apollo_federation_subgraph_post.graphql index 52d055d7fb..0546fd7139 100644 --- a/examples/apollo_federation_subgraph_post.graphql +++ b/examples/apollo_federation_subgraph_post.graphql @@ -17,5 +17,5 @@ type Post @http(path: "/posts", query: [{key: "id", value: "{{.value.id}}"}], ba userId: Int! title: String! body: String! - user: User @expr(body: { id: "{{.value.userId}}" }) + user: User @expr(body: {id: "{{.value.userId}}"}) } diff --git a/examples/federation/README.md b/examples/federation/README.md index 664ff8d689..1d8d49842f 100644 --- a/examples/federation/README.md +++ b/examples/federation/README.md @@ -1,11 +1,15 @@ # Apollo Federation example 1. Start tailcall subgraph examples: - - `cargo run -- start examples/apollo_federation_subgraph_post.graphql` - - `cargo run -- start examples/apollo_federation_subgraph_user.graphql` + +- `cargo run -- start examples/apollo_federation_subgraph_post.graphql` +- `cargo run -- start examples/apollo_federation_subgraph_user.graphql` + 2. Run Apollo router by one of the following methods: - - run `@apollo/gateway` with `npm start` (with `npm install` for the first time) from "examples/federation" folder - - start apollo router with `rover.sh` script (install [apollo rover](https://www.apollographql.com/docs/rover) first) + +- run `@apollo/gateway` with `npm start` (with `npm install` for the first time) from "examples/federation" folder +- start apollo router with `rover.sh` script (install [apollo rover](https://www.apollographql.com/docs/rover) first) + 3. Navigate to `http://localhost:4000` and execute supergraph queries, see [examples](#query-examples) # Query examples @@ -21,5 +25,4 @@ } } } - ``` diff --git a/examples/federation/gateway.js b/examples/federation/gateway.js index 0c7bdd565a..dcb0b89760 100644 --- a/examples/federation/gateway.js +++ b/examples/federation/gateway.js @@ -1,20 +1,20 @@ -import { ApolloServer } from '@apollo/server'; -import { startStandaloneServer } from '@apollo/server/standalone'; -import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway'; +import {ApolloServer} from "@apollo/server" +import {startStandaloneServer} from "@apollo/server/standalone" +import {ApolloGateway, IntrospectAndCompose} from "@apollo/gateway" const gateway = new ApolloGateway({ supergraphSdl: new IntrospectAndCompose({ subgraphs: [ - { name: 'post', url: 'http://localhost:8001/graphql' }, - { name: 'user', url: 'http://localhost:8002/graphql' }, + {name: "post", url: "http://localhost:8001/graphql"}, + {name: "user", url: "http://localhost:8002/graphql"}, ], }), -}); +}) const server = new ApolloServer({ gateway, introspection: true, -}); +}) -const { url } = await startStandaloneServer(server); -console.log(`šŸš€ Server ready at ${url}`); +const {url} = await startStandaloneServer(server) +console.log(`šŸš€ Server ready at ${url}`) diff --git a/src/core/blueprint/operators/apollo_federation.rs b/src/core/blueprint/operators/apollo_federation.rs index fdc4eb1041..24d4124a43 100644 --- a/src/core/blueprint/operators/apollo_federation.rs +++ b/src/core/blueprint/operators/apollo_federation.rs @@ -2,9 +2,10 @@ use std::collections::HashMap; use std::fmt::Write; use super::{compile_call, compile_expr, compile_graphql, compile_grpc, compile_http, compile_js}; -use crate::core::{blueprint::FieldDefinition, config::Config}; +use crate::core::blueprint::FieldDefinition; use crate::core::config::{ - ApolloFederation, ConfigModule, EntityResolver, Field, GraphQLOperationType, Resolver, Type, + ApolloFederation, Config, ConfigModule, EntityResolver, Field, GraphQLOperationType, Resolver, + Type, }; use crate::core::ir::model::IR; use crate::core::try_fold::TryFold; @@ -81,7 +82,12 @@ pub fn compile_service(config: &ConfigModule) -> Valid { writeln!(sdl).ok(); // Add tailcall specific definitions to the sdl output - writeln!(sdl, "{}", crate::core::document::print(Config::graphql_schema())).ok(); + writeln!( + sdl, + "{}", + crate::core::document::print(Config::graphql_schema()) + ) + .ok(); writeln!(sdl).ok(); // Mark subgraph as Apollo federation v2 compatible according to [docs](https://www.apollographql.com/docs/apollo-server/using-federation/apollo-subgraph-setup/#2-opt-in-to-federation-2) // (borrowed from async_graphql) From 2ec982ecfc7fee0ba40da70fedab3dda093d3208 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Wed, 21 Aug 2024 21:22:49 +0000 Subject: [PATCH 13/17] fix snapshots --- tests/core/snapshots/apollo-federation-entities-batch.md_1.snap | 2 +- tests/core/snapshots/apollo-federation-entities.md_1.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap b/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap index bfa96d6fc7..57584dfb8d 100644 --- a/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap +++ b/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap @@ -10,7 +10,7 @@ expression: response "body": { "data": { "_service": { - "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @graphQL(args: [{key: \"id\", value: \"{{.value.id}}\"}], baseURL: \"http://upstream/graphql\", batch: true, name: \"post\") @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @http(batchKey: [\"id\"], path: \"/users\", query: [{key: \"id\", value: \"{{.value.user.id}}\"}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" + "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @graphQL(args: [{key: \"id\", value: \"{{.value.id}}\"}], baseURL: \"http://upstream/graphql\", batch: true, name: \"post\") @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @http(batchKey: [\"id\"], path: \"/users\", query: [{key: \"id\", value: \"{{.value.user.id}}\"}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @link directive allows you to import external resources, such as configuration \nā€“ which will be merged into the config importing it ā€“, or a .proto file ā€“ which\n will be later used by `@grpc` directive ā€“.\n\"\"\"\ndirective @link(\n \"\"\"\n The id of the link. It is used to reference the link in the schema.\n \"\"\"\n id: String\n \"\"\"\n The source of the link. It can be a URL or a path to a file. If a path is provided, \n it is relative to the file that imports the link.\n \"\"\"\n src: String!\n \"\"\"\n The type of the link. It can be `Config`, or `Protobuf`.\n \"\"\"\n type: LinkType\n) repeatable on SCHEMA\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" } } } diff --git a/tests/core/snapshots/apollo-federation-entities.md_1.snap b/tests/core/snapshots/apollo-federation-entities.md_1.snap index b516301809..7616829cd0 100644 --- a/tests/core/snapshots/apollo-federation-entities.md_1.snap +++ b/tests/core/snapshots/apollo-federation-entities.md_1.snap @@ -10,7 +10,7 @@ expression: response "body": { "data": { "_service": { - "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @expr(body: {id: \"{{.value.id}}\", title: \"post-title-{{.value.id}}\"}) @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @call(steps: [{query: \"user\", args: {id: \"{{.value.user.id}}\"}}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" + "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @expr(body: {id: \"{{.value.id}}\", title: \"post-title-{{.value.id}}\"}) @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @call(steps: [{query: \"user\", args: {id: \"{{.value.user.id}}\"}}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @link directive allows you to import external resources, such as configuration \nā€“ which will be merged into the config importing it ā€“, or a .proto file ā€“ which\n will be later used by `@grpc` directive ā€“.\n\"\"\"\ndirective @link(\n \"\"\"\n The id of the link. It is used to reference the link in the schema.\n \"\"\"\n id: String\n \"\"\"\n The source of the link. It can be a URL or a path to a file. If a path is provided, \n it is relative to the file that imports the link.\n \"\"\"\n src: String!\n \"\"\"\n The type of the link. It can be `Config`, or `Protobuf`.\n \"\"\"\n type: LinkType\n) repeatable on SCHEMA\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" } } } From abcbe010f35b0915fc4c681611bacdd4e85f0504 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:41:22 +0000 Subject: [PATCH 14/17] refactor: reuse typename if possible --- src/core/ir/eval.rs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/core/ir/eval.rs b/src/core/ir/eval.rs index c798d7b628..67ad9ebafb 100644 --- a/src/core/ir/eval.rs +++ b/src/core/ir/eval.rs @@ -88,9 +88,13 @@ impl IR { second.eval(ctx).await } IR::Discriminate(discriminator, expr) => expr.eval(ctx).await.and_then(|value| { - // TODO: in some cases __typename could be already present or could be inferred - // by some way like for EntityResolver let value = value.map(|mut value| { + if value.get_type_name().is_some() { + // if typename is already present in value just reuse it instead + // of recalculating from scratch + return Ok(value); + } + let type_name = discriminator.resolve_type(&value)?; value.set_type_name(type_name.to_string())?; @@ -114,22 +118,25 @@ impl IR { for repr in representations { // TODO: combine errors, instead of fail fast? - let typename = repr.get_key("__typename").and_then(|t| t.as_str()).ok_or( - Error::EntityResolver( - "expected __typename to be the part of the representation" - .to_string(), - ), - )?; - - let ir = map.get(typename).ok_or(Error::EntityResolver(format!( - "Cannot find a resolver for type: `{typename}`" + let type_name = repr.get_type_name().ok_or(Error::EntityResolver( + "expected __typename to be the part of the representation".to_string(), + ))?; + + let ir = map.get(type_name).ok_or(Error::EntityResolver(format!( + "Cannot find a resolver for type: `{type_name}`" )))?; // pass the input for current representation as value in context // TODO: can we drop clone? let mut ctx = ctx.with_value(repr.clone()); - tasks.push(async move { ir.eval(&mut ctx).await }); + tasks.push(async move { + ir.eval(&mut ctx).await.and_then(|mut value| { + // set typename explicitly to reuse it if needed + value.set_type_name(type_name.to_owned())?; + Ok(value) + }) + }); } let result = join_all(tasks).await; From 63bd4fedba9d0e4584cb3a5435042fc261def078 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:59:42 +0000 Subject: [PATCH 15/17] fix snapshot tests --- tests/core/snapshots/apollo-federation-entities-batch.md_1.snap | 2 +- tests/core/snapshots/apollo-federation-entities.md_1.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap b/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap index e43c539567..7c2d964a18 100644 --- a/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap +++ b/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap @@ -10,7 +10,7 @@ expression: response "body": { "data": { "_service": { - "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @graphQL(args: [{key: \"id\", value: \"{{.value.id}}\"}], baseURL: \"http://upstream/graphql\", batch: true, name: \"post\") @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @http(batchKey: [\"id\"], path: \"/users\", query: [{key: \"id\", value: \"{{.value.user.id}}\"}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @link directive allows you to import external resources, such as configuration \nā€“ which will be merged into the config importing it ā€“, or a .proto file ā€“ which\n will be later used by `@grpc` directive ā€“.\n\"\"\"\ndirective @link(\n \"\"\"\n The id of the link. It is used to reference the link in the schema.\n \"\"\"\n id: String\n \"\"\"\n Additional metadata pertaining to the linked resource.\n \"\"\"\n meta: JSON\n \"\"\"\n The source of the link. It can be a URL or a path to a file. If a path is provided, \n it is relative to the file that imports the link.\n \"\"\"\n src: String!\n \"\"\"\n The type of the link. It can be `Config`, or `Protobuf`.\n \"\"\"\n type: LinkType\n) repeatable on SCHEMA\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" + "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @graphQL(args: [{key: \"id\", value: \"{{.value.id}}\"}], baseURL: \"http://upstream/graphql\", batch: true, name: \"post\") @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @http(batchKey: [\"id\"], path: \"/users\", query: [{key: \"id\", value: \"{{.value.user.id}}\"}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @link directive allows you to import external resources, such as configuration \nā€“ which will be merged into the config importing it ā€“, or a .proto file ā€“ which\n will be later used by `@grpc` directive ā€“.\n\"\"\"\ndirective @link(\n \"\"\"\n The id of the link. It is used to reference the link in the schema.\n \"\"\"\n id: String\n \"\"\"\n Additional metadata pertaining to the linked resource.\n \"\"\"\n meta: JSON\n \"\"\"\n The source of the link. It can be a URL or a path to a file. If a path is provided, \n it is relative to the file that imports the link.\n \"\"\"\n src: String!\n \"\"\"\n The type of the link. It can be `Config`, or `Protobuf`.\n \"\"\"\n type: LinkType\n) repeatable on SCHEMA\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n `routes` allows customization of server endpoint paths. It provides options to change \n the default paths for status and GraphQL endpoints. Default values are: - status: \n \"/status\" - graphQL: \"/graphql\" If not specified, these default values will be used.\n \"\"\"\n routes: Routes\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput Routes {\n graphQL: String!\n status: String!\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" } } } diff --git a/tests/core/snapshots/apollo-federation-entities.md_1.snap b/tests/core/snapshots/apollo-federation-entities.md_1.snap index 25744f360b..a2d4f074f8 100644 --- a/tests/core/snapshots/apollo-federation-entities.md_1.snap +++ b/tests/core/snapshots/apollo-federation-entities.md_1.snap @@ -10,7 +10,7 @@ expression: response "body": { "data": { "_service": { - "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @expr(body: {id: \"{{.value.id}}\", title: \"post-title-{{.value.id}}\"}) @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @call(steps: [{query: \"user\", args: {id: \"{{.value.user.id}}\"}}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @link directive allows you to import external resources, such as configuration \nā€“ which will be merged into the config importing it ā€“, or a .proto file ā€“ which\n will be later used by `@grpc` directive ā€“.\n\"\"\"\ndirective @link(\n \"\"\"\n The id of the link. It is used to reference the link in the schema.\n \"\"\"\n id: String\n \"\"\"\n Additional metadata pertaining to the linked resource.\n \"\"\"\n meta: JSON\n \"\"\"\n The source of the link. It can be a URL or a path to a file. If a path is provided, \n it is relative to the file that imports the link.\n \"\"\"\n src: String!\n \"\"\"\n The type of the link. It can be `Config`, or `Protobuf`.\n \"\"\"\n type: LinkType\n) repeatable on SCHEMA\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" + "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @expr(body: {id: \"{{.value.id}}\", title: \"post-title-{{.value.id}}\"}) @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @call(steps: [{query: \"user\", args: {id: \"{{.value.user.id}}\"}}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @link directive allows you to import external resources, such as configuration \nā€“ which will be merged into the config importing it ā€“, or a .proto file ā€“ which\n will be later used by `@grpc` directive ā€“.\n\"\"\"\ndirective @link(\n \"\"\"\n The id of the link. It is used to reference the link in the schema.\n \"\"\"\n id: String\n \"\"\"\n Additional metadata pertaining to the linked resource.\n \"\"\"\n meta: JSON\n \"\"\"\n The source of the link. It can be a URL or a path to a file. If a path is provided, \n it is relative to the file that imports the link.\n \"\"\"\n src: String!\n \"\"\"\n The type of the link. It can be `Config`, or `Protobuf`.\n \"\"\"\n type: LinkType\n) repeatable on SCHEMA\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n `routes` allows customization of server endpoint paths. It provides options to change \n the default paths for status and GraphQL endpoints. Default values are: - status: \n \"/status\" - graphQL: \"/graphql\" If not specified, these default values will be used.\n \"\"\"\n routes: Routes\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput Routes {\n graphQL: String!\n status: String!\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" } } } From 59cae80351610823a3ef93650bed5f77563be198 Mon Sep 17 00:00:00 2001 From: meskill <8974488+meskill@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:23:40 +0000 Subject: [PATCH 16/17] fix review comments --- src/core/blueprint/definitions.rs | 2 +- .../blueprint/operators/apollo_federation.rs | 2 +- src/core/config/config.rs | 58 ++----------------- src/core/config/mod.rs | 2 + src/core/config/resolver.rs | 56 ++++++++++++++++++ src/core/config/transformer/mod.rs | 4 +- src/core/config/transformer/required.rs | 2 +- ...raph__tests__extractor__extract_call.snap} | 2 +- ...raph__tests__extractor__extract_expr.snap} | 2 +- ...h__tests__extractor__extract_graphql.snap} | 2 +- ...raph__tests__extractor__extract_grpc.snap} | 2 +- ...raph__tests__extractor__extract_http.snap} | 2 +- ...tests__extractor__non_value_template.snap} | 2 +- ...r__subgraph__tests__keys__keys_merge.snap} | 2 +- ...mer__subgraph__tests__keys__keys_set.snap} | 2 +- ...llo_federation_subgraph.rs => subgraph.rs} | 4 +- src/core/ir/error.rs | 4 +- src/core/ir/eval.rs | 8 +-- src/core/ir/model.rs | 4 +- 19 files changed, 86 insertions(+), 76 deletions(-) create mode 100644 src/core/config/resolver.rs rename src/core/config/transformer/snapshots/{tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_call.snap => tailcall__core__config__transformer__subgraph__tests__extractor__extract_call.snap} (60%) rename src/core/config/transformer/snapshots/{tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_expr.snap => tailcall__core__config__transformer__subgraph__tests__extractor__extract_expr.snap} (60%) rename src/core/config/transformer/snapshots/{tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_graphql.snap => tailcall__core__config__transformer__subgraph__tests__extractor__extract_graphql.snap} (63%) rename src/core/config/transformer/snapshots/{tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_grpc.snap => tailcall__core__config__transformer__subgraph__tests__extractor__extract_grpc.snap} (64%) rename src/core/config/transformer/snapshots/{tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_http.snap => tailcall__core__config__transformer__subgraph__tests__extractor__extract_http.snap} (67%) rename src/core/config/transformer/snapshots/{tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__non_value_template.snap => tailcall__core__config__transformer__subgraph__tests__extractor__non_value_template.snap} (91%) rename src/core/config/transformer/snapshots/{tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_merge.snap => tailcall__core__config__transformer__subgraph__tests__keys__keys_merge.snap} (88%) rename src/core/config/transformer/snapshots/{tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_set.snap => tailcall__core__config__transformer__subgraph__tests__keys__keys_set.snap} (89%) rename src/core/config/transformer/{apollo_federation_subgraph.rs => subgraph.rs} (99%) diff --git a/src/core/blueprint/definitions.rs b/src/core/blueprint/definitions.rs index 6c183aad27..c7e47650a9 100644 --- a/src/core/blueprint/definitions.rs +++ b/src/core/blueprint/definitions.rs @@ -492,7 +492,7 @@ pub fn to_field_definition( name: &String, ) -> Valid { update_args() - .and(update_apollo_federation(operation_type).trace("EntityResolver")) + .and(update_apollo_federation(operation_type).trace("_entities")) .and(update_http().trace(config::Http::trace_name().as_str())) .and(update_grpc(operation_type).trace(config::Grpc::trace_name().as_str())) .and(update_const_field().trace(config::Expr::trace_name().as_str())) diff --git a/src/core/blueprint/operators/apollo_federation.rs b/src/core/blueprint/operators/apollo_federation.rs index f418970b03..e9e5a9e370 100644 --- a/src/core/blueprint/operators/apollo_federation.rs +++ b/src/core/blueprint/operators/apollo_federation.rs @@ -74,7 +74,7 @@ pub fn compile_entity_resolver(inputs: CompileEntityResolver<'_>) -> Valid Valid { diff --git a/src/core/config/config.rs b/src/core/config/config.rs index 265263aab6..d04c0f3fe8 100644 --- a/src/core/config/config.rs +++ b/src/core/config/config.rs @@ -3,24 +3,22 @@ use std::fmt::{self, Display}; use std::num::NonZeroU64; use anyhow::Result; -use async_graphql::parser::types::{ConstDirective, ServiceDocument}; -use async_graphql::Positioned; +use async_graphql::parser::types::ServiceDocument; use derive_setters::Setters; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use serde_json::Value; -use tailcall_macros::{CustomResolver, DirectiveDefinition, InputDefinition}; +use tailcall_macros::{DirectiveDefinition, InputDefinition}; use tailcall_typedefs_common::directive_definition::DirectiveDefinition; use tailcall_typedefs_common::input_definition::InputDefinition; use tailcall_typedefs_common::ServiceDocumentBuilder; -use super::directives::{Call, EntityResolver, Expr, GraphQL, Grpc, Http, Key, JS}; +use super::directives::{Call, Expr, GraphQL, Grpc, Http, Key, JS}; use super::from_document::from_document; use super::telemetry::Telemetry; -use super::{Link, Server, Upstream}; +use super::{Link, Resolver, Server, Upstream}; use crate::core::config::npo::QueryPath; use crate::core::config::source::Source; -use crate::core::directive::DirectiveCodec; use crate::core::is_default; use crate::core::macros::MergeRight; use crate::core::merge_right::MergeRight; @@ -220,53 +218,6 @@ pub struct RootSchema { /// Used to omit a field from public consumption. pub struct Omit {} -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ApolloFederation { - EntityResolver(EntityResolver), - Service, -} - -#[derive( - Serialize, - Deserialize, - Clone, - Debug, - PartialEq, - Eq, - schemars::JsonSchema, - MergeRight, - CustomResolver, -)] -#[serde(rename_all = "camelCase")] -pub enum Resolver { - Http(Http), - Grpc(Grpc), - Graphql(GraphQL), - Call(Call), - Js(JS), - Expr(Expr), - #[serde(skip)] - #[resolver(skip_directive)] - ApolloFederation(ApolloFederation), -} - -impl Resolver { - pub fn is_batched(&self) -> bool { - match self { - Resolver::Http(http) => !http.batch_key.is_empty(), - Resolver::Grpc(grpc) => !grpc.batch_key.is_empty(), - Resolver::Graphql(graphql) => graphql.batch, - Resolver::ApolloFederation(ApolloFederation::EntityResolver(entity_resolver)) => { - entity_resolver - .resolver_by_type - .values() - .any(Resolver::is_batched) - } - _ => false, - } - } -} - /// /// A field definition containing all the metadata information about resolving a /// field. @@ -795,6 +746,7 @@ mod tests { use pretty_assertions::assert_eq; use super::*; + use crate::core::directive::DirectiveCodec; #[test] fn test_field_has_or_not_batch_resolver() { diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 7b8ae927a0..8288d9485c 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -6,6 +6,7 @@ pub use key_values::*; pub use link::*; pub use npo::QueryPath; pub use reader_context::*; +pub use resolver::*; pub use server::*; pub use source::*; pub use telemetry::*; @@ -25,6 +26,7 @@ mod link; mod npo; pub mod reader; pub mod reader_context; +mod resolver; mod server; mod source; mod telemetry; diff --git a/src/core/config/resolver.rs b/src/core/config/resolver.rs new file mode 100644 index 0000000000..c8b42c21a0 --- /dev/null +++ b/src/core/config/resolver.rs @@ -0,0 +1,56 @@ +use async_graphql::parser::types::ConstDirective; +use async_graphql::Positioned; +use serde::{Deserialize, Serialize}; +use tailcall_macros::{CustomResolver, MergeRight}; + +use super::{Call, EntityResolver, Expr, GraphQL, Grpc, Http, JS}; +use crate::core::directive::DirectiveCodec; +use crate::core::merge_right::MergeRight; +use crate::core::valid::{Valid, Validator}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ApolloFederation { + EntityResolver(EntityResolver), + Service, +} + +#[derive( + Serialize, + Deserialize, + Clone, + Debug, + PartialEq, + Eq, + schemars::JsonSchema, + MergeRight, + CustomResolver, +)] +#[serde(rename_all = "camelCase")] +pub enum Resolver { + Http(Http), + Grpc(Grpc), + Graphql(GraphQL), + Call(Call), + Js(JS), + Expr(Expr), + #[serde(skip)] + #[resolver(skip_directive)] + ApolloFederation(ApolloFederation), +} + +impl Resolver { + pub fn is_batched(&self) -> bool { + match self { + Resolver::Http(http) => !http.batch_key.is_empty(), + Resolver::Grpc(grpc) => !grpc.batch_key.is_empty(), + Resolver::Graphql(graphql) => graphql.batch, + Resolver::ApolloFederation(ApolloFederation::EntityResolver(entity_resolver)) => { + entity_resolver + .resolver_by_type + .values() + .any(Resolver::is_batched) + } + _ => false, + } + } +} diff --git a/src/core/config/transformer/mod.rs b/src/core/config/transformer/mod.rs index b4fe637fe4..75a1396761 100644 --- a/src/core/config/transformer/mod.rs +++ b/src/core/config/transformer/mod.rs @@ -1,5 +1,4 @@ mod ambiguous_type; -mod apollo_federation_subgraph; mod consolidate_url; mod flatten_single_field; mod improve_type_names; @@ -8,11 +7,11 @@ mod nested_unions; mod preset; mod rename_types; mod required; +mod subgraph; mod tree_shake; mod union_input_type; pub use ambiguous_type::{AmbiguousType, Resolution}; -pub use apollo_federation_subgraph::ApolloFederationSubgraph; pub use consolidate_url::ConsolidateURL; pub use flatten_single_field::FlattenSingleField; pub use improve_type_names::ImproveTypeNames; @@ -21,5 +20,6 @@ pub use nested_unions::NestedUnions; pub use preset::Preset; pub use rename_types::RenameTypes; pub use required::Required; +pub use subgraph::Subgraph; pub use tree_shake::TreeShake; pub use union_input_type::UnionInputType; diff --git a/src/core/config/transformer/required.rs b/src/core/config/transformer/required.rs index 3fc25e313f..e8ba0c2803 100644 --- a/src/core/config/transformer/required.rs +++ b/src/core/config/transformer/required.rs @@ -15,7 +15,7 @@ impl Transform for Required { config: Self::Value, ) -> crate::core::valid::Valid { transform::default() - .pipe(super::ApolloFederationSubgraph) + .pipe(super::Subgraph) .pipe(super::NestedUnions) .pipe(super::UnionInputType) .pipe(super::AmbiguousType::default()) diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_call.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_call.snap similarity index 60% rename from src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_call.snap rename to src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_call.snap index 26e8567487..056418f3e3 100644 --- a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_call.snap +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_call.snap @@ -1,5 +1,5 @@ --- -source: src/core/config/transformer/apollo_federation_subgraph.rs +source: src/core/config/transformer/subgraph.rs expression: keys --- Valid( diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_expr.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_expr.snap similarity index 60% rename from src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_expr.snap rename to src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_expr.snap index 54d1422762..29c06be101 100644 --- a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_expr.snap +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_expr.snap @@ -1,5 +1,5 @@ --- -source: src/core/config/transformer/apollo_federation_subgraph.rs +source: src/core/config/transformer/subgraph.rs expression: keys --- Valid( diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_graphql.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_graphql.snap similarity index 63% rename from src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_graphql.snap rename to src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_graphql.snap index 7f3fc05246..ae0c75e46a 100644 --- a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_graphql.snap +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_graphql.snap @@ -1,5 +1,5 @@ --- -source: src/core/config/transformer/apollo_federation_subgraph.rs +source: src/core/config/transformer/subgraph.rs expression: keys --- Valid( diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_grpc.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_grpc.snap similarity index 64% rename from src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_grpc.snap rename to src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_grpc.snap index 66f34d0706..a58bbbb93b 100644 --- a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_grpc.snap +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_grpc.snap @@ -1,5 +1,5 @@ --- -source: src/core/config/transformer/apollo_federation_subgraph.rs +source: src/core/config/transformer/subgraph.rs expression: keys --- Valid( diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_http.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_http.snap similarity index 67% rename from src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_http.snap rename to src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_http.snap index 21904a81cf..bd776f6c9d 100644 --- a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__extract_http.snap +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__extract_http.snap @@ -1,5 +1,5 @@ --- -source: src/core/config/transformer/apollo_federation_subgraph.rs +source: src/core/config/transformer/subgraph.rs expression: keys --- Valid( diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__non_value_template.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__non_value_template.snap similarity index 91% rename from src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__non_value_template.snap rename to src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__non_value_template.snap index 9c9cec60b3..8fabb5a9e6 100644 --- a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__extractor__non_value_template.snap +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__extractor__non_value_template.snap @@ -1,5 +1,5 @@ --- -source: src/core/config/transformer/apollo_federation_subgraph.rs +source: src/core/config/transformer/subgraph.rs expression: keys --- Valid( diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_merge.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__keys__keys_merge.snap similarity index 88% rename from src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_merge.snap rename to src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__keys__keys_merge.snap index 09ddecb5f2..a1a1125ffd 100644 --- a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_merge.snap +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__keys__keys_merge.snap @@ -1,5 +1,5 @@ --- -source: src/core/config/transformer/apollo_federation_subgraph.rs +source: src/core/config/transformer/subgraph.rs expression: keys_a.merge_right(keys_b) --- Keys( diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_set.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__keys__keys_set.snap similarity index 89% rename from src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_set.snap rename to src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__keys__keys_set.snap index 85fc734916..0ca1900d8e 100644 --- a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__apollo_federation_subgraph__tests__keys__keys_set.snap +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__subgraph__tests__keys__keys_set.snap @@ -1,5 +1,5 @@ --- -source: src/core/config/transformer/apollo_federation_subgraph.rs +source: src/core/config/transformer/subgraph.rs expression: keys --- Keys( diff --git a/src/core/config/transformer/apollo_federation_subgraph.rs b/src/core/config/transformer/subgraph.rs similarity index 99% rename from src/core/config/transformer/apollo_federation_subgraph.rs rename to src/core/config/transformer/subgraph.rs index 5c77c678c1..3ef4126ee2 100644 --- a/src/core/config/transformer/apollo_federation_subgraph.rs +++ b/src/core/config/transformer/subgraph.rs @@ -26,9 +26,9 @@ const ENTITIES_TYPE_NAME: &str = "_Any"; /// Adds compatibility layer for Apollo Federation v2 /// so tailcall may act as a Federation Subgraph. /// Followed by [spec](https://www.apollographql.com/docs/federation/subgraph-spec/) -pub struct ApolloFederationSubgraph; +pub struct Subgraph; -impl Transform for ApolloFederationSubgraph { +impl Transform for Subgraph { type Value = Config; type Error = String; diff --git a/src/core/ir/error.rs b/src/core/ir/error.rs index 7c7dc02a13..7f026f83a7 100644 --- a/src/core/ir/error.rs +++ b/src/core/ir/error.rs @@ -33,7 +33,7 @@ pub enum Error { Cache(cache::Error), #[from(ignore)] - EntityResolver(String), + Entity(String), } impl Display for Error { @@ -66,7 +66,7 @@ impl From for Errata { } Error::Worker(err) => Errata::new("Worker Error").description(err.to_string()), Error::Cache(err) => Errata::new("Cache Error").description(err.to_string()), - Error::EntityResolver(message) => Errata::new("Entity Resolver Error").description(message) + Error::Entity(message) => Errata::new("Entity Resolver Error").description(message) } } } diff --git a/src/core/ir/eval.rs b/src/core/ir/eval.rs index 0502605481..234197b751 100644 --- a/src/core/ir/eval.rs +++ b/src/core/ir/eval.rs @@ -104,13 +104,13 @@ impl IR { Ok(value) }), - IR::EntityResolver(map) => { + IR::Entity(map) => { let representations = ctx.path_arg(&["representations"]); let representations = representations .as_ref() .and_then(|repr| repr.as_array()) - .ok_or(Error::EntityResolver( + .ok_or(Error::Entity( "expected `representations` arg as an array of _Any".to_string(), ))?; @@ -118,11 +118,11 @@ impl IR { for repr in representations { // TODO: combine errors, instead of fail fast? - let type_name = repr.get_type_name().ok_or(Error::EntityResolver( + let type_name = repr.get_type_name().ok_or(Error::Entity( "expected __typename to be the part of the representation".to_string(), ))?; - let ir = map.get(type_name).ok_or(Error::EntityResolver(format!( + let ir = map.get(type_name).ok_or(Error::Entity(format!( "Cannot find a resolver for type: `{type_name}`" )))?; diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index a3642a8e17..3473d47d96 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -27,7 +27,7 @@ pub enum IR { Pipe(Box, Box), Discriminate(Discriminator, Box), /// Apollo Federation _entities resolver - EntityResolver(HashMap), + Entity(HashMap), /// Apollo Federation _service resolver Service(String), } @@ -154,7 +154,7 @@ impl IR { IR::Discriminate(discriminator, expr) => { IR::Discriminate(discriminator, expr.modify_box(modifier)) } - IR::EntityResolver(map) => IR::EntityResolver( + IR::Entity(map) => IR::Entity( map.into_iter() .map(|(k, v)| (k, v.modify(modifier))) .collect(), From 866bc3d2b1d8984322a38f7f0396293ecb9330e5 Mon Sep 17 00:00:00 2001 From: Kiryl Mialeshka <8974488+meskill@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:13:30 +0200 Subject: [PATCH 17/17] fix: directives name clashes with apollo federation for link (#2886) --- examples/federation/rover.sh | 2 +- .../blueprint/operators/apollo_federation.rs | 42 ++++++++++++++++++- ...apollo-federation-entities-batch.md_1.snap | 2 +- .../apollo-federation-entities.md_1.snap | 2 +- .../apollo-federation-entities.md_merged.snap | 3 +- tests/execution/apollo-federation-entities.md | 5 ++- 6 files changed, 49 insertions(+), 7 deletions(-) diff --git a/examples/federation/rover.sh b/examples/federation/rover.sh index b56e0c96d6..aadf665b04 100755 --- a/examples/federation/rover.sh +++ b/examples/federation/rover.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eumo pipefail diff --git a/src/core/blueprint/operators/apollo_federation.rs b/src/core/blueprint/operators/apollo_federation.rs index e9e5a9e370..a078e6fe8c 100644 --- a/src/core/blueprint/operators/apollo_federation.rs +++ b/src/core/blueprint/operators/apollo_federation.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; use std::fmt::Write; +use async_graphql::parser::types::{SchemaDefinition, ServiceDocument, TypeSystemDefinition}; + use super::{compile_call, compile_expr, compile_graphql, compile_grpc, compile_http, compile_js}; use crate::core::blueprint::FieldDefinition; use crate::core::config::{ @@ -78,14 +80,15 @@ pub fn compile_entity_resolver(inputs: CompileEntityResolver<'_>) -> Valid Valid { - let mut sdl = config.to_sdl(); + let mut sdl = + crate::core::document::print(filter_conflicting_directives(config.config().into())); writeln!(sdl).ok(); // Add tailcall specific definitions to the sdl output writeln!( sdl, "{}", - crate::core::document::print(Config::graphql_schema()) + crate::core::document::print(filter_conflicting_directives(Config::graphql_schema())) ) .ok(); writeln!(sdl).ok(); @@ -99,6 +102,41 @@ pub fn compile_service(config: &ConfigModule) -> Valid { Valid::succeed(IR::Service(sdl)) } +fn filter_conflicting_directives(sd: ServiceDocument) -> ServiceDocument { + fn filter_directive(directive_name: &str) -> bool { + directive_name != "link" + } + + fn filter_map(def: TypeSystemDefinition) -> Option { + match def { + TypeSystemDefinition::Schema(schema) => { + Some(TypeSystemDefinition::Schema(schema.map(|schema| { + SchemaDefinition { + directives: schema + .directives + .into_iter() + .filter(|d| filter_directive(d.node.name.node.as_str())) + .collect(), + ..schema + } + }))) + } + TypeSystemDefinition::Directive(directive) => { + if filter_directive(directive.node.name.node.as_str()) { + Some(TypeSystemDefinition::Directive(directive)) + } else { + None + } + } + ty => Some(ty), + } + } + + ServiceDocument { + definitions: sd.definitions.into_iter().filter_map(filter_map).collect(), + } +} + pub fn update_apollo_federation<'a>( operation_type: &'a GraphQLOperationType, ) -> TryFold<'a, (&'a ConfigModule, &'a Field, &'a config::Type, &'a str), FieldDefinition, String> diff --git a/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap b/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap index 7c2d964a18..311df64dd7 100644 --- a/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap +++ b/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap @@ -10,7 +10,7 @@ expression: response "body": { "data": { "_service": { - "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @graphQL(args: [{key: \"id\", value: \"{{.value.id}}\"}], baseURL: \"http://upstream/graphql\", batch: true, name: \"post\") @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @http(batchKey: [\"id\"], path: \"/users\", query: [{key: \"id\", value: \"{{.value.user.id}}\"}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @link directive allows you to import external resources, such as configuration \nā€“ which will be merged into the config importing it ā€“, or a .proto file ā€“ which\n will be later used by `@grpc` directive ā€“.\n\"\"\"\ndirective @link(\n \"\"\"\n The id of the link. It is used to reference the link in the schema.\n \"\"\"\n id: String\n \"\"\"\n Additional metadata pertaining to the linked resource.\n \"\"\"\n meta: JSON\n \"\"\"\n The source of the link. It can be a URL or a path to a file. If a path is provided, \n it is relative to the file that imports the link.\n \"\"\"\n src: String!\n \"\"\"\n The type of the link. It can be `Config`, or `Protobuf`.\n \"\"\"\n type: LinkType\n) repeatable on SCHEMA\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n `routes` allows customization of server endpoint paths. It provides options to change \n the default paths for status and GraphQL endpoints. Default values are: - status: \n \"/status\" - graphQL: \"/graphql\" If not specified, these default values will be used.\n \"\"\"\n routes: Routes\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput Routes {\n graphQL: String!\n status: String!\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" + "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @graphQL(args: [{key: \"id\", value: \"{{.value.id}}\"}], baseURL: \"http://upstream/graphql\", batch: true, name: \"post\") @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @http(batchKey: [\"id\"], path: \"/users\", query: [{key: \"id\", value: \"{{.value.user.id}}\"}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n `routes` allows customization of server endpoint paths. It provides options to change \n the default paths for status and GraphQL endpoints. Default values are: - status: \n \"/status\" - graphQL: \"/graphql\" If not specified, these default values will be used.\n \"\"\"\n routes: Routes\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput Routes {\n graphQL: String!\n status: String!\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" } } } diff --git a/tests/core/snapshots/apollo-federation-entities.md_1.snap b/tests/core/snapshots/apollo-federation-entities.md_1.snap index a2d4f074f8..33868f4d32 100644 --- a/tests/core/snapshots/apollo-federation-entities.md_1.snap +++ b/tests/core/snapshots/apollo-federation-entities.md_1.snap @@ -10,7 +10,7 @@ expression: response "body": { "data": { "_service": { - "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @expr(body: {id: \"{{.value.id}}\", title: \"post-title-{{.value.id}}\"}) @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @call(steps: [{query: \"user\", args: {id: \"{{.value.user.id}}\"}}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @link directive allows you to import external resources, such as configuration \nā€“ which will be merged into the config importing it ā€“, or a .proto file ā€“ which\n will be later used by `@grpc` directive ā€“.\n\"\"\"\ndirective @link(\n \"\"\"\n The id of the link. It is used to reference the link in the schema.\n \"\"\"\n id: String\n \"\"\"\n Additional metadata pertaining to the linked resource.\n \"\"\"\n meta: JSON\n \"\"\"\n The source of the link. It can be a URL or a path to a file. If a path is provided, \n it is relative to the file that imports the link.\n \"\"\"\n src: String!\n \"\"\"\n The type of the link. It can be `Config`, or `Protobuf`.\n \"\"\"\n type: LinkType\n) repeatable on SCHEMA\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n `routes` allows customization of server endpoint paths. It provides options to change \n the default paths for status and GraphQL endpoints. Default values are: - status: \n \"/status\" - graphQL: \"/graphql\" If not specified, these default values will be used.\n \"\"\"\n routes: Routes\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput Routes {\n graphQL: String!\n status: String!\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" + "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @expr(body: {id: \"{{.value.id}}\", title: \"post-title-{{.value.id}}\"}) @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @call(steps: [{query: \"user\", args: {id: \"{{.value.user.id}}\"}}]) @key(fields: \"user { id }\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n `routes` allows customization of server endpoint paths. It provides options to change \n the default paths for status and GraphQL endpoints. Default values are: - status: \n \"/status\" - graphQL: \"/graphql\" If not specified, these default values will be used.\n \"\"\"\n routes: Routes\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput Routes {\n graphQL: String!\n status: String!\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" } } } diff --git a/tests/core/snapshots/apollo-federation-entities.md_merged.snap b/tests/core/snapshots/apollo-federation-entities.md_merged.snap index 7947097524..b88afd6fb4 100644 --- a/tests/core/snapshots/apollo-federation-entities.md_merged.snap +++ b/tests/core/snapshots/apollo-federation-entities.md_merged.snap @@ -4,7 +4,8 @@ expression: formatter --- schema @server(port: 8000) - @upstream(baseURL: "http://jsonplaceholder.typicode.com", batch: {delay: 100, headers: []}, httpCache: 42) { + @upstream(baseURL: "http://jsonplaceholder.typicode.com", batch: {delay: 100, headers: []}, httpCache: 42) + @link(src: "./posts.graphql", type: Config) { query: Query } diff --git a/tests/execution/apollo-federation-entities.md b/tests/execution/apollo-federation-entities.md index ea5e4eabf9..8ee332ce6e 100644 --- a/tests/execution/apollo-federation-entities.md +++ b/tests/execution/apollo-federation-entities.md @@ -3,7 +3,8 @@ ```graphql @config schema @server(port: 8000) - @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) { + @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) + @link(src: "./posts.graphql") { query: Query } @@ -15,7 +16,9 @@ type User @call(steps: [{query: "user", args: {id: "{{.value.user.id}}"}}]) { id: Int! name: String! } +``` +```graphql @file:posts.graphql type Post @expr(body: {id: "{{.value.id}}", title: "post-title-{{.value.id}}"}) { id: Int! title: String!