graphql 1.11.6 → 1.13.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (293) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -8
  3. data/lib/generators/graphql/enum_generator.rb +4 -10
  4. data/lib/generators/graphql/field_extractor.rb +31 -0
  5. data/lib/generators/graphql/input_generator.rb +50 -0
  6. data/lib/generators/graphql/install/mutation_root_generator.rb +34 -0
  7. data/lib/generators/graphql/install_generator.rb +17 -7
  8. data/lib/generators/graphql/interface_generator.rb +7 -7
  9. data/lib/generators/graphql/loader_generator.rb +1 -0
  10. data/lib/generators/graphql/mutation_create_generator.rb +22 -0
  11. data/lib/generators/graphql/mutation_delete_generator.rb +22 -0
  12. data/lib/generators/graphql/mutation_generator.rb +6 -30
  13. data/lib/generators/graphql/mutation_update_generator.rb +22 -0
  14. data/lib/generators/graphql/object_generator.rb +12 -38
  15. data/lib/generators/graphql/orm_mutations_base.rb +40 -0
  16. data/lib/generators/graphql/relay.rb +63 -0
  17. data/lib/generators/graphql/relay_generator.rb +21 -0
  18. data/lib/generators/graphql/scalar_generator.rb +4 -2
  19. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  20. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  21. data/lib/generators/graphql/templates/enum.erb +5 -1
  22. data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
  23. data/lib/generators/graphql/templates/input.erb +9 -0
  24. data/lib/generators/graphql/templates/interface.erb +4 -2
  25. data/lib/generators/graphql/templates/mutation.erb +1 -1
  26. data/lib/generators/graphql/templates/mutation_create.erb +20 -0
  27. data/lib/generators/graphql/templates/mutation_delete.erb +20 -0
  28. data/lib/generators/graphql/templates/mutation_update.erb +21 -0
  29. data/lib/generators/graphql/templates/node_type.erb +9 -0
  30. data/lib/generators/graphql/templates/object.erb +5 -3
  31. data/lib/generators/graphql/templates/query_type.erb +1 -3
  32. data/lib/generators/graphql/templates/scalar.erb +3 -1
  33. data/lib/generators/graphql/templates/schema.erb +19 -34
  34. data/lib/generators/graphql/templates/union.erb +4 -2
  35. data/lib/generators/graphql/type_generator.rb +47 -10
  36. data/lib/generators/graphql/union_generator.rb +5 -5
  37. data/lib/graphql/analysis/analyze_query.rb +7 -0
  38. data/lib/graphql/analysis/ast/field_usage.rb +28 -1
  39. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  40. data/lib/graphql/analysis/ast/visitor.rb +14 -5
  41. data/lib/graphql/analysis/ast.rb +11 -2
  42. data/lib/graphql/argument.rb +1 -1
  43. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  44. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  45. data/lib/graphql/backtrace/table.rb +34 -3
  46. data/lib/graphql/backtrace/traced_error.rb +0 -1
  47. data/lib/graphql/backtrace/tracer.rb +40 -10
  48. data/lib/graphql/backtrace.rb +28 -19
  49. data/lib/graphql/backwards_compatibility.rb +2 -1
  50. data/lib/graphql/base_type.rb +6 -4
  51. data/lib/graphql/boolean_type.rb +1 -1
  52. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  53. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  54. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  55. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  56. data/lib/graphql/dataloader/null_dataloader.rb +22 -0
  57. data/lib/graphql/dataloader/request.rb +19 -0
  58. data/lib/graphql/dataloader/request_all.rb +19 -0
  59. data/lib/graphql/dataloader/source.rb +155 -0
  60. data/lib/graphql/dataloader.rb +308 -0
  61. data/lib/graphql/date_encoding_error.rb +16 -0
  62. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  63. data/lib/graphql/define/instance_definable.rb +48 -3
  64. data/lib/graphql/define/type_definer.rb +5 -5
  65. data/lib/graphql/deprecated_dsl.rb +18 -5
  66. data/lib/graphql/deprecation.rb +9 -0
  67. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  68. data/lib/graphql/directive/include_directive.rb +1 -1
  69. data/lib/graphql/directive/skip_directive.rb +1 -1
  70. data/lib/graphql/directive.rb +1 -5
  71. data/lib/graphql/enum_type.rb +9 -3
  72. data/lib/graphql/execution/errors.rb +110 -7
  73. data/lib/graphql/execution/execute.rb +8 -1
  74. data/lib/graphql/execution/interpreter/arguments.rb +57 -5
  75. data/lib/graphql/execution/interpreter/arguments_cache.rb +49 -15
  76. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  77. data/lib/graphql/execution/interpreter/resolve.rb +37 -25
  78. data/lib/graphql/execution/interpreter/runtime.rb +670 -294
  79. data/lib/graphql/execution/interpreter.rb +16 -16
  80. data/lib/graphql/execution/lazy.rb +5 -1
  81. data/lib/graphql/execution/lookahead.rb +2 -2
  82. data/lib/graphql/execution/multiplex.rb +39 -23
  83. data/lib/graphql/field.rb +1 -1
  84. data/lib/graphql/float_type.rb +1 -1
  85. data/lib/graphql/function.rb +4 -0
  86. data/lib/graphql/id_type.rb +1 -1
  87. data/lib/graphql/input_object_type.rb +3 -1
  88. data/lib/graphql/int_type.rb +1 -1
  89. data/lib/graphql/integer_decoding_error.rb +17 -0
  90. data/lib/graphql/integer_encoding_error.rb +18 -2
  91. data/lib/graphql/interface_type.rb +4 -2
  92. data/lib/graphql/internal_representation/document.rb +2 -2
  93. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  94. data/lib/graphql/introspection/directive_location_enum.rb +2 -2
  95. data/lib/graphql/introspection/directive_type.rb +11 -5
  96. data/lib/graphql/introspection/entry_points.rb +2 -2
  97. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  98. data/lib/graphql/introspection/field_type.rb +3 -3
  99. data/lib/graphql/introspection/input_value_type.rb +10 -4
  100. data/lib/graphql/introspection/schema_type.rb +10 -5
  101. data/lib/graphql/introspection/type_type.rb +18 -12
  102. data/lib/graphql/introspection.rb +5 -2
  103. data/lib/graphql/invalid_null_error.rb +1 -1
  104. data/lib/graphql/language/block_string.rb +2 -6
  105. data/lib/graphql/language/cache.rb +37 -0
  106. data/lib/graphql/language/document_from_schema_definition.rb +60 -26
  107. data/lib/graphql/language/lexer.rb +50 -28
  108. data/lib/graphql/language/lexer.rl +2 -4
  109. data/lib/graphql/language/nodes.rb +14 -4
  110. data/lib/graphql/language/parser.rb +856 -825
  111. data/lib/graphql/language/parser.y +28 -11
  112. data/lib/graphql/language/printer.rb +10 -1
  113. data/lib/graphql/language/sanitized_printer.rb +5 -5
  114. data/lib/graphql/language/token.rb +0 -4
  115. data/lib/graphql/language.rb +1 -0
  116. data/lib/graphql/name_validator.rb +0 -4
  117. data/lib/graphql/object_type.rb +4 -4
  118. data/lib/graphql/pagination/active_record_relation_connection.rb +47 -3
  119. data/lib/graphql/pagination/connection.rb +19 -1
  120. data/lib/graphql/pagination/connections.rb +45 -30
  121. data/lib/graphql/pagination/relation_connection.rb +69 -28
  122. data/lib/graphql/parse_error.rb +0 -1
  123. data/lib/graphql/query/arguments.rb +2 -2
  124. data/lib/graphql/query/arguments_cache.rb +1 -2
  125. data/lib/graphql/query/context.rb +22 -4
  126. data/lib/graphql/query/executor.rb +0 -1
  127. data/lib/graphql/query/input_validation_result.rb +9 -0
  128. data/lib/graphql/query/literal_input.rb +1 -1
  129. data/lib/graphql/query/null_context.rb +21 -9
  130. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  131. data/lib/graphql/query/serial_execution.rb +1 -0
  132. data/lib/graphql/query/validation_pipeline.rb +3 -4
  133. data/lib/graphql/query/variable_validation_error.rb +3 -3
  134. data/lib/graphql/query/variables.rb +35 -4
  135. data/lib/graphql/query.rb +20 -8
  136. data/lib/graphql/railtie.rb +9 -1
  137. data/lib/graphql/rake_task.rb +3 -0
  138. data/lib/graphql/relay/array_connection.rb +2 -2
  139. data/lib/graphql/relay/base_connection.rb +7 -0
  140. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  141. data/lib/graphql/relay/connection_type.rb +16 -3
  142. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  143. data/lib/graphql/relay/global_id_resolve.rb +1 -2
  144. data/lib/graphql/relay/mutation.rb +2 -1
  145. data/lib/graphql/relay/node.rb +3 -0
  146. data/lib/graphql/relay/page_info.rb +1 -1
  147. data/lib/graphql/relay/range_add.rb +14 -5
  148. data/lib/graphql/relay/type_extensions.rb +2 -0
  149. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  150. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  151. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  152. data/lib/graphql/rubocop.rb +4 -0
  153. data/lib/graphql/scalar_type.rb +3 -1
  154. data/lib/graphql/schema/addition.rb +247 -0
  155. data/lib/graphql/schema/argument.rb +177 -21
  156. data/lib/graphql/schema/build_from_definition.rb +150 -55
  157. data/lib/graphql/schema/default_type_error.rb +2 -0
  158. data/lib/graphql/schema/directive/feature.rb +1 -1
  159. data/lib/graphql/schema/directive/flagged.rb +57 -0
  160. data/lib/graphql/schema/directive/include.rb +1 -1
  161. data/lib/graphql/schema/directive/skip.rb +1 -1
  162. data/lib/graphql/schema/directive/transform.rb +14 -2
  163. data/lib/graphql/schema/directive.rb +103 -4
  164. data/lib/graphql/schema/enum.rb +72 -11
  165. data/lib/graphql/schema/enum_value.rb +18 -6
  166. data/lib/graphql/schema/field/connection_extension.rb +4 -2
  167. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  168. data/lib/graphql/schema/field.rb +332 -111
  169. data/lib/graphql/schema/field_extension.rb +89 -2
  170. data/lib/graphql/schema/find_inherited_value.rb +4 -1
  171. data/lib/graphql/schema/finder.rb +5 -5
  172. data/lib/graphql/schema/input_object.rb +79 -55
  173. data/lib/graphql/schema/interface.rb +12 -20
  174. data/lib/graphql/schema/introspection_system.rb +1 -1
  175. data/lib/graphql/schema/list.rb +21 -4
  176. data/lib/graphql/schema/loader.rb +11 -0
  177. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  178. data/lib/graphql/schema/member/base_dsl_methods.rb +5 -16
  179. data/lib/graphql/schema/member/build_type.rb +4 -7
  180. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  181. data/lib/graphql/schema/member/has_arguments.rb +166 -74
  182. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  183. data/lib/graphql/schema/member/has_directives.rb +98 -0
  184. data/lib/graphql/schema/member/has_fields.rb +77 -22
  185. data/lib/graphql/schema/member/has_interfaces.rb +100 -0
  186. data/lib/graphql/schema/member/has_validators.rb +31 -0
  187. data/lib/graphql/schema/member/instrumentation.rb +0 -1
  188. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  189. data/lib/graphql/schema/member/validates_input.rb +2 -2
  190. data/lib/graphql/schema/member.rb +5 -0
  191. data/lib/graphql/schema/middleware_chain.rb +1 -1
  192. data/lib/graphql/schema/non_null.rb +9 -3
  193. data/lib/graphql/schema/object.rb +40 -80
  194. data/lib/graphql/schema/printer.rb +16 -20
  195. data/lib/graphql/schema/relay_classic_mutation.rb +38 -4
  196. data/lib/graphql/schema/resolver/has_payload_type.rb +29 -2
  197. data/lib/graphql/schema/resolver.rb +110 -64
  198. data/lib/graphql/schema/scalar.rb +18 -2
  199. data/lib/graphql/schema/subscription.rb +55 -9
  200. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  201. data/lib/graphql/schema/traversal.rb +1 -1
  202. data/lib/graphql/schema/type_expression.rb +1 -1
  203. data/lib/graphql/schema/type_membership.rb +18 -4
  204. data/lib/graphql/schema/union.rb +8 -1
  205. data/lib/graphql/schema/validation.rb +4 -2
  206. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  207. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  208. data/lib/graphql/schema/validator/exclusion_validator.rb +33 -0
  209. data/lib/graphql/schema/validator/format_validator.rb +48 -0
  210. data/lib/graphql/schema/validator/inclusion_validator.rb +35 -0
  211. data/lib/graphql/schema/validator/length_validator.rb +59 -0
  212. data/lib/graphql/schema/validator/numericality_validator.rb +82 -0
  213. data/lib/graphql/schema/validator/required_validator.rb +82 -0
  214. data/lib/graphql/schema/validator.rb +171 -0
  215. data/lib/graphql/schema/warden.rb +126 -53
  216. data/lib/graphql/schema.rb +262 -281
  217. data/lib/graphql/static_validation/all_rules.rb +2 -0
  218. data/lib/graphql/static_validation/base_visitor.rb +9 -6
  219. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  220. data/lib/graphql/static_validation/error.rb +3 -1
  221. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  222. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +4 -2
  223. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
  224. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  225. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  226. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  227. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  228. data/lib/graphql/static_validation/rules/fields_will_merge.rb +90 -47
  229. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  230. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  231. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  232. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  233. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  234. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  235. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -2
  236. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  237. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  238. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +14 -8
  239. data/lib/graphql/static_validation/validation_context.rb +12 -2
  240. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  241. data/lib/graphql/static_validation/validator.rb +41 -10
  242. data/lib/graphql/static_validation.rb +1 -0
  243. data/lib/graphql/string_encoding_error.rb +13 -3
  244. data/lib/graphql/string_type.rb +1 -1
  245. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +39 -8
  246. data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
  247. data/lib/graphql/subscriptions/event.rb +68 -32
  248. data/lib/graphql/subscriptions/instrumentation.rb +0 -1
  249. data/lib/graphql/subscriptions/serialize.rb +34 -5
  250. data/lib/graphql/subscriptions/subscription_root.rb +1 -1
  251. data/lib/graphql/subscriptions.rb +34 -39
  252. data/lib/graphql/tracing/active_support_notifications_tracing.rb +8 -21
  253. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  254. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  255. data/lib/graphql/tracing/data_dog_tracing.rb +24 -2
  256. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  257. data/lib/graphql/tracing/platform_tracing.rb +24 -12
  258. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  259. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  260. data/lib/graphql/tracing.rb +2 -2
  261. data/lib/graphql/types/big_int.rb +5 -1
  262. data/lib/graphql/types/int.rb +10 -3
  263. data/lib/graphql/types/iso_8601_date.rb +13 -5
  264. data/lib/graphql/types/iso_8601_date_time.rb +8 -1
  265. data/lib/graphql/types/relay/base_connection.rb +6 -91
  266. data/lib/graphql/types/relay/base_edge.rb +2 -34
  267. data/lib/graphql/types/relay/connection_behaviors.rb +174 -0
  268. data/lib/graphql/types/relay/default_relay.rb +31 -0
  269. data/lib/graphql/types/relay/edge_behaviors.rb +64 -0
  270. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  271. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  272. data/lib/graphql/types/relay/node.rb +2 -4
  273. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  274. data/lib/graphql/types/relay/node_field.rb +3 -22
  275. data/lib/graphql/types/relay/nodes_field.rb +16 -18
  276. data/lib/graphql/types/relay/page_info.rb +2 -14
  277. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  278. data/lib/graphql/types/relay.rb +11 -3
  279. data/lib/graphql/types/string.rb +8 -2
  280. data/lib/graphql/unauthorized_error.rb +1 -1
  281. data/lib/graphql/union_type.rb +3 -1
  282. data/lib/graphql/upgrader/member.rb +1 -0
  283. data/lib/graphql/upgrader/schema.rb +1 -0
  284. data/lib/graphql/version.rb +1 -1
  285. data/lib/graphql.rb +68 -37
  286. data/readme.md +3 -6
  287. metadata +83 -113
  288. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
  289. data/lib/graphql/types/relay/base_field.rb +0 -22
  290. data/lib/graphql/types/relay/base_interface.rb +0 -29
  291. data/lib/graphql/types/relay/base_object.rb +0 -26
  292. /data/lib/generators/graphql/{templates → install/templates}/base_mutation.erb +0 -0
  293. /data/lib/generators/graphql/{templates → install/templates}/mutation_type.erb +0 -0
