Closed Bockit closed 8 years ago
Thanks for the bug report! I'll see if can reproduce this and let you know if I have any questions.
@joshprice I put together a reproduction as a test and it passes so it looks like things are good. I think the infinite loop is me calling schema, calling schema, calling schema, calling schema in my definitions.
If you want the test in here I can make it a PR, otherwise I'll drop the branch. Either way, I think this issue is closable. Sorry if you wasted any time on it!
Hadn't had a chance to look at it yet, but glad you got to the bottom of it. A PR with that recursion test in would be fantastic thanks!
Turns out my test wasn't a circular dep, if I went any levels deeper it wouldn't work because elixir isn't mutable and my last reference to fruit in basket doesn't have a basket field.
Basically something like this that I would do in node isn't something I can do in elixir.
var a = {}
var b = {}
b.a = a
a.b = b
console.log(a.b.a.b.a.b.a.b === b) // true
Which in a long story short is to say in the end it's not testing anything you aren't already testing :)
Just had a chat about it in #general
in the elixir slack. The tip about recursive traversal algorithms is intriguing but seeing as the use case I have for it can be solved by using my own recursion for how deep I define the schema I'm going to go with that and the recursive algorithm traversal is probably way out of scope for this library.
So, I saw this Issue just before starting to travel, and ended up coming with a solution ( #39 ) analogous to the js reference implementation of graphql. Where they wrap the fields
in an anonymous function to delay execution, I think we can quote
ours.
Thanks @markolson, really great!
I'm using @markolson's fix (https://github.com/graphql-elixir/graphql/pull/39), however with graphql-relay
, running GraphQL.Relay.generate_schema_json!
throws this error:
[debug] Updating GraphQL schema.json
** (FunctionClauseError) no function clause matching in GraphQL.Type.CompositeType.do_get_fields/1
(graphql) lib/graphql/type/composite_type.ex:33: GraphQL.Type.CompositeType.do_get_fields({:%{}, [], [max_score: {:%{}, [], [type: {:%, [], [{:__aliases__, [alias: false], [:GraphQL, :Type, :Float]}, {:%{}, [], []}]}, resolve: {:fn, [], [{:->, [], [[{:_, [], VideoSchema}, {:_, [], VideoSchema}, {:_, [], VideoSchema}], 1]}]}]}, videos: {:%{}, [], [type: {{:., [], [Access, :get]}, [], [{:video_connection, [], VideoSchema}, :connection_type]}, args: {{:., [], [{:__aliases__, [alias: GraphQL.Relay.Connection], [:Connection]}, :args]}, [], []}, resolve: {:fn, [], [{:->, [], [[{:videos, [], VideoSchema}, {:args, [], VideoSchema}, {:_ctx, [], VideoSchema}], {:__block__, [], [{{:., [], [{:__aliases__, [alias: false], [:IO]}, :inspect]}, [], [{:args, [], VideoSchema}]}, {:=, [], [{:cursor_count, [], VideoSchema}, {:try, [], [[do: {{:., [], [{:__aliases__, [alias: GraphQL.Relay.Connection.List], [:Connection, :List]}, :cursor_to_offset]}, [], [{:||, [context: VideoSchema, import: Kernel], [{{:., [], [Access, :get]}, [], [{:args, [], VideoSchema}, :after]}, {{:., [], [Access, :get]}, [], [{:args, [], VideoSchema}, :before]}]}]}, rescue: [{:->, [], [[{:__aliases__, [alias: false], [:FunctionClauseError]}], 0]}]]]}]}, {:=, [], [{:count, [], VideoSchema}, {:+, [context: VideoSchema, import: Kernel], [{:+, [context: VideoSchema, import: Kernel], [{:cursor_count, [], VideoSchema}, {{:., [], [Access, :get]}, [], [{:args, [], VideoSchema}, :first]}]}, 1]}]}, {{:., [], [{:__aliases__, [alias: GraphQL.Relay.Connection.List], [:Connection, :List]}, :resolve]}, [], [{:videos, [], VideoSchema}, {:args, [], VideoSchema}]}]}]}]}]}]})
(graphql) lib/graphql/type/schema.ex:56: GraphQL.Schema.reduce_types/2
(stdlib) lists.erl:1262: :lists.foldl/3
(graphql) lib/graphql/type/schema.ex:57: GraphQL.Schema.reduce_types/2
(stdlib) lists.erl:1262: :lists.foldl/3
(graphql) lib/graphql/type/schema.ex:57: GraphQL.Schema.reduce_types/2
(graphql) lib/graphql/type/schema.ex:31: GraphQL.Schema.reduce_types/1
(graphql) lib/graphql/type/schema.ex:26: GraphQL.Schema.type_from_ast/2
(graphql) lib/graphql/lang/ast/type_info_visitor.ex:100: GraphQL.Lang.AST.Visitor.GraphQL.Lang.AST.TypeInfoVisitor.enter/3
(graphql) lib/graphql/lang/ast/composite_visitor.ex:71: GraphQL.Lang.AST.Visitor.GraphQL.Lang.AST.CompositeVisitor.call_in_order/5
(graphql) lib/graphql/lang/ast/reducer.ex:33: GraphQL.Lang.AST.Reducer.visit/3
(graphql) lib/graphql/lang/ast/reducer.ex:21: GraphQL.Lang.AST.Reducer.visit/3
(graphql) lib/graphql/lang/ast/reducer.ex:51: GraphQL.Lang.AST.Reducer.visit_each_child/3
(graphql) lib/graphql/lang/ast/reducer.ex:36: GraphQL.Lang.AST.Reducer.visit/3
(graphql) lib/graphql/lang/ast/reducer.ex:16: GraphQL.Lang.AST.Reducer.reduce/3
(graphql) lib/graphql/validation/validator.ex:29: GraphQL.Validation.Validator.validate_with_rules/3
(graphql) lib/graphql.ex:44: GraphQL.execute_with_optional_validation/6
lib/graphql_relay.ex:28: GraphQL.Relay.introspection/0
lib/graphql_relay.ex:23: GraphQL.Relay.generate_schema_json!/0
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
I'm not sure whether this is an issue with graphql-elixir
or graphql-relay
?
EDIT: I get the error when starting the server too, so I don't think it's related to graphql-relay
.
Thanks for the bug report.
Not sure which branch you mean, but please try using the latest GraphQL release. Also it would be great to see your schema to help debug this.
On Mon, Apr 18, 2016 at 3:19 PM, Ashley Williams notifications@github.com wrote:
I'm using @markolson https://github.com/markolson's branch with this fix, however when using graphql-relay, running GraphQL.Relay.generate_schema_json! throws this error:
[debug] Updating GraphQL schema.json \ (FunctionClauseError) no function clause matching in GraphQL.Type.CompositeType.do_get_fields/1 (graphql) lib/graphql/type/composite_type.ex:33: GraphQL.Type.CompositeType.do_get_fields({:%{}, [], [maxscore: {:%{}, [], [type: {:%, [], [{:aliases, [alias: false], [:GraphQL, :Type, :Float]}, {:%{}, [], []}]}, resolve: {:fn, [], [{:->, [], [[{:, [], VideoSchema}, {:, [], VideoSchema}, {:, [], VideoSchema}], 1]}]}]}, videos: {:%{}, [], [type: {{:., [], [Access, :get]}, [], [{:video_connection, [], VideoSchema}, :connection_type]}, args: {{:., [], [{:aliases, [alias: GraphQL.Relay.Connection], [:Connection]}, :args]}, [], []}, resolve: {:fn, [], [{:->, [], [[{:videos, [], VideoSchema}, {:args, [], VideoSchema}, {:_ctx, [], VideoSchema}], {:block, [], [{{:., [], [{:aliases, [alias: false], [:IO]}, :inspect]}, [], [{:args, [], VideoSchema}]}, {:=, [], [{:cursor_count, [], VideoSchema}, {:try, [], [[do: {{:., [], [{:aliases, [alias: GraphQL.Relay.Connection.List], [:Connection, :List]}, :cursor_to_offset]}, [], [{:||, [context: VideoSchema, imp ort: Ker nel], [{{:., [], [Access, :get]}, [], [{:args, [], VideoSchema}, :after]}, {{:., [], [Access, :get]}, [], [{:args, [], VideoSchema}, :before]}]}]}, rescue: [{:->, [], [[{:aliases, [alias: false], [:FunctionClauseError]}], 0]}]]]}]}, {:=, [], [{:count, [], VideoSchema}, {:+, [context: VideoSchema, import: Kernel], [{:+, [context: VideoSchema, import: Kernel], [{:cursor_count, [], VideoSchema}, {{:., [], [Access, :get]}, [], [{:args, [], VideoSchema}, :first]}]}, 1]}]}, {{:., [], [{:aliases, [alias: GraphQL.Relay.Connection.List], [:Connection, :List]}, :resolve]}, [], [{:videos, [], VideoSchema}, {:args, [], VideoSchema}]}]}]}]}]}]}) (graphql) lib/graphql/type/schema.ex:56: GraphQL.Schema.reduce_types/2 (stdlib) lists.erl:1262: :lists.foldl/3 (graphql) lib/graphql/type/schema.ex:57: GraphQL.Schema.reduce_types/2 (stdlib) lists.erl:1262: :lists.foldl/3 (graphql) lib/graphql/type/schema.ex:57: GraphQL.Schema.reduce_types/2 (graphql) lib/graphql/type/schema.ex:31: GraphQL.Schema.reduce_types/1 (graphql) lib/graphql/type/schema.ex:26: GraphQL.Schema.type_from_ast/2 (graphql) lib/graphql/lang/ast/type_info_visitor.ex:100: GraphQL.Lang.AST.Visitor.GraphQL.Lang.AST.TypeInfoVisitor.enter/3 (graphql) lib/graphql/lang/ast/composite_visitor.ex:71: GraphQL.Lang.AST.Visitor.GraphQL.Lang.AST.CompositeVisitor.call_in_order/5 (graphql) lib/graphql/lang/ast/reducer.ex:33: GraphQL.Lang.AST.Reducer.visit/3 (graphql) lib/graphql/lang/ast/reducer.ex:21: GraphQL.Lang.AST.Reducer.visit/3 (graphql) lib/graphql/lang/ast/reducer.ex:51: GraphQL.Lang.AST.Reducer.visit_each_child/3 (graphql) lib/graphql/lang/ast/reducer.ex:36: GraphQL.Lang.AST.Reducer.visit/3 (graphql) lib/graphql/lang/ast/reducer.ex:16: GraphQL.Lang.AST.Reducer.reduce/3 (graphql) lib/graphql/validation/validator.ex:29: GraphQL.Validation.Validator.validate_with_rules/3 (graphql) lib/graphql.ex:44: GraphQL.execute_with_optional_validation/6 lib/graphql_relay.ex:28: GraphQL.Relay.introspection/0 lib/graphql_relay.ex:23: GraphQL.Relay.generate_schema_json!/0 (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
I'm not sure whether this is an issue with graphql-elixir or graphql-relay ?
— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/graphql-elixir/graphql/issues/38#issuecomment-211206530
This is using the master branch. I think this is the relevant schema:
def node_interface do
Node.define_interface(fn(obj) ->
case obj do
%{videos: _videos} ->
video_list
_ ->
video
end
end)
end
def video_connection do
%{
name: "Video",
node_type: video,
edge_fields: %{},
connection_fields: %{},
resolve_node: nil,
resolve_cursor: nil
} |> Connection.new
end
def video do
%GraphQL.Type.ObjectType{
name: "Video",
description: "A Video",
fields: quote do %{
id: Node.global_id_field("video"),
more_like_this: %{
type: %GraphQL.Type.List{ ofType: video }
args: %{
count: %{type: %GraphQL.Type.Int{}}
},
resolve: fn _, _, _ -> end
}
} end,
interfaces: [node_interface]
}
end
Thanks!
So the quote
fix is deprecated since we support referencing types as modules (see https://github.com/graphql-elixir/graphql/blob/master/test/support/star_wars/schema.exs for an example).
Update: this is definitely a schema issue, and I suspect the quoting might be causing this.
Ah — thanks!
Although I think there may still be an issue. type: %{type: VideoList}
doesn't work, yet type: %List{ofType: Video}
does. Is that intended? Or am I doing something wrong? :)
defmodule VideoList do
def video_connection do
%{
name: "Video",
node_type: Video,
edge_fields: %{},
connection_fields: %{},
resolve_node: nil,
resolve_cursor: nil
} |> Connection.new
end
def type do
%ObjectType{
name: "Videos",
description: "List of Videos",
fields: %{
videos: %{
type: video_connection[:connection_type],
args: Connection.args,
resolve: fn(videos, args, _ctx) ->
cursor_count = try do
Connection.List.cursor_to_offset(args[:after] || args[:before])
rescue
FunctionClauseError -> 0
end
count = cursor_count + args[:first] + 1
Connection.List.resolve(videos, args)
end
}
}
}
end
end
defmodule Video do
def type do
%ObjectType{
name: "Video",
description: "A YouTube video",
fields: %{
id: Node.global_id_field("video"),
more_like_this: %{
type: %{type: VideoList},
resolve: fn _, _, _ -> end
}
}
}
end
end
[debug] Updating GraphQL schema.json
** (FunctionClauseError) no function clause matching in GraphQL.Schema.reduce_types/2
(graphql) lib/graphql/type/schema.ex:36: GraphQL.Schema.reduce_types(%{"Channel" => %GraphQL.Type.ObjectType{description: "YouTube channel", fields: %{title: %{type: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}, video_list: %{type: VideoSchema.VideoList}}, interfaces: [], isTypeOf: nil, name: "Channel"}, "Float" => %GraphQL.Type.Float{description: "The `Float` scalar type represents signed double-precision fractional\nvalues as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).\n", name: "Float"}, "ID" => %GraphQL.Type.ID{description: "The `ID` scalar type represents a unique identifier, often used to\nrefetch an object or as key for a cache. The ID type appears in a JSON\nresponse as a String; however, it is not intended to be human-readable.\nWhen expected as an input type, any string (such as `\"4\"`) or integer\n(such as `4`) input value will be accepted as an ID.\n", name: "ID"}, "Int" => %GraphQL.Type.Int{description: "The `Int` scalar type represents non-fractional signed whole numeric\nvalues. Int can represent values between -(2^53 - 1) and 2^53 - 1 since\nrepresented in JSON as double-precision floating point numbers specified\nby [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).\n", name: "Int"}, "Query" => %GraphQL.Type.ObjectType{description: "", fields: %{channel: %{args: %{id: %{type: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}}, resolve: &VideoSchema.show_channel/3, type: VideoSchema.Channel}, mostPopular: %{resolve: #Function<0.116378938/3 in VideoSchema.schema/0>, type: VideoSchema.VideoList}, search: %{args: %{query: %{type: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}}, resolve: #Function<1.116378938/3 in VideoSchema.schema/0>, type: VideoSchema.Search}, video: %{args: %{yt_id: %{type: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}}, resolve: &VideoSchema.show_video/3, type: VideoSchema.Video}, viewer: %{resolve: #Function<2.116378938/3 in VideoSchema.schema/0>, type: VideoSchema.Viewer}}, interfaces: [], isTypeOf: nil, name: "Query"}, "String" => %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}, "Video" => %GraphQL.Type.ObjectType{description: "A YouTube video", fields: %{id: %{description: "The ID of an object", name: "id", resolve: #Function<1.110845899/3 in GraphQL.Relay.Node.global_id_field/1>, type: %GraphQL.Type.NonNull{ofType: %GraphQL.Type.ID{description: "The `ID` scalar type represents a unique identifier, often used to\nrefetch an object or as key for a cache. The ID type appears in a JSON\nresponse as a String; however, it is not intended to be human-readable.\nWhen expected as an input type, any string (such as `\"4\"`) or integer\n(such as `4`) input value will be accepted as an ID.\n", name: "ID"}}}, more_like_this: %{resolve: #Function<0.119064294/3 in VideoSchema.Video.type/0>, type: %{type: VideoSchema.VideoList}}}, interfaces: [], isTypeOf: nil, name: "Video"}, "VideoConnection" => %GraphQL.Type.ObjectType{description: "A connection to a list of items.", fields: %{edges: %{description: "Information to aid in pagination.", type: %GraphQL.Type.List{ofType: %GraphQL.Type.ObjectType{description: "An edge in a connection.", fields: %{cursor: %{description: "A cursor for use in pagination", resolve: nil, type: %GraphQL.Type.NonNull{ofType: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}}, node: %{description: "The item at the end of the edge", resolve: nil, type: VideoSchema.Video}}, interfaces: [], isTypeOf: nil, name: "VideoEdge"}}}, pageInfo: %{description: "Information to aid in pagination.", type: %GraphQL.Type.NonNull{ofType: %GraphQL.Type.ObjectType{description: "Information about pagination in a connection.", fields: %{endCursor: %{description: "When paginating forwards, the cursor to continue.", type: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}, hasNextPage: %{description: "When paginating forwards, are there more items?", type: %GraphQL.Type.NonNull{ofType: %GraphQL.Type.Boolean{description: "The `Boolean` scalar type represents `true` or `false`.\n", name: "Boolean"}}}, hasPreviousPage: %{description: "When paginating backwards, are there more items?", type: %GraphQL.Type.NonNull{ofType: %GraphQL.Type.Boolean{description: "The `Boolean` scalar type represents `true` or `false`.\n", name: "Boolean"}}}, startCursor: %{description: "When paginating backwards, the cursor to continue.", type: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}}, interfaces: [], isTypeOf: nil, name: "PageInfo"}}}}, interfaces: [], isTypeOf: nil, name: "VideoConnection"}, "VideoEdge" => %GraphQL.Type.ObjectType{description: "An edge in a connection.", fields: %{cursor: %{description: "A cursor for use in pagination", resolve: nil, type: %GraphQL.Type.NonNull{ofType: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}}, node: %{description: "The item at the end of the edge", resolve: nil, type: VideoSchema.Video}}, interfaces: [], isTypeOf: nil, name: "VideoEdge"}, "Videos" => %GraphQL.Type.ObjectType{description: "List of Videos", fields: %{max_score: %{resolve: #Function<0.47853847/3 in VideoSchema.VideoList.type/0>, type: %GraphQL.Type.Float{description: "The `Float` scalar type represents signed double-precision fractional\nvalues as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).\n", name: "Float"}}, videos: %{args: %{after: %{type: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}, before: %{type: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}, first: %{type: %GraphQL.Type.Int{description: "The `Int` scalar type represents non-fractional signed whole numeric\nvalues. Int can represent values between -(2^53 - 1) and 2^53 - 1 since\nrepresented in JSON as double-precision floating point numbers specified\nby [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).\n", name: "Int"}}, last: %{type: %GraphQL.Type.Int{description: "The `Int` scalar type represents non-fractional signed whole numeric\nvalues. Int can represent values between -(2^53 - 1) and 2^53 - 1 since\nrepresented in JSON as double-precision floating point numbers specified\nby [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).\n", name: "Int"}}}, resolve: #Function<1.47853847/3 in VideoSchema.VideoList.type/0>, type: %GraphQL.Type.ObjectType{description: "A connection to a list of items.", fields: %{edges: %{description: "Information to aid in pagination.", type: %GraphQL.Type.List{ofType: %GraphQL.Type.ObjectType{description: "An edge in a connection.", fields: %{cursor: %{description: "A cursor for use in pagination", resolve: nil, type: %GraphQL.Type.NonNull{ofType: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}}, node: %{description: "The item at the end of the edge", resolve: nil, type: VideoSchema.Video}}, interfaces: [], isTypeOf: nil, name: "VideoEdge"}}}, pageInfo: %{description: "Information to aid in pagination.", type: %GraphQL.Type.NonNull{ofType: %GraphQL.Type.ObjectType{description: "Information about pagination in a connection.", fields: %{endCursor: %{description: "When paginating forwards, the cursor to continue.", type: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}, hasNextPage: %{description: "When paginating forwards, are there more items?", type: %GraphQL.Type.NonNull{ofType: %GraphQL.Type.Boolean{description: "The `Boolean` scalar type represents `true` or `false`.\n", name: "Boolean"}}}, hasPreviousPage: %{description: "When paginating backwards, are there more items?", type: %GraphQL.Type.NonNull{ofType: %GraphQL.Type.Boolean{description: "The `Boolean` scalar type represents `true` or `false`.\n", name: "Boolean"}}}, startCursor: %{description: "When paginating backwards, the cursor to continue.", type: %GraphQL.Type.String{description: "The `String` scalar type represents textual data, represented as UTF-8\ncharacter sequences. The String type is most often used by GraphQL to\nrepresent free-form human-readable text.\n", name: "String"}}}, interfaces: [], isTypeOf: nil, name: "PageInfo"}}}}, interfaces: [], isTypeOf: nil, name: "VideoConnection"}}}, interfaces: [], isTypeOf: nil, name: "Videos"}}, %{type: VideoSchema.VideoList})
The second argument that function looks wrong: %{type: VideoSchema.VideoList}
should be just VideoSchema.VideoList
I suspect.
So change type: %{type: VideoList}
to type: VideoList
.
If that is still broken please open a new issue as this isn't related to the original issue. Or ping me in the GraphQL or Elixir slacks.
@joshprice You're right, it should just be type: VideoList
, not type: {type: VideoList}
. Thank you for your help! 😊
Hi. Firstly, thanks for all the work on graphql-elixir and plug-graphql.
I'm having trouble with a circular schema definition. Using plug_graphql in phoenix and the server freezes up no matter the query when the circular definitions are in play.
Relations are:
https://github.com/Bockit/budget/tree/master/apps/api-server/web/graphql is the graphql schema I have for this model. The root query provides a singular and list type query for each model. Each model has a resolve tag for its relations.
Before I added the relations to the Tag schema, things were great but once I added the recurrings and transactions relations to the Tag schema all requests freeze up in what appears to be an infinite loop. Even if you don't request any of the relation fields.
If you comment out the tags field on Transaction, and the recurrings field on Tag (breaking the circle) then you can do the following and it works as expected:
If this is confusing I can make a simpler, proof-of-concept reproduction tomorrow. If I'm correct and this is causing an infinite loop with a circular reference as described then a simple schema with 2 object types that both refer back to each other should cause the same effect.
Thanks for your time.