@@ -8,6 +8,137 @@ module GraphQL
8
8
  #
9
9
  # @api private
10
10
  class Runtime
11
+
12
+ module GraphQLResult
13
+ def initialize(result_name, parent_result)
14
+ @graphql_parent = parent_result
15
+ if parent_result && parent_result.graphql_dead
16
+ @graphql_dead = true
17
+ end
18
+ @graphql_result_name = result_name
19
+ # Jump through some hoops to avoid creating this duplicate storage if at all possible.
20
+ @graphql_metadata = nil
21
+ end
22
+
23
+ attr_accessor :graphql_dead
24
+ attr_reader :graphql_parent, :graphql_result_name
25
+
26
+ # Although these are used by only one of the Result classes,
27
+ # it's handy to have the methods implemented on both (even though they just return `nil`)
28
+ # because it makes it easy to check if anything is assigned.
29
+ # @return [nil, Array<String>]
30
+ attr_accessor :graphql_non_null_field_names
31
+ # @return [nil, true]
32
+ attr_accessor :graphql_non_null_list_items
33
+
34
+ # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
35
+ attr_accessor :graphql_result_data
36
+ end
37
+
38
+ class GraphQLResultHash
39
+ def initialize(_result_name, _parent_result)
40
+ super
41
+ @graphql_result_data = {}
42
+ end
43
+
44
+ include GraphQLResult
45
+
46
+ attr_accessor :graphql_merged_into
47
+
48
+ def []=(key, value)
49
+ # This is a hack.
50
+ # Basically, this object is merged into the root-level result at some point.
51
+ # But the problem is, some lazies are created whose closures retain reference to _this_
52
+ # object. When those lazies are resolved, they cause an update to this object.
53
+ #
54
+ # In order to return a proper top-level result, we have to update that top-level result object.
55
+ # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
56
+ # Yowza.
57
+ if (t = @graphql_merged_into)
58
+ t[key] = value
59
+ end
60
+
61
+ if value.respond_to?(:graphql_result_data)
62
+ @graphql_result_data[key] = value.graphql_result_data
63
+ # If we encounter some part of this response that requires metadata tracking,
64
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
65
+ (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
66
+ else
67
+ @graphql_result_data[key] = value
68
+ # keep this up-to-date if it's been initialized
69
+ @graphql_metadata && @graphql_metadata[key] = value
70
+ end
71
+
72
+ value
73
+ end
74
+
75
+ def delete(key)
76
+ @graphql_metadata && @graphql_metadata.delete(key)
77
+ @graphql_result_data.delete(key)
78
+ end
79
+
80
+ def each
81
+ (@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) }
82
+ end
83
+
84
+ def values
85
+ (@graphql_metadata || @graphql_result_data).values
86
+ end
87
+
88
+ def key?(k)
89
+ @graphql_result_data.key?(k)
90
+ end
91
+
92
+ def [](k)
93
+ (@graphql_metadata || @graphql_result_data)[k]
94
+ end
95
+ end
96
+
97
+ class GraphQLResultArray
98
+ include GraphQLResult
99
+
100
+ def initialize(_result_name, _parent_result)
101
+ super
102
+ @graphql_result_data = []
103
+ end
104
+
105
+ def graphql_skip_at(index)
106
+ # Mark this index as dead. It's tricky because some indices may already be storing
107
+ # `Lazy`s. So the runtime is still holding indexes _before_ skipping,
108
+ # this object has to coordinate incoming writes to account for any already-skipped indices.
109
+ @skip_indices ||= []
110
+ @skip_indices << index
111
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index}
112
+ delete_at_index = index - offset_by
113
+ @graphql_metadata && @graphql_metadata.delete_at(delete_at_index)
114
+ @graphql_result_data.delete_at(delete_at_index)
115
+ end
116
+
117
+ def []=(idx, value)
118
+ if @skip_indices
119
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
120
+ idx -= offset_by
121
+ end
122
+ if value.respond_to?(:graphql_result_data)
123
+ @graphql_result_data[idx] = value.graphql_result_data
124
+ (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
125
+ else
126
+ @graphql_result_data[idx] = value
127
+ @graphql_metadata && @graphql_metadata[idx] = value
128
+ end
129
+
130
+ value
131
+ end
132
+
133
+ def values
134
+ (@graphql_metadata || @graphql_result_data)
135
+ end
136
+ end
137
+
138
+ class GraphQLSelectionSet < Hash
139
+ attr_accessor :graphql_directives
140
+ end
141
+
11
142
  # @return [GraphQL::Query]
12
143
  attr_reader :query
13
144
 
@@ -17,28 +148,49 @@ module GraphQL
17
148
  # @return [GraphQL::Query::Context]
18
149
  attr_reader :context
19
150
 
20
- def initialize(query:, response:)
151
+ def initialize(query:)
21
152
  @query = query
153
+ @dataloader = query.multiplex.dataloader
22
154
  @schema = query.schema
23
155
  @context = query.context
156
+ @multiplex_context = query.multiplex.context
24
157
  @interpreter_context = @context.namespace(:interpreter)
25
- @response = response
26
- @dead_paths = {}
27
- @types_at_paths = {}
158
+ @response = GraphQLResultHash.new(nil, nil)
159
+ # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
160
+ @runtime_directive_names = []
161
+ noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
162
+ @schema_directives = schema.directives
163
+ @schema_directives.each do |name, dir_defn|
164
+ if dir_defn.method(:resolve).owner != noop_resolve_owner
165
+ @runtime_directive_names << name
166
+ end
167
+ end
28
168
  # A cache of { Class => { String => Schema::Field } }
29
169
  # Which assumes that MyObject.get_field("myField") will return the same field
30
170
  # during the lifetime of a query
31
171
  @fields_cache = Hash.new { |h, k| h[k] = {} }
172
+ # { Class => Boolean }
173
+ @lazy_cache = {}
32
174
  end
33
175
 
34
- def final_value
35
- @response.final_value
176
+ def final_result
177
+ @response && @response.graphql_result_data
36
178
  end
37
179
 
38
180
  def inspect
39
181
  "#<#{self.class.name} response=#{@response.inspect}>"
40
182
  end
41
183
 
184
+ def tap_or_each(obj_or_array)
185
+ if obj_or_array.is_a?(Array)
186
+ obj_or_array.each do |item|
187
+ yield(item, true)
188
+ end
189
+ else
190
+ yield(obj_or_array, false)
191
+ end
192
+ end
193
+
42
194
  # This _begins_ the execution. Some deferred work
43
195
  # might be stored up in lazies.
44
196
  # @return [void]
@@ -47,15 +199,50 @@ module GraphQL
47
199
  root_op_type = root_operation.operation_type || "query"
48
200
  root_type = schema.root_type_for_operation(root_op_type)
49
201
  path = []
50
- set_interpreter_context(:current_object, query.root_value)
51
- set_interpreter_context(:current_path, path)
52
- object_proxy = authorized_new(root_type, query.root_value, context, path)
202
+ set_all_interpreter_context(query.root_value, nil, nil, path)
203
+ object_proxy = authorized_new(root_type, query.root_value, context)
53
204
  object_proxy = schema.sync_lazy(object_proxy)
205
+
54
206
  if object_proxy.nil?
55
207
  # Root .authorized? returned false.
56
- write_in_response(path, nil)
208
+ @response = nil
57
209
  else
58
- evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
210
+ call_method_on_directives(:resolve, object_proxy, root_operation.directives) do # execute query level directives
211
+ gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
212
+ # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
213
+ # require isolation during execution (because of runtime directives). In that case,
214
+ # make a new, isolated result hash for writing the result into. (That isolated response
215
+ # is eventually merged back into the main response)
216
+ #
217
+ # Otherwise, `gathered_selections` is a hash of selections which can be
218
+ # directly evaluated and the results can be written right into the main response hash.
219
+ tap_or_each(gathered_selections) do |selections, is_selection_array|
220
+ if is_selection_array
221
+ selection_response = GraphQLResultHash.new(nil, nil)
222
+ final_response = @response
223
+ else
224
+ selection_response = @response
225
+ final_response = nil
226
+ end
227
+
228
+ @dataloader.append_job {
229
+ set_all_interpreter_context(query.root_value, nil, nil, path)
230
+ call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
231
+ evaluate_selections(
232
+ path,
233
+ context.scoped_context,
234
+ object_proxy,
235
+ root_type,
236
+ root_op_type == "mutation",
237
+ selections,
238
+ selection_response,
239
+ final_response,
240
+ nil,
241
+ )
242
+ end
243
+ }
244
+ end
245
+ end
59
246
  end
60
247
  delete_interpreter_context(:current_path)
61
248
  delete_interpreter_context(:current_field)
@@ -64,15 +251,36 @@ module GraphQL
64
251
  nil
65
252
  end
66
253
 
67
- def gather_selections(owner_object, owner_type, selections, selections_by_name)
254
+ # @return [void]
255
+ def deep_merge_selection_result(from_result, into_result)
256
+ from_result.each do |key, value|
257
+ if !into_result.key?(key)
258
+ into_result[key] = value
259
+ else
260
+ case value
261
+ when GraphQLResultHash
262
+ deep_merge_selection_result(value, into_result[key])
263
+ else
264
+ # We have to assume that, since this passed the `fields_will_merge` selection,
265
+ # that the old and new values are the same.
266
+ # There's no special handling of arrays because currently, there's no way to split the execution
267
+ # of a list over several concurrent flows.
268
+ into_result[key] = value
269
+ end
270
+ end
271
+ end
272
+ from_result.graphql_merged_into = into_result
273
+ nil
274
+ end
275
+
276
+ def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
68
277
  selections.each do |node|
69
278
  # Skip gathering this if the directive says so
70
279
  if !directives_include?(node, owner_object, owner_type)
71
280
  next
72
281
  end
73
282
 
74
- case node
75
- when GraphQL::Language::Nodes::Field
283
+ if node.is_a?(GraphQL::Language::Nodes::Field)
76
284
  response_key = node.alias || node.name
77
285
  selections = selections_by_name[response_key]
78
286
  # if there was already a selection of this field,
@@ -88,203 +296,373 @@ module GraphQL
88
296
  # No selection was found for this field yet
89
297
  selections_by_name[response_key] = node
90
298
  end
91
- when GraphQL::Language::Nodes::InlineFragment
92
- if node.type
93
- type_defn = schema.get_type(node.type.name)
94
- # Faster than .map{}.include?()
95
- query.warden.possible_types(type_defn).each do |t|
299
+ else
300
+ # This is an InlineFragment or a FragmentSpread
301
+ if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
302
+ next_selections = GraphQLSelectionSet.new
303
+ next_selections.graphql_directives = node.directives
304
+ if selections_to_run
305
+ selections_to_run << next_selections
306
+ else
307
+ selections_to_run = []
308
+ selections_to_run << selections_by_name
309
+ selections_to_run << next_selections
310
+ end
311
+ else
312
+ next_selections = selections_by_name
313
+ end
314
+
315
+ case node
316
+ when GraphQL::Language::Nodes::InlineFragment
317
+ if node.type
318
+ type_defn = schema.get_type(node.type.name, context)
319
+
320
+ # Faster than .map{}.include?()
321
+ query.warden.possible_types(type_defn).each do |t|
322
+ if t == owner_type
323
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
324
+ break
325
+ end
326
+ end
327
+ else
328
+ # it's an untyped fragment, definitely continue
329
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
330
+ end
331
+ when GraphQL::Language::Nodes::FragmentSpread
332
+ fragment_def = query.fragments[node.name]
333
+ type_defn = query.get_type(fragment_def.type.name)
334
+ possible_types = query.warden.possible_types(type_defn)
335
+ possible_types.each do |t|
96
336
  if t == owner_type
97
- gather_selections(owner_object, owner_type, node.selections, selections_by_name)
337
+ gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
98
338
  break
99
339
  end
100
340
  end
101
341
  else
102
- # it's an untyped fragment, definitely continue
103
- gather_selections(owner_object, owner_type, node.selections, selections_by_name)
342
+ raise "Invariant: unexpected selection class: #{node.class}"
104
343
  end
105
- when GraphQL::Language::Nodes::FragmentSpread
106
- fragment_def = query.fragments[node.name]
107
- type_defn = schema.get_type(fragment_def.type.name)
108
- possible_types = query.warden.possible_types(type_defn)
109
- possible_types.each do |t|
110
- if t == owner_type
111
- gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
112
- break
113
- end
114
- end
115
- else
116
- raise "Invariant: unexpected selection class: #{node.class}"
117
344
  end
118
345
  end
346
+ selections_to_run || selections_by_name
119
347
  end
120
348
 
121
- def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
122
- set_interpreter_context(:current_object, owner_object)
123
- set_interpreter_context(:current_path, path)
124
- selections_by_name = {}
125
- gather_selections(owner_object, owner_type, selections, selections_by_name)
126
- selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
127
- # As a performance optimization, the hash key will be a `Node` if
128
- # there's only one selection of the field. But if there are multiple
129
- # selections of the field, it will be an Array of nodes
130
- if field_ast_nodes_or_ast_node.is_a?(Array)
131
- field_ast_nodes = field_ast_nodes_or_ast_node
132
- ast_node = field_ast_nodes.first
133
- else
134
- field_ast_nodes = nil
135
- ast_node = field_ast_nodes_or_ast_node
136
- end
137
- field_name = ast_node.name
138
- field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
139
- is_introspection = false
140
- if field_defn.nil?
141
- field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
142
- is_introspection = true
143
- entry_point_field
144
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
145
- is_introspection = true
146
- dynamic_field
147
- else
148
- raise "Invariant: no field for #{owner_type}.#{field_name}"
349
+ NO_ARGS = {}.freeze
350
+
351
+ # @return [void]
352
+ def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
353
+ set_all_interpreter_context(owner_object, nil, nil, path)
354
+
355
+ finished_jobs = 0
356
+ enqueued_jobs = gathered_selections.size
357
+ gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
358
+ @dataloader.append_job {
359
+ evaluate_selection(
360
+ path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result, parent_object
361
+ )
362
+ finished_jobs += 1
363
+ if target_result && finished_jobs == enqueued_jobs
364
+ deep_merge_selection_result(selections_result, target_result)
149
365
  end
366
+ }
367
+ end
368
+
369
+ selections_result
370
+ end
371
+
372
+ attr_reader :progress_path
373
+
374
+ # @return [void]
375
+ def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
376
+ return if dead_result?(selections_result)
377
+ # As a performance optimization, the hash key will be a `Node` if
378
+ # there's only one selection of the field. But if there are multiple
379
+ # selections of the field, it will be an Array of nodes
380
+ if field_ast_nodes_or_ast_node.is_a?(Array)
381
+ field_ast_nodes = field_ast_nodes_or_ast_node
382
+ ast_node = field_ast_nodes.first
383
+ else
384
+ field_ast_nodes = nil
385
+ ast_node = field_ast_nodes_or_ast_node
386
+ end
387
+ field_name = ast_node.name
388
+ # This can't use `query.get_field` because it gets confused on introspection below if `field_defn` isn't `nil`,
389
+ # because of how `is_introspection` is used to call `.authorized_new` later on.
390
+ field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name, @context)
391
+ is_introspection = false
392
+ if field_defn.nil?
393
+ field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
394
+ is_introspection = true
395
+ entry_point_field
396
+ elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
397
+ is_introspection = true
398
+ dynamic_field
399
+ else
400
+ raise "Invariant: no field for #{owner_type}.#{field_name}"
150
401
  end
151
- return_type = field_defn.type
402
+ end
152
403
 
153
- next_path = path.dup
154
- next_path << result_name
155
- next_path.freeze
404
+ return_type = field_defn.type
156
405
 
157
- # This seems janky, but we need to know
158
- # the field's return type at this path in order
159
- # to propagate `null`
160
- set_type_at_path(next_path, return_type)
161
- # Set this before calling `run_with_directives`, so that the directive can have the latest path
162
- set_interpreter_context(:current_path, next_path)
163
- set_interpreter_context(:current_field, field_defn)
406
+ next_path = path.dup
407
+ next_path << result_name
408
+ next_path.freeze
164
409
 
165
- context.scoped_context = scoped_context
166
- object = owner_object
410
+ # This seems janky, but we need to know
411
+ # the field's return type at this path in order
412
+ # to propagate `null`
413
+ if return_type.non_null?
414
+ (selections_result.graphql_non_null_field_names ||= []).push(result_name)
415
+ end
416
+ # Set this before calling `run_with_directives`, so that the directive can have the latest path
417
+ set_all_interpreter_context(nil, field_defn, nil, next_path)
418
+
419
+ context.scoped_context = scoped_context
420
+ object = owner_object
167
421
 
168
- if is_introspection
169
- object = authorized_new(field_defn.owner, object, context, next_path)
422
+ if is_introspection
423
+ object = authorized_new(field_defn.owner, object, context)
424
+ end
425
+
426
+ total_args_count = field_defn.arguments(context).size
427
+ if total_args_count == 0
428
+ resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
429
+ evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type)
430
+ else
431
+ # TODO remove all arguments(...) usages?
432
+ @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
433
+ evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type)
170
434
  end
435
+ end
436
+ end
171
437
 
172
- begin
173
- kwarg_arguments = arguments(object, field_defn, ast_node)
174
- rescue GraphQL::ExecutionError => e
175
- continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
438
+ def evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type) # rubocop:disable Metrics/ParameterLists
439
+ context.scoped_context = scoped_context
440
+ after_lazy(arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
441
+ if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
442
+ continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
176
443
  next
177
444
  end
178
445
 
179
- after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
180
- if resolved_arguments.is_a? GraphQL::ExecutionError
181
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
182
- next
183
- end
184
-
185
- kwarg_arguments = resolved_arguments.keyword_arguments
186
-
446
+ kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
447
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
448
+ NO_ARGS
449
+ else
450
+ # Bundle up the extras, then make a new arguments instance
451
+ # that includes the extras, too.
452
+ extra_args = {}
187
453
  field_defn.extras.each do |extra|
188
454
  case extra
189
455
  when :ast_node
190
- kwarg_arguments[:ast_node] = ast_node
456
+ extra_args[:ast_node] = ast_node
191
457
  when :execution_errors
192
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
458
+ extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
193
459
  when :path
194
- kwarg_arguments[:path] = next_path
460
+ extra_args[:path] = next_path
195
461
  when :lookahead
196
462
  if !field_ast_nodes
197
463
  field_ast_nodes = [ast_node]
198
464
  end
199
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
465
+
466
+ extra_args[:lookahead] = Execution::Lookahead.new(
200
467
  query: query,
201
468
  ast_nodes: field_ast_nodes,
202
469
  field: field_defn,
203
470
  )
204
471
  when :argument_details
205
- kwarg_arguments[:argument_details] = resolved_arguments
472
+ # Use this flag to tell Interpreter::Arguments to add itself
473
+ # to the keyword args hash _before_ freezing everything.
474
+ extra_args[:argument_details] = :__arguments_add_self
475
+ when :irep_node
476
+ # This is used by `__typename` in order to support the legacy runtime,
477
+ # but it has no use here (and it's always `nil`).
478
+ # Stop adding it here to avoid the overhead of `.merge_extras` below.
479
+ when :parent
480
+ extra_args[:parent] = parent_object
206
481
  else
207
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
482
+ extra_args[extra] = field_defn.fetch_extra(extra, context)
208
483
  end
209
484
  end
485
+ if extra_args.any?
486
+ resolved_arguments = resolved_arguments.merge_extras(extra_args)
487
+ end
488
+ resolved_arguments.keyword_arguments
489
+ end
210
490
 
211
- set_interpreter_context(:current_arguments, kwarg_arguments)
491
+ set_all_interpreter_context(nil, nil, resolved_arguments, nil)
212
492
 
213
- # Optimize for the case that field is selected only once
214
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
215
- next_selections = ast_node.selections
216
- else
217
- next_selections = []
218
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
219
- end
493
+ # Optimize for the case that field is selected only once
494
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
495
+ next_selections = ast_node.selections
496
+ directives = ast_node.directives
497
+ else
498
+ next_selections = []
499
+ directives = []
500
+ field_ast_nodes.each { |f|
501
+ next_selections.concat(f.selections)
502
+ directives.concat(f.directives)
503
+ }
504
+ end
220
505
 
221
- field_result = resolve_with_directives(object, ast_node) do
222
- # Actually call the field resolver and capture the result
223
- app_result = begin
224
- query.with_error_handling do
225
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
226
- field_defn.resolve(object, kwarg_arguments, context)
227
- end
506
+ field_result = call_method_on_directives(:resolve, object, directives) do
507
+ # Actually call the field resolver and capture the result
508
+ app_result = begin
509
+ query.with_error_handling do
510
+ query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments}) do
511
+ field_defn.resolve(object, kwarg_arguments, context)
228
512
  end
229
- rescue GraphQL::ExecutionError => err
230
- err
231
513
  end
232
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
233
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
234
- if RawValue === continue_value
235
- # Write raw value directly to the response without resolving nested objects
236
- write_in_response(next_path, continue_value.resolve)
237
- elsif HALT != continue_value
238
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
239
- end
514
+ rescue GraphQL::ExecutionError => err
515
+ err
516
+ end
517
+ after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
518
+ continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
519
+ if HALT != continue_value
520
+ continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
240
521
  end
241
522
  end
523
+ end
242
524
 
243
- # If this field is a root mutation field, immediately resolve
244
- # all of its child fields before moving on to the next root mutation field.
245
- # (Subselections of this mutation will still be resolved level-by-level.)
246
- if root_operation_type == "mutation"
247
- Interpreter::Resolve.resolve_all([field_result])
525
+ # If this field is a root mutation field, immediately resolve
526
+ # all of its child fields before moving on to the next root mutation field.
527
+ # (Subselections of this mutation will still be resolved level-by-level.)
528
+ if is_eager_field
529
+ Interpreter::Resolve.resolve_all([field_result], @dataloader)
530
+ else
531
+ # Return this from `after_lazy` because it might be another lazy that needs to be resolved
532
+ field_result
533
+ end
534
+ end
535
+ end
536
+
537
+ def dead_result?(selection_result)
538
+ selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
539
+ end
540
+
541
+ def set_result(selection_result, result_name, value)
542
+ if !dead_result?(selection_result)
543
+ if value.nil? &&
544
+ ( # there are two conditions under which `nil` is not allowed in the response:
545
+ (selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
546
+ ((nn = selection_result.graphql_non_null_field_names) && nn.include?(result_name)) # this value would be written into a field that doesn't allow nils
547
+ )
548
+ # This is an invalid nil that should be propagated
549
+ # One caller of this method passes a block,
550
+ # namely when application code returns a `nil` to GraphQL and it doesn't belong there.
551
+ # The other possibility for reaching here is when a field returns an ExecutionError, so we write
552
+ # `nil` to the response, not knowing whether it's an invalid `nil` or not.
553
+ # (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.)
554
+ # TODO the code is trying to tell me something.
555
+ yield if block_given?
556
+ parent = selection_result.graphql_parent
557
+ name_in_parent = selection_result.graphql_result_name
558
+ if parent.nil? # This is a top-level result hash
559
+ @response = nil
248
560
  else
249
- field_result
561
+ set_result(parent, name_in_parent, nil)
562
+ set_graphql_dead(selection_result)
250
563
  end
564
+ else
565
+ selection_result[result_name] = value
251
566
  end
252
567
  end
253
568
  end
254
569
 
570
+ # Mark this node and any already-registered children as dead,
571
+ # so that it accepts no more writes.
572
+ def set_graphql_dead(selection_result)
573
+ case selection_result
574
+ when GraphQLResultArray
575
+ selection_result.graphql_dead = true
576
+ selection_result.values.each { |v| set_graphql_dead(v) }
577
+ when GraphQLResultHash
578
+ selection_result.graphql_dead = true
579
+ selection_result.each { |k, v| set_graphql_dead(v) }
580
+ else
581
+ # It's a scalar, no way to mark it dead.
582
+ end
583
+ end
584
+
255
585
  HALT = Object.new
256
- def continue_value(path, value, parent_type, field, is_non_null, ast_node)
257
- if value.nil?
586
+ def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
587
+ case value
588
+ when nil
258
589
  if is_non_null
259
- err = parent_type::InvalidNullError.new(parent_type, field, value)
260
- write_invalid_null_in_response(path, err)
590
+ set_result(selection_result, result_name, nil) do
591
+ # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
592
+ err = parent_type::InvalidNullError.new(parent_type, field, value)
593
+ schema.type_error(err, context)
594
+ end
261
595
  else
262
- write_in_response(path, nil)
596
+ set_result(selection_result, result_name, nil)
263
597
  end
264
598
  HALT
265
- elsif value.is_a?(GraphQL::ExecutionError)
266
- value.path ||= path
267
- value.ast_node ||= ast_node
268
- write_execution_errors_in_response(path, [value])
269
- HALT
270
- elsif value.is_a?(Array) && value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
271
- value.each_with_index do |error, index|
272
- error.ast_node ||= ast_node
273
- error.path ||= path + (field.type.list? ? [index] : [])
599
+ when GraphQL::Error
600
+ # Handle these cases inside a single `when`
601
+ # to avoid the overhead of checking three different classes
602
+ # every time.
603
+ if value.is_a?(GraphQL::ExecutionError)
604
+ if selection_result.nil? || !dead_result?(selection_result)
605
+ value.path ||= path
606
+ value.ast_node ||= ast_node
607
+ context.errors << value
608
+ if selection_result
609
+ set_result(selection_result, result_name, nil)
610
+ end
611
+ end
612
+ HALT
613
+ elsif value.is_a?(GraphQL::UnauthorizedError)
614
+ # this hook might raise & crash, or it might return
615
+ # a replacement value
616
+ next_value = begin
617
+ schema.unauthorized_object(value)
618
+ rescue GraphQL::ExecutionError => err
619
+ err
620
+ end
621
+ continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
622
+ elsif GraphQL::Execution::Execute::SKIP == value
623
+ # It's possible a lazy was already written here
624
+ case selection_result
625
+ when GraphQLResultHash
626
+ selection_result.delete(result_name)
627
+ when GraphQLResultArray
628
+ selection_result.graphql_skip_at(result_name)
629
+ when nil
630
+ # this can happen with directives
631
+ else
632
+ raise "Invariant: unexpected result class #{selection_result.class} (#{selection_result.inspect})"
633
+ end
634
+ HALT
635
+ else
636
+ # What could this actually _be_? Anyhow,
637
+ # preserve the default behavior of doing nothing with it.
638
+ value
274
639
  end
275
- write_execution_errors_in_response(path, value)
276
- HALT
277
- elsif value.is_a?(GraphQL::UnauthorizedError)
278
- # this hook might raise & crash, or it might return
279
- # a replacement value
280
- next_value = begin
281
- schema.unauthorized_object(value)
282
- rescue GraphQL::ExecutionError => err
283
- err
640
+ when Array
641
+ # It's an array full of execution errors; add them all.
642
+ if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
643
+ list_type_at_all = (field && (field.type.list?))
644
+ if selection_result.nil? || !dead_result?(selection_result)
645
+ value.each_with_index do |error, index|
646
+ error.ast_node ||= ast_node
647
+ error.path ||= path + (list_type_at_all ? [index] : [])
648
+ context.errors << error
649
+ end
650
+ if selection_result
651
+ if list_type_at_all
652
+ result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
653
+ set_result(selection_result, result_name, result_without_errors)
654
+ else
655
+ set_result(selection_result, result_name, nil)
656
+ end
657
+ end
658
+ end
659
+ HALT
660
+ else
661
+ value
284
662
  end
285
-
286
- continue_value(path, next_value, parent_type, field, is_non_null, ast_node)
287
- elsif GraphQL::Execution::Execute::SKIP == value
663
+ when GraphQL::Execution::Interpreter::RawValue
664
+ # Write raw value directly to the response without resolving nested objects
665
+ set_result(selection_result, result_name, value.resolve)
288
666
  HALT
289
667
  else
290
668
  value
@@ -299,17 +677,22 @@ module GraphQL
299
677
  # Location information from `path` and `ast_node`.
300
678
  #
301
679
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
302
- def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
680
+ def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
681
+ if current_type.non_null?
682
+ current_type = current_type.of_type
683
+ is_non_null = true
684
+ end
685
+
303
686
  case current_type.kind.name
304
687
  when "SCALAR", "ENUM"
305
688
  r = current_type.coerce_result(value, context)
306
- write_in_response(path, r)
689
+ set_result(selection_result, result_name, r)
307
690
  r
308
691
  when "UNION", "INTERFACE"
309
692
  resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
310
693
  resolved_value ||= value
311
694
 
312
- after_lazy(resolved_type_or_lazy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
695
+ after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type|
313
696
  possible_types = query.possible_types(current_type)
314
697
 
315
698
  if !possible_types.include?(resolved_type)
@@ -317,46 +700,82 @@ module GraphQL
317
700
  err_class = current_type::UnresolvedTypeError
318
701
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
319
702
  schema.type_error(type_error, context)
320
- write_in_response(path, nil)
703
+ set_result(selection_result, result_name, nil)
321
704
  nil
322
705
  else
323
- continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
706
+ continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
324
707
  end
325
708
  end
326
709
  when "OBJECT"
327
710
  object_proxy = begin
328
- authorized_new(current_type, value, context, path)
711
+ authorized_new(current_type, value, context)
329
712
  rescue GraphQL::ExecutionError => err
330
713
  err
331
714
  end
332
- after_lazy(object_proxy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
333
- continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
715
+ after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
716
+ continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
334
717
  if HALT != continue_value
335
- response_hash = {}
336
- write_in_response(path, response_hash)
337
- evaluate_selections(path, context.scoped_context, continue_value, current_type, next_selections)
338
- response_hash
718
+ response_hash = GraphQLResultHash.new(result_name, selection_result)
719
+ set_result(selection_result, result_name, response_hash)
720
+ gathered_selections = gather_selections(continue_value, current_type, next_selections)
721
+ # There are two possibilities for `gathered_selections`:
722
+ # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
723
+ # This case is handled below, and the result can be written right into the main `response_hash` above.
724
+ # In this case, `gathered_selections` is a hash of selections.
725
+ # 2. Some selections of this object have runtime directives that may or may not modify execution.
726
+ # That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
727
+ # eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
728
+ # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
729
+ tap_or_each(gathered_selections) do |selections, is_selection_array|
730
+ if is_selection_array
731
+ this_result = GraphQLResultHash.new(result_name, selection_result)
732
+ final_result = response_hash
733
+ else
734
+ this_result = response_hash
735
+ final_result = nil
736
+ end
737
+ set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
738
+ call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
739
+ evaluate_selections(
740
+ path,
741
+ context.scoped_context,
742
+ continue_value,
743
+ current_type,
744
+ false,
745
+ selections,
746
+ this_result,
747
+ final_result,
748
+ owner_object.object,
749
+ )
750
+ this_result
751
+ end
752
+ end
339
753
  end
340
754
  end
341
755
  when "LIST"
342
- response_list = []
343
- write_in_response(path, response_list)
344
756
  inner_type = current_type.of_type
757
+ # This is true for objects, unions, and interfaces
758
+ use_dataloader_job = !inner_type.unwrap.kind.input?
759
+ response_list = GraphQLResultArray.new(result_name, selection_result)
760
+ response_list.graphql_non_null_list_items = inner_type.non_null?
761
+ set_result(selection_result, result_name, response_list)
762
+
345
763
  idx = 0
346
764
  scoped_context = context.scoped_context
347
765
  begin
348
766
  value.each do |inner_value|
767
+ break if dead_result?(response_list)
349
768
  next_path = path.dup
350
769
  next_path << idx
770
+ this_idx = idx
351
771
  next_path.freeze
352
772
  idx += 1
353
- set_type_at_path(next_path, inner_type)
354
- # This will update `response_list` with the lazy
355
- after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
356
- continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
357
- if HALT != continue_value
358
- continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
773
+ if use_dataloader_job
774
+ @dataloader.append_job do
775
+ resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
359
776
  end
777
+ else
778
+ resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
360
779
  end
361
780
  end
362
781
  rescue NoMethodError => err
@@ -371,33 +790,56 @@ module GraphQL
371
790
  end
372
791
 
373
792
  response_list
374
- when "NON_NULL"
375
- inner_type = current_type.of_type
376
- # Don't `set_type_at_path` because we want the static type,
377
- # we're going to use that to determine whether a `nil` should be propagated or not.
378
- continue_field(path, value, owner_type, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
379
793
  else
380
794
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
381
795
  end
382
796
  end
383
797
 
384
- def resolve_with_directives(object, ast_node, &block)
385
- return yield if ast_node.directives.empty?
386
- run_directive(object, ast_node, 0, &block)
798
+ def resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
799
+ set_all_interpreter_context(nil, nil, nil, next_path)
800
+ call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
801
+ # This will update `response_list` with the lazy
802
+ after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
803
+ continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
804
+ if HALT != continue_value
805
+ continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
806
+ end
807
+ end
808
+ end
809
+ end
810
+
811
+ def call_method_on_directives(method_name, object, directives, &block)
812
+ return yield if directives.nil? || directives.empty?
813
+ run_directive(method_name, object, directives, 0, &block)
387
814
  end
388
815
 
389
- def run_directive(object, ast_node, idx, &block)
390
- dir_node = ast_node.directives[idx]
816
+ def run_directive(method_name, object, directives, idx, &block)
817
+ dir_node = directives[idx]
391
818
  if !dir_node
392
819
  yield
393
820
  else
394
- dir_defn = schema.directives.fetch(dir_node.name)
821
+ dir_defn = @schema_directives.fetch(dir_node.name)
395
822
  if !dir_defn.is_a?(Class)
396
823
  dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
397
824
  end
398
- dir_args = arguments(nil, dir_defn, dir_node).keyword_arguments
399
- dir_defn.resolve(object, dir_args, context) do
400
- run_directive(object, ast_node, idx + 1, &block)
825
+ raw_dir_args = arguments(nil, dir_defn, dir_node)
826
+ dir_args = continue_value(
827
+ @context[:current_path], # path
828
+ raw_dir_args, # value
829
+ dir_defn, # parent_type
830
+ nil, # field
831
+ false, # is_non_null
832
+ dir_node, # ast_node
833
+ nil, # result_name
834
+ nil, # selection_result
835
+ )
836
+
837
+ if dir_args == HALT
838
+ nil
839
+ else
840
+ dir_defn.public_send(method_name, object, dir_args, context) do
841
+ run_directive(method_name, object, directives, idx + 1, &block)
842
+ end
401
843
  end
402
844
  end
403
845
  end
@@ -405,8 +847,8 @@ module GraphQL
405
847
  # Check {Schema::Directive.include?} for each directive that's present
406
848
  def directives_include?(node, graphql_object, parent_type)
407
849
  node.directives.each do |dir_node|
408
- dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
409
- args = arguments(graphql_object, dir_defn, dir_node).keyword_arguments
850
+ dir_defn = @schema_directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
851
+ args = arguments(graphql_object, dir_defn, dir_node)
410
852
  if !dir_defn.include?(graphql_object, args, context)
411
853
  return false
412
854
  end
@@ -414,57 +856,68 @@ module GraphQL
414
856
  true
415
857
  end
416
858
 
859
+ def set_all_interpreter_context(object, field, arguments, path)
860
+ if object
861
+ @context[:current_object] = @interpreter_context[:current_object] = object
862
+ end
863
+ if field
864
+ @context[:current_field] = @interpreter_context[:current_field] = field
865
+ end
866
+ if arguments
867
+ @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
868
+ end
869
+ if path
870
+ @context[:current_path] = @interpreter_context[:current_path] = path
871
+ end
872
+ end
873
+
417
874
  # @param obj [Object] Some user-returned value that may want to be batched
418
875
  # @param path [Array<String>]
419
876
  # @param field [GraphQL::Schema::Field]
420
877
  # @param eager [Boolean] Set to `true` for mutation root fields only
421
878
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
422
879
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
423
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
424
- set_interpreter_context(:current_object, owner_object)
425
- set_interpreter_context(:current_arguments, arguments)
426
- set_interpreter_context(:current_path, path)
427
- set_interpreter_context(:current_field, field)
428
- if schema.lazy?(lazy_obj)
880
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
881
+ if lazy?(lazy_obj)
429
882
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
430
- set_interpreter_context(:current_path, path)
431
- set_interpreter_context(:current_field, field)
432
- set_interpreter_context(:current_object, owner_object)
433
- set_interpreter_context(:current_arguments, arguments)
883
+ set_all_interpreter_context(owner_object, field, arguments, path)
434
884
  context.scoped_context = scoped_context
435
885
  # Wrap the execution of _this_ method with tracing,
436
886
  # but don't wrap the continuation below
437
887
  inner_obj = begin
438
888
  query.with_error_handling do
439
- if trace
440
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
889
+ begin
890
+ if trace
891
+ query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
892
+ schema.sync_lazy(lazy_obj)
893
+ end
894
+ else
441
895
  schema.sync_lazy(lazy_obj)
442
896
  end
443
- else
444
- schema.sync_lazy(lazy_obj)
897
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
898
+ err
445
899
  end
446
900
  end
447
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
448
- err
901
+ rescue GraphQL::ExecutionError => ex_err
902
+ ex_err
449
903
  end
450
- after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
904
+ yield(inner_obj)
451
905
  end
452
906
 
453
907
  if eager
454
908
  lazy.value
455
909
  else
456
- write_in_response(path, lazy)
910
+ set_result(result, result_name, lazy)
457
911
  lazy
458
912
  end
459
913
  else
914
+ set_all_interpreter_context(owner_object, field, arguments, path)
460
915
  yield(lazy_obj)
461
916
  end
462
917
  end
463
918
 
464
919
  def arguments(graphql_object, arg_owner, ast_node)
465
- # Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure
466
- if arg_owner.arguments_statically_coercible? &&
467
- (!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
920
+ if arg_owner.arguments_statically_coercible?
468
921
  query.arguments_for(ast_node, arg_owner)
469
922
  else
470
923
  # The arguments must be prepared in the context of the given object
@@ -472,75 +925,6 @@ module GraphQL
472
925
  end
473
926
  end
474
927
 
475
- def write_invalid_null_in_response(path, invalid_null_error)
476
- if !dead_path?(path)
477
- schema.type_error(invalid_null_error, context)
478
- write_in_response(path, nil)
479
- add_dead_path(path)
480
- end
481
- end
482
-
483
- def write_execution_errors_in_response(path, errors)
484
- if !dead_path?(path)
485
- errors.each do |v|
486
- context.errors << v
487
- end
488
- write_in_response(path, nil)
489
- add_dead_path(path)
490
- end
491
- end
492
-
493
- def write_in_response(path, value)
494
- if dead_path?(path)
495
- return
496
- else
497
- if value.nil? && path.any? && type_at(path).non_null?
498
- # This nil is invalid, try writing it at the previous spot
499
- propagate_path = path[0..-2]
500
- write_in_response(propagate_path, value)
501
- add_dead_path(propagate_path)
502
- else
503
- @response.write(path, value)
504
- end
505
- end
506
- end
507
-
508
- # To propagate nulls, we have to know what the field type was
509
- # at previous parts of the response.
510
- # This hash matches the response
511
- def type_at(path)
512
- @types_at_paths.fetch(path)
513
- end
514
-
515
- def set_type_at_path(path, type)
516
- @types_at_paths[path] = type
517
- nil
518
- end
519
-
520
- # Mark `path` as having been permanently nulled out.
521
- # No values will be added beyond that path.
522
- def add_dead_path(path)
523
- dead = @dead_paths
524
- path.each do |part|
525
- dead = dead[part] ||= {}
526
- end
527
- dead[:__dead] = true
528
- end
529
-
530
- def dead_path?(path)
531
- res = @dead_paths
532
- path.each do |part|
533
- if res
534
- if res[:__dead]
535
- break
536
- else
537
- res = res[part]
538
- end
539
- end
540
- end
541
- res && res[:__dead]
542
- end
543
-
544
928
  # Set this pair in the Query context, but also in the interpeter namespace,
545
929
  # for compatibility.
546
930
  def set_interpreter_context(key, value)
@@ -559,7 +943,7 @@ module GraphQL
559
943
  query.resolve_type(type, value)
560
944
  end
561
945
 
562
- if schema.lazy?(resolved_type)
946
+ if lazy?(resolved_type)
563
947
  GraphQL::Execution::Lazy.new do
564
948
  query.trace("resolve_type_lazy", trace_payload) do
565
949
  schema.sync_lazy(resolved_type)
@@ -570,22 +954,14 @@ module GraphQL
570
954
  end
571
955
  end
572
956
 
573
- def authorized_new(type, value, context, path)
574
- trace_payload = { context: context, type: type, object: value, path: path }
575
-
576
- auth_val = context.query.trace("authorized", trace_payload) do
577
- type.authorized_new(value, context)
578
- end
957
+ def authorized_new(type, value, context)
958
+ type.authorized_new(value, context)
959
+ end
579
960
 
580
- if context.schema.lazy?(auth_val)
581
- GraphQL::Execution::Lazy.new do
582
- context.query.trace("authorized_lazy", trace_payload) do
583
- context.schema.sync_lazy(auth_val)
584
- end
585
- end
586
- else
587
- auth_val
588
- end
961
+ def lazy?(object)
962
+ @lazy_cache.fetch(object.class) {
963
+ @lazy_cache[object.class] = @schema.lazy?(object)
964
+ }
589
965
  end
590
966
  end
591
967
  end