graphql 2.6.11 → 2.6.13

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.
Files changed (382) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +4 -0
  3. data/lib/generators/graphql/core.rb +1 -0
  4. data/lib/generators/graphql/detailed_trace_generator.rb +1 -0
  5. data/lib/generators/graphql/enum_generator.rb +1 -0
  6. data/lib/generators/graphql/field_extractor.rb +1 -0
  7. data/lib/generators/graphql/input_generator.rb +1 -0
  8. data/lib/generators/graphql/install/mutation_root_generator.rb +1 -0
  9. data/lib/generators/graphql/install_generator.rb +1 -0
  10. data/lib/generators/graphql/interface_generator.rb +1 -0
  11. data/lib/generators/graphql/loader_generator.rb +8 -2
  12. data/lib/generators/graphql/mutation_create_generator.rb +8 -2
  13. data/lib/generators/graphql/mutation_delete_generator.rb +8 -2
  14. data/lib/generators/graphql/mutation_generator.rb +8 -2
  15. data/lib/generators/graphql/mutation_update_generator.rb +8 -2
  16. data/lib/generators/graphql/object_generator.rb +1 -0
  17. data/lib/generators/graphql/orm_mutations_base.rb +8 -2
  18. data/lib/generators/graphql/relay.rb +1 -0
  19. data/lib/generators/graphql/relay_generator.rb +1 -0
  20. data/lib/generators/graphql/scalar_generator.rb +1 -0
  21. data/lib/generators/graphql/type_generator.rb +38 -8
  22. data/lib/generators/graphql/union_generator.rb +1 -0
  23. data/lib/graphql/analysis/analyzer.rb +35 -9
  24. data/lib/graphql/analysis/field_usage.rb +1 -0
  25. data/lib/graphql/analysis/max_query_complexity.rb +2 -1
  26. data/lib/graphql/analysis/max_query_depth.rb +1 -0
  27. data/lib/graphql/analysis/query_complexity.rb +57 -20
  28. data/lib/graphql/analysis/query_depth.rb +17 -12
  29. data/lib/graphql/analysis/visitor.rb +79 -16
  30. data/lib/graphql/analysis.rb +23 -6
  31. data/lib/graphql/analysis_error.rb +1 -0
  32. data/lib/graphql/autoload.rb +30 -7
  33. data/lib/graphql/backtrace/table.rb +21 -5
  34. data/lib/graphql/backtrace/traced_error.rb +8 -3
  35. data/lib/graphql/backtrace.rb +14 -9
  36. data/lib/graphql/coercion_error.rb +1 -0
  37. data/lib/graphql/current.rb +44 -19
  38. data/lib/graphql/dashboard/application_controller.rb +1 -0
  39. data/lib/graphql/dashboard/detailed_traces.rb +1 -0
  40. data/lib/graphql/dashboard/installable.rb +1 -0
  41. data/lib/graphql/dashboard/landings_controller.rb +1 -0
  42. data/lib/graphql/dashboard/limiters.rb +1 -0
  43. data/lib/graphql/dashboard/operation_store.rb +1 -0
  44. data/lib/graphql/dashboard/statics_controller.rb +1 -0
  45. data/lib/graphql/dashboard/subscriptions.rb +1 -0
  46. data/lib/graphql/dashboard.rb +36 -22
  47. data/lib/graphql/dataloader/active_record_association_source.rb +1 -0
  48. data/lib/graphql/dataloader/active_record_source.rb +1 -0
  49. data/lib/graphql/dataloader/async_dataloader.rb +10 -7
  50. data/lib/graphql/dataloader/null_dataloader.rb +4 -3
  51. data/lib/graphql/dataloader/request.rb +8 -2
  52. data/lib/graphql/dataloader/request_all.rb +8 -2
  53. data/lib/graphql/dataloader/source.rb +127 -35
  54. data/lib/graphql/dataloader.rb +93 -42
  55. data/lib/graphql/date_encoding_error.rb +2 -1
  56. data/lib/graphql/dig.rb +12 -3
  57. data/lib/graphql/duration_encoding_error.rb +2 -1
  58. data/lib/graphql/execution/directive_checks.rb +8 -3
  59. data/lib/graphql/execution/errors.rb +20 -6
  60. data/lib/graphql/execution/field_resolve_step.rb +40 -13
  61. data/lib/graphql/execution/finalize.rb +12 -6
  62. data/lib/graphql/execution/input_values.rb +2 -1
  63. data/lib/graphql/execution/interpreter/argument_value.rb +17 -5
  64. data/lib/graphql/execution/interpreter/arguments.rb +24 -9
  65. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -1
  66. data/lib/graphql/execution/interpreter/execution_errors.rb +1 -0
  67. data/lib/graphql/execution/interpreter/handles_raw_value.rb +1 -0
  68. data/lib/graphql/execution/interpreter/resolve.rb +18 -8
  69. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +4 -1
  70. data/lib/graphql/execution/interpreter/runtime.rb +72 -25
  71. data/lib/graphql/execution/interpreter.rb +15 -6
  72. data/lib/graphql/execution/lazy/lazy_method_map.rb +21 -8
  73. data/lib/graphql/execution/lazy.rb +34 -10
  74. data/lib/graphql/execution/load_argument_step.rb +1 -0
  75. data/lib/graphql/execution/lookahead.rb +137 -54
  76. data/lib/graphql/execution/multiplex.rb +4 -4
  77. data/lib/graphql/execution/next.rb +7 -0
  78. data/lib/graphql/execution/prepare_object_step.rb +1 -0
  79. data/lib/graphql/execution/resolve_type_step.rb +2 -1
  80. data/lib/graphql/execution/runner.rb +9 -3
  81. data/lib/graphql/execution/selections_step.rb +3 -2
  82. data/lib/graphql/execution.rb +2 -2
  83. data/lib/graphql/execution_error.rb +19 -10
  84. data/lib/graphql/float_decoding_error.rb +1 -0
  85. data/lib/graphql/float_encoding_error.rb +7 -2
  86. data/lib/graphql/integer_decoding_error.rb +2 -1
  87. data/lib/graphql/integer_encoding_error.rb +8 -3
  88. data/lib/graphql/introspection/base_object.rb +3 -1
  89. data/lib/graphql/introspection/directive_location_enum.rb +1 -0
  90. data/lib/graphql/introspection/directive_type.rb +1 -0
  91. data/lib/graphql/introspection/dynamic_fields.rb +1 -0
  92. data/lib/graphql/introspection/entry_points.rb +1 -0
  93. data/lib/graphql/introspection/enum_value_type.rb +1 -0
  94. data/lib/graphql/introspection/field_type.rb +1 -0
  95. data/lib/graphql/introspection/input_value_type.rb +1 -0
  96. data/lib/graphql/introspection/introspection_query.rb +1 -0
  97. data/lib/graphql/introspection/schema_type.rb +1 -0
  98. data/lib/graphql/introspection/type_kind_enum.rb +1 -0
  99. data/lib/graphql/introspection/type_type.rb +1 -0
  100. data/lib/graphql/introspection.rb +1 -0
  101. data/lib/graphql/invalid_name_error.rb +1 -0
  102. data/lib/graphql/invalid_null_error.rb +19 -6
  103. data/lib/graphql/language/block_string.rb +2 -1
  104. data/lib/graphql/language/cache.rb +10 -4
  105. data/lib/graphql/language/comment.rb +1 -0
  106. data/lib/graphql/language/definition_slice.rb +1 -0
  107. data/lib/graphql/language/document_from_schema_definition.rb +9 -10
  108. data/lib/graphql/language/generation.rb +23 -9
  109. data/lib/graphql/language/lexer.rb +5 -0
  110. data/lib/graphql/language/nodes.rb +2094 -419
  111. data/lib/graphql/language/parser.rb +24 -17
  112. data/lib/graphql/language/printer.rb +31 -16
  113. data/lib/graphql/language/sanitized_printer.rb +16 -5
  114. data/lib/graphql/language/static_visitor.rb +19 -4
  115. data/lib/graphql/language/visitor.rb +51 -29
  116. data/lib/graphql/language.rb +13 -7
  117. data/lib/graphql/load_application_object_failed_error.rb +14 -5
  118. data/lib/graphql/name_validator.rb +1 -0
  119. data/lib/graphql/pagination/active_record_relation_connection.rb +1 -0
  120. data/lib/graphql/pagination/array_connection.rb +20 -6
  121. data/lib/graphql/pagination/connection.rb +178 -48
  122. data/lib/graphql/pagination/connections.rb +21 -14
  123. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -0
  124. data/lib/graphql/pagination/relation_connection.rb +123 -22
  125. data/lib/graphql/pagination/sequel_dataset_connection.rb +1 -0
  126. data/lib/graphql/pagination.rb +1 -0
  127. data/lib/graphql/parse_error.rb +1 -0
  128. data/lib/graphql/query/context/scoped_context.rb +2 -1
  129. data/lib/graphql/query/context.rb +106 -35
  130. data/lib/graphql/query/fingerprint.rb +15 -7
  131. data/lib/graphql/query/input_validation_result.rb +1 -0
  132. data/lib/graphql/query/null_context.rb +1 -0
  133. data/lib/graphql/query/partial.rb +28 -15
  134. data/lib/graphql/query/result.rb +15 -5
  135. data/lib/graphql/query/validation_pipeline.rb +16 -6
  136. data/lib/graphql/query/variable_validation_error.rb +1 -0
  137. data/lib/graphql/query/variables.rb +9 -2
  138. data/lib/graphql/query.rb +193 -49
  139. data/lib/graphql/railtie.rb +7 -3
  140. data/lib/graphql/rake_task/validate.rb +72 -17
  141. data/lib/graphql/rake_task.rb +55 -23
  142. data/lib/graphql/relay/range_add.rb +32 -21
  143. data/lib/graphql/relay.rb +1 -0
  144. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -0
  145. data/lib/graphql/rubocop/graphql/default_null_true.rb +9 -6
  146. data/lib/graphql/rubocop/graphql/default_required_true.rb +9 -6
  147. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +12 -8
  148. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +9 -5
  149. data/lib/graphql/rubocop.rb +1 -0
  150. data/lib/graphql/runtime_error.rb +1 -0
  151. data/lib/graphql/runtime_type_error.rb +1 -0
  152. data/lib/graphql/schema/addition.rb +18 -6
  153. data/lib/graphql/schema/always_visible.rb +1 -0
  154. data/lib/graphql/schema/argument.rb +265 -42
  155. data/lib/graphql/schema/base_64_encoder.rb +2 -2
  156. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -0
  157. data/lib/graphql/schema/build_from_definition/resolve_map.rb +2 -2
  158. data/lib/graphql/schema/build_from_definition.rb +94 -27
  159. data/lib/graphql/schema/built_in_types.rb +1 -0
  160. data/lib/graphql/schema/directive/deprecated.rb +1 -0
  161. data/lib/graphql/schema/directive/feature.rb +39 -22
  162. data/lib/graphql/schema/directive/flagged.rb +6 -1
  163. data/lib/graphql/schema/directive/include.rb +1 -0
  164. data/lib/graphql/schema/directive/one_of.rb +1 -0
  165. data/lib/graphql/schema/directive/skip.rb +1 -0
  166. data/lib/graphql/schema/directive/specified_by.rb +1 -0
  167. data/lib/graphql/schema/directive/transform.rb +17 -8
  168. data/lib/graphql/schema/directive.rb +12 -7
  169. data/lib/graphql/schema/enum.rb +99 -38
  170. data/lib/graphql/schema/enum_value.rb +21 -13
  171. data/lib/graphql/schema/field/connection_extension.rb +10 -2
  172. data/lib/graphql/schema/field/scope_extension.rb +1 -0
  173. data/lib/graphql/schema/field.rb +621 -109
  174. data/lib/graphql/schema/field_extension.rb +89 -34
  175. data/lib/graphql/schema/find_inherited_value.rb +5 -3
  176. data/lib/graphql/schema/finder.rb +22 -8
  177. data/lib/graphql/schema/has_single_input_argument.rb +39 -6
  178. data/lib/graphql/schema/input_object.rb +40 -13
  179. data/lib/graphql/schema/interface.rb +33 -17
  180. data/lib/graphql/schema/introspection_system.rb +7 -3
  181. data/lib/graphql/schema/late_bound_type.rb +2 -2
  182. data/lib/graphql/schema/list.rb +15 -4
  183. data/lib/graphql/schema/loader.rb +23 -12
  184. data/lib/graphql/schema/member/base_dsl_methods.rb +58 -12
  185. data/lib/graphql/schema/member/build_type.rb +16 -5
  186. data/lib/graphql/schema/member/graphql_type_names.rb +10 -6
  187. data/lib/graphql/schema/member/has_arguments.rb +115 -48
  188. data/lib/graphql/schema/member/has_ast_node.rb +1 -0
  189. data/lib/graphql/schema/member/has_authorization.rb +5 -0
  190. data/lib/graphql/schema/member/has_dataloader.rb +91 -34
  191. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -2
  192. data/lib/graphql/schema/member/has_directives.rb +38 -10
  193. data/lib/graphql/schema/member/has_fields.rb +151 -70
  194. data/lib/graphql/schema/member/has_interfaces.rb +10 -2
  195. data/lib/graphql/schema/member/has_path.rb +14 -5
  196. data/lib/graphql/schema/member/has_unresolved_type_error.rb +1 -0
  197. data/lib/graphql/schema/member/has_validators.rb +19 -4
  198. data/lib/graphql/schema/member/relay_shortcuts.rb +10 -2
  199. data/lib/graphql/schema/member/scoped.rb +14 -3
  200. data/lib/graphql/schema/member/type_system_helpers.rb +36 -6
  201. data/lib/graphql/schema/member/validates_input.rb +1 -0
  202. data/lib/graphql/schema/member.rb +9 -2
  203. data/lib/graphql/schema/mutation.rb +50 -46
  204. data/lib/graphql/schema/non_null.rb +21 -5
  205. data/lib/graphql/schema/object.rb +33 -12
  206. data/lib/graphql/schema/printer.rb +52 -31
  207. data/lib/graphql/schema/ractor_shareable.rb +6 -2
  208. data/lib/graphql/schema/relay_classic_mutation.rb +3 -3
  209. data/lib/graphql/schema/resolver/has_payload_type.rb +29 -7
  210. data/lib/graphql/schema/resolver.rb +156 -45
  211. data/lib/graphql/schema/scalar.rb +1 -0
  212. data/lib/graphql/schema/subscription.rb +67 -28
  213. data/lib/graphql/schema/timeout.rb +65 -25
  214. data/lib/graphql/schema/type_expression.rb +14 -5
  215. data/lib/graphql/schema/type_membership.rb +29 -10
  216. data/lib/graphql/schema/union.rb +4 -2
  217. data/lib/graphql/schema/unique_within_type.rb +24 -5
  218. data/lib/graphql/schema/validator/all_validator.rb +17 -9
  219. data/lib/graphql/schema/validator/allow_blank_validator.rb +8 -2
  220. data/lib/graphql/schema/validator/allow_null_validator.rb +8 -2
  221. data/lib/graphql/schema/validator/exclusion_validator.rb +14 -5
  222. data/lib/graphql/schema/validator/format_validator.rb +21 -10
  223. data/lib/graphql/schema/validator/inclusion_validator.rb +15 -6
  224. data/lib/graphql/schema/validator/length_validator.rb +23 -12
  225. data/lib/graphql/schema/validator/numericality_validator.rb +29 -16
  226. data/lib/graphql/schema/validator/required_validator.rb +45 -31
  227. data/lib/graphql/schema/validator.rb +89 -25
  228. data/lib/graphql/schema/visibility/migration.rb +14 -7
  229. data/lib/graphql/schema/visibility/profile.rb +65 -20
  230. data/lib/graphql/schema/visibility/visit.rb +12 -10
  231. data/lib/graphql/schema/visibility.rb +24 -20
  232. data/lib/graphql/schema/warden.rb +117 -31
  233. data/lib/graphql/schema/wrapper.rb +4 -1
  234. data/lib/graphql/schema.rb +1046 -300
  235. data/lib/graphql/static_validation/all_rules.rb +2 -1
  236. data/lib/graphql/static_validation/base_visitor.rb +59 -9
  237. data/lib/graphql/static_validation/definition_dependencies.rb +27 -7
  238. data/lib/graphql/static_validation/error.rb +3 -0
  239. data/lib/graphql/static_validation/interpreter_visitor.rb +1 -0
  240. data/lib/graphql/static_validation/literal_validator.rb +1 -0
  241. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +4 -0
  242. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +1 -0
  243. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +3 -0
  244. data/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb +1 -0
  245. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -0
  246. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +1 -0
  247. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -0
  248. data/lib/graphql/static_validation/rules/directives_are_defined_error.rb +1 -0
  249. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +3 -0
  250. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb +1 -0
  251. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +3 -0
  252. data/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb +1 -0
  253. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +9 -1
  254. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb +1 -0
  255. data/lib/graphql/static_validation/rules/fields_will_merge.rb +295 -134
  256. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +14 -2
  257. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +3 -0
  258. data/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb +1 -0
  259. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -0
  260. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb +1 -0
  261. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +3 -0
  262. data/lib/graphql/static_validation/rules/fragment_types_exist_error.rb +1 -0
  263. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +3 -0
  264. data/lib/graphql/static_validation/rules/fragments_are_finite_error.rb +1 -0
  265. data/lib/graphql/static_validation/rules/fragments_are_named.rb +3 -0
  266. data/lib/graphql/static_validation/rules/fragments_are_named_error.rb +1 -0
  267. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +3 -0
  268. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb +1 -0
  269. data/lib/graphql/static_validation/rules/fragments_are_used.rb +3 -0
  270. data/lib/graphql/static_validation/rules/fragments_are_used_error.rb +1 -0
  271. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +3 -0
  272. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +1 -0
  273. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +3 -0
  274. data/lib/graphql/static_validation/rules/mutation_root_exists_error.rb +1 -0
  275. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +3 -0
  276. data/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb +1 -0
  277. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +1 -0
  278. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +4 -0
  279. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +1 -0
  280. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +3 -0
  281. data/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb +1 -0
  282. data/lib/graphql/static_validation/rules/query_root_exists.rb +3 -0
  283. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +1 -0
  284. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -1
  285. data/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb +1 -0
  286. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -0
  287. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb +1 -0
  288. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +43 -5
  289. data/lib/graphql/static_validation/rules/subscription_root_exists_error.rb +1 -0
  290. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +3 -0
  291. data/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb +1 -0
  292. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +3 -0
  293. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.rb +1 -0
  294. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +3 -0
  295. data/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb +1 -0
  296. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +207 -51
  297. data/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb +1 -0
  298. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +3 -0
  299. data/lib/graphql/static_validation/rules/variables_are_input_types_error.rb +1 -0
  300. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +6 -3
  301. data/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb +1 -0
  302. data/lib/graphql/static_validation/validation_context.rb +3 -2
  303. data/lib/graphql/static_validation/validation_timeout_error.rb +1 -0
  304. data/lib/graphql/static_validation/validator.rb +41 -16
  305. data/lib/graphql/static_validation.rb +1 -0
  306. data/lib/graphql/string_encoding_error.rb +1 -0
  307. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +85 -65
  308. data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
  309. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +1 -0
  310. data/lib/graphql/subscriptions/event.rb +25 -6
  311. data/lib/graphql/subscriptions/serialize.rb +58 -19
  312. data/lib/graphql/subscriptions.rb +172 -46
  313. data/lib/graphql/testing/helpers.rb +17 -4
  314. data/lib/graphql/testing/mock_action_cable.rb +55 -33
  315. data/lib/graphql/testing.rb +1 -0
  316. data/lib/graphql/tracing/active_support_notifications_trace.rb +17 -9
  317. data/lib/graphql/tracing/active_support_notifications_tracing.rb +2 -1
  318. data/lib/graphql/tracing/appoptics_trace.rb +2 -1
  319. data/lib/graphql/tracing/appoptics_tracing.rb +2 -1
  320. data/lib/graphql/tracing/appsignal_trace.rb +16 -7
  321. data/lib/graphql/tracing/appsignal_tracing.rb +7 -3
  322. data/lib/graphql/tracing/call_legacy_tracers.rb +1 -0
  323. data/lib/graphql/tracing/data_dog_trace.rb +17 -6
  324. data/lib/graphql/tracing/data_dog_tracing.rb +10 -3
  325. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +1 -0
  326. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +1 -0
  327. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +1 -0
  328. data/lib/graphql/tracing/detailed_trace.rb +105 -42
  329. data/lib/graphql/tracing/legacy_hooks_trace.rb +5 -0
  330. data/lib/graphql/tracing/legacy_trace.rb +1 -0
  331. data/lib/graphql/tracing/monitor_trace.rb +17 -11
  332. data/lib/graphql/tracing/new_relic_trace.rb +17 -8
  333. data/lib/graphql/tracing/new_relic_tracing.rb +7 -3
  334. data/lib/graphql/tracing/notifications_trace.rb +11 -8
  335. data/lib/graphql/tracing/notifications_tracing.rb +23 -7
  336. data/lib/graphql/tracing/null_trace.rb +1 -0
  337. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +1 -0
  338. data/lib/graphql/tracing/perfetto_trace.rb +41 -19
  339. data/lib/graphql/tracing/platform_trace.rb +3 -1
  340. data/lib/graphql/tracing/platform_tracing.rb +16 -7
  341. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +1 -0
  342. data/lib/graphql/tracing/prometheus_trace.rb +22 -13
  343. data/lib/graphql/tracing/prometheus_tracing.rb +1 -0
  344. data/lib/graphql/tracing/scout_trace.rb +10 -4
  345. data/lib/graphql/tracing/scout_tracing.rb +7 -3
  346. data/lib/graphql/tracing/sentry_trace.rb +12 -5
  347. data/lib/graphql/tracing/statsd_trace.rb +12 -6
  348. data/lib/graphql/tracing/statsd_tracing.rb +7 -1
  349. data/lib/graphql/tracing/trace.rb +231 -58
  350. data/lib/graphql/tracing.rb +25 -9
  351. data/lib/graphql/type_kinds.rb +2 -1
  352. data/lib/graphql/types/big_int.rb +1 -0
  353. data/lib/graphql/types/boolean.rb +1 -0
  354. data/lib/graphql/types/float.rb +1 -0
  355. data/lib/graphql/types/id.rb +1 -0
  356. data/lib/graphql/types/int.rb +2 -1
  357. data/lib/graphql/types/iso_8601_date.rb +21 -4
  358. data/lib/graphql/types/iso_8601_date_time.rb +33 -6
  359. data/lib/graphql/types/iso_8601_duration.rb +42 -9
  360. data/lib/graphql/types/json.rb +1 -0
  361. data/lib/graphql/types/relay/base_connection.rb +38 -32
  362. data/lib/graphql/types/relay/base_edge.rb +15 -9
  363. data/lib/graphql/types/relay/connection_behaviors.rb +19 -3
  364. data/lib/graphql/types/relay/edge_behaviors.rb +17 -4
  365. data/lib/graphql/types/relay/has_node_field.rb +6 -1
  366. data/lib/graphql/types/relay/has_nodes_field.rb +6 -1
  367. data/lib/graphql/types/relay/node.rb +1 -0
  368. data/lib/graphql/types/relay/node_behaviors.rb +5 -1
  369. data/lib/graphql/types/relay/page_info.rb +1 -0
  370. data/lib/graphql/types/relay/page_info_behaviors.rb +1 -0
  371. data/lib/graphql/types/relay.rb +4 -3
  372. data/lib/graphql/types/string.rb +1 -0
  373. data/lib/graphql/types.rb +1 -0
  374. data/lib/graphql/unauthorized_enum_value_error.rb +4 -1
  375. data/lib/graphql/unauthorized_error.rb +11 -4
  376. data/lib/graphql/unauthorized_field_error.rb +4 -1
  377. data/lib/graphql/unresolved_type_error.rb +16 -5
  378. data/lib/graphql/version.rb +2 -1
  379. data/lib/graphql.rb +37 -9
  380. data/readme.md +1 -1
  381. metadata +89 -5
  382. data/.yardopts +0 -5
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
 
3
4
  module GraphQL
4
5
  module Pagination
@@ -10,15 +11,33 @@ module GraphQL
10
11
  #
11
12
  # Unlike the previous connection implementation, these default to bidirectional pagination.
12
13
  #
13
- # Pagination arguments and context may be provided at initialization or assigned later (see {Schema::Field::ConnectionExtension}).
14
+ # Pagination arguments and context may be provided at initialization or assigned later (see [Schema::Field::ConnectionExtension](rdoc-ref:Schema::Field::ConnectionExtension)).
14
15
  class Connection
15
16
  class PaginationImplementationMissingError < GraphQL::Error
16
17
  end
17
18
 
18
- # @return [Object] A list object, from the application. This is the unpaginated value passed into the connection.
19
+ class << self
20
+ # Subclasses may override `cursor_for` to build their own cursors (for example, value-based cursors).
21
+ # In that case, position-based cursors from the parent class would be wrong, so `cursor_for_position`
22
+ # should call the overridden `cursor_for` instead.
23
+ #
24
+ # The result is cached on each class, since it's the same for every instance.
25
+ def custom_cursor_for?(default_owner) # :nodoc:
26
+ if !defined?(@custom_cursor_for)
27
+ @custom_cursor_for = instance_method(:cursor_for).owner != default_owner
28
+ end
29
+ @custom_cursor_for
30
+ end
31
+ end
32
+
33
+ # **Returns**
34
+ #
35
+ # - `Object` — A list object, from the application. This is the unpaginated value passed into the connection.
19
36
  attr_reader :items
20
37
 
21
- # @return [GraphQL::Query::Context]
38
+ # **Returns**
39
+ #
40
+ # - `GraphQL::Query::Context`
22
41
  attr_reader :context
23
42
 
24
43
  def context=(new_ctx)
@@ -29,13 +48,20 @@ module GraphQL
29
48
  @context
30
49
  end
31
50
 
32
- # @return [Object] the object this collection belongs to
51
+ # **Returns**
52
+ #
53
+ # - `Object` — the object this collection belongs to
33
54
  attr_accessor :parent
34
55
 
35
56
  # Raw access to client-provided values. (`max_page_size` not applied to first or last.)
36
57
  attr_accessor :before_value, :after_value, :first_value, :last_value
37
58
 
38
- # @return [String, nil] the client-provided cursor. `""` is treated as `nil`.
59
+ # **Returns**
60
+ #
61
+ # - `String, nil` — the client-provided cursor. `""` is treated as `nil`.
62
+ #
63
+ # :call-seq:
64
+ # before() -> String | nil
39
65
  def before
40
66
  if defined?(@before)
41
67
  @before
@@ -44,7 +70,12 @@ module GraphQL
44
70
  end
45
71
  end
46
72
 
47
- # @return [String, nil] the client-provided cursor. `""` is treated as `nil`.
73
+ # **Returns**
74
+ #
75
+ # - `String, nil` — the client-provided cursor. `""` is treated as `nil`.
76
+ #
77
+ # :call-seq:
78
+ # after() -> String | nil
48
79
  def after
49
80
  if defined?(@after)
50
81
  @after
@@ -53,19 +84,26 @@ module GraphQL
53
84
  end
54
85
  end
55
86
 
56
- # @return [Hash<Symbol => Object>] The field arguments from the field that returned this connection
87
+ # **Returns**
88
+ #
89
+ # - `Hash<Symbol => Object>` — The field arguments from the field that returned this connection
57
90
  attr_accessor :arguments
58
91
 
59
- # @param items [Object] some unpaginated collection item, like an `Array` or `ActiveRecord::Relation`
60
- # @param context [Query::Context]
61
- # @param parent [Object] The object this collection belongs to
62
- # @param first [Integer, nil] The limit parameter from the client, if it provided one
63
- # @param after [String, nil] A cursor for pagination, if the client provided one
64
- # @param last [Integer, nil] Limit parameter from the client, if provided
65
- # @param before [String, nil] A cursor for pagination, if the client provided one.
66
- # @param arguments [Hash] The arguments to the field that returned the collection wrapped by this connection
67
- # @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given and no `default_page_size` is set.
68
- # @param default_page_size [Integer, nil] A configured value to determine the result size when neither first or last are given.
92
+ # **Parameters**
93
+ #
94
+ # - `items` (`Object`) — some unpaginated collection item, like an `Array` or `ActiveRecord::Relation`
95
+ # - `context` (`Query::Context`)
96
+ # - `parent` (`Object`) — The object this collection belongs to
97
+ # - `first` (`Integer, nil`) — The limit parameter from the client, if it provided one
98
+ # - `after` (`String, nil`) — A cursor for pagination, if the client provided one
99
+ # - `last` (`Integer, nil`) — Limit parameter from the client, if provided
100
+ # - `before` (`String, nil`) — A cursor for pagination, if the client provided one.
101
+ # - `arguments` (`Hash`) — The arguments to the field that returned the collection wrapped by this connection
102
+ # - `max_page_size` (`Integer, nil`) — A configured value to cap the result size. Applied as `first` if neither first or last are given and no `default_page_size` is set.
103
+ # - `default_page_size` (`Integer, nil`) — A configured value to determine the result size when neither first or last are given.
104
+ #
105
+ # :call-seq:
106
+ # initialize(Object items, Object parent:, field:, Query::Context context:, Integer | nil first:, String | nil after:, Integer | nil max_page_size:, Integer | nil default_page_size:, Integer | nil last:, String | nil before:, edge_class:, Hash arguments:)
69
107
  def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, last: nil, before: nil, edge_class: nil, arguments: nil)
70
108
  @items = items
71
109
  @parent = parent
@@ -76,7 +114,7 @@ module GraphQL
76
114
  @last_value = last
77
115
  @before_value = before
78
116
  @arguments = arguments
79
- @edge_class = edge_class || self.class::Edge
117
+ @edge_class = edge_class || self.class.const_get(:Edge)
80
118
  # This is only true if the object was _initialized_ with an override
81
119
  # or if one is assigned later.
82
120
  @has_max_page_size_override = max_page_size != NOT_CONFIGURED
@@ -135,13 +173,12 @@ module GraphQL
135
173
  end
136
174
 
137
175
  attr_writer :first
138
- # @return [Integer, nil]
139
- # A clamped `first` value.
140
- # (The underlying instance variable doesn't have limits on it.)
141
- # If neither `first` nor `last` is given, but `default_page_size` is
142
- # present, default_page_size is used for first. If `default_page_size`
143
- # is greater than `max_page_size``, it'll be clamped down to
144
- # `max_page_size`. If `default_page_size` is nil, use `max_page_size`.
176
+ # **Returns**
177
+ #
178
+ # - `Integer, nil` — A clamped `first` value. (The underlying instance variable doesn't have limits on it.) If neither `first` nor `last` is given, but `default_page_size` is present, default_page_size is used for first. If `default_page_size` is greater than `max_page_size``, it'll be clamped down to `max_page_size`. If `default_page_size` is nil, use `max_page_size`.
179
+ #
180
+ # :call-seq:
181
+ # first() -> Integer | nil
145
182
  def first
146
183
  @first ||= begin
147
184
  capped = limit_pagination_argument(@first_value, max_page_size)
@@ -155,36 +192,72 @@ module GraphQL
155
192
  # This is called by `Relay::RangeAdd` -- it can be overridden
156
193
  # when `item` needs some modifications based on this connection's state.
157
194
  #
158
- # @param item [Object] An item newly added to `items`
159
- # @return [Edge]
195
+ # **Parameters**
196
+ #
197
+ # - `item` (`Object`) — An item newly added to `items`
198
+ #
199
+ # **Returns**
200
+ #
201
+ # - `Edge`
202
+ #
203
+ # :call-seq:
204
+ # range_add_edge(Object item) -> Edge
160
205
  def range_add_edge(item)
161
206
  edge_class.new(item, self)
162
207
  end
163
208
 
164
209
  attr_writer :last
165
- # @return [Integer, nil] A clamped `last` value. (The underlying instance variable doesn't have limits on it)
210
+ # **Returns**
211
+ #
212
+ # - `Integer, nil` — A clamped `last` value. (The underlying instance variable doesn't have limits on it)
213
+ #
214
+ # :call-seq:
215
+ # last() -> Integer | nil
166
216
  def last
167
217
  @last ||= limit_pagination_argument(@last_value, max_page_size)
168
218
  end
169
219
 
170
- # @return [Array<Edge>] {nodes}, but wrapped with Edge instances
220
+ # **Returns**
221
+ #
222
+ # - `Array<Edge>` — [nodes](rdoc-ref:nodes), but wrapped with Edge instances
223
+ #
224
+ # :call-seq:
225
+ # edges() -> Array[Edge]
171
226
  def edges
172
- @edges ||= nodes.map { |n| @edge_class.new(n, self) }
227
+ @edges ||= nodes.each_with_index.map do |n, index|
228
+ edge = @edge_class.new(n, self)
229
+ edge.graphql_connection_position = index
230
+ edge
231
+ end
173
232
  end
174
233
 
175
- # @return [Class] A wrapper class for edges of this connection
234
+ # **Returns**
235
+ #
236
+ # - `Class` — A wrapper class for edges of this connection
176
237
  attr_accessor :edge_class
177
238
 
178
- # @return [GraphQL::Schema::Field] The field this connection was returned by
239
+ # **Returns**
240
+ #
241
+ # - `GraphQL::Schema::Field` — The field this connection was returned by
179
242
  attr_accessor :field
243
+
244
+ # **Returns**
245
+ #
246
+ # - `GraphQL::Execution::Lookahead` Selections on this connection, set by connection fields
247
+ attr_accessor :lookahead
180
248
 
181
- # @return [Array<Object>] A slice of {items}, constrained by {@first_value}/{@after_value}/{@last_value}/{@before_value}
249
+ # **Returns**
250
+ #
251
+ # - `Array<Object>` — A slice of [items](rdoc-ref:items), constrained by `@first_value`/`@after_value`/`@last_value`/`@before_value`
252
+ #
253
+ # :call-seq:
254
+ # nodes() -> Array[Object]
182
255
  def nodes
183
256
  raise PaginationImplementationMissingError, "Implement #{self.class}#nodes to paginate `@items`"
184
257
  end
185
258
 
186
- # A dynamic alias for compatibility with {Relay::BaseConnection}.
187
- # @deprecated use {#nodes} instead
259
+ # A dynamic alias for compatibility with [GraphQL::Types::Relay::BaseConnection](rdoc-ref:GraphQL::Types::Relay::BaseConnection).
260
+ # **Deprecated:** use [nodes](rdoc-ref:#nodes) instead
188
261
  def edge_nodes
189
262
  nodes
190
263
  end
@@ -194,35 +267,77 @@ module GraphQL
194
267
  self
195
268
  end
196
269
 
197
- # @return [Boolean] True if there are more items after this page
270
+ # **Returns**
271
+ #
272
+ # - `Boolean` — True if there are more items after this page
273
+ #
274
+ # :call-seq:
275
+ # has_next_page() -> bool
198
276
  def has_next_page
199
277
  raise PaginationImplementationMissingError, "Implement #{self.class}#has_next_page to return the next-page check"
200
278
  end
201
279
 
202
- # @return [Boolean] True if there were items before these items
280
+ # **Returns**
281
+ #
282
+ # - `Boolean` — True if there were items before these items
283
+ #
284
+ # :call-seq:
285
+ # has_previous_page() -> bool
203
286
  def has_previous_page
204
287
  raise PaginationImplementationMissingError, "Implement #{self.class}#has_previous_page to return the previous-page check"
205
288
  end
206
289
 
207
- # @return [String] The cursor of the first item in {nodes}
290
+ # **Returns**
291
+ #
292
+ # - `String` — The cursor of the first item in [nodes](rdoc-ref:nodes)
293
+ #
294
+ # :call-seq:
295
+ # start_cursor() -> String
208
296
  def start_cursor
209
- nodes.first && cursor_for(nodes.first)
297
+ nodes.first && cursor_for_position(nodes.first, 0)
210
298
  end
211
299
 
212
- # @return [String] The cursor of the last item in {nodes}
300
+ # **Returns**
301
+ #
302
+ # - `String` — The cursor of the last item in [nodes](rdoc-ref:nodes)
303
+ #
304
+ # :call-seq:
305
+ # end_cursor() -> String
213
306
  def end_cursor
214
- nodes.last && cursor_for(nodes.last)
307
+ nodes.last && cursor_for_position(nodes.last, nodes.length - 1)
215
308
  end
216
309
 
217
310
  # Return a cursor for this item.
218
- # @param item [Object] one of the passed in {items}, taken from {nodes}
219
- # @return [String]
311
+ #
312
+ # **Parameters**
313
+ #
314
+ # - `item` (`Object`) — one of the passed in [items](rdoc-ref:items), taken from [nodes](rdoc-ref:nodes)
315
+ #
316
+ # **Returns**
317
+ #
318
+ # - `String`
319
+ #
320
+ # :call-seq:
321
+ # cursor_for(Object item) -> String
220
322
  def cursor_for(item)
221
323
  raise PaginationImplementationMissingError, "Implement #{self.class}#cursor_for(item) to return the cursor for #{item.inspect}"
222
324
  end
223
325
 
326
+ def cursor_for_position(item, _position) # :nodoc:
327
+ cursor_for(item)
328
+ end
329
+
224
330
  private
225
331
 
332
+ def cursor_index_for(item, collection)
333
+ @cursor_indexes ||= {}.compare_by_identity.tap do |indexes|
334
+ collection.each_with_index { |node, index| indexes[node] ||= index }
335
+ end
336
+ @cursor_indexes.fetch(item) do
337
+ raise GraphQL::ExecutionError, "Can't generate a cursor for an item outside this connection"
338
+ end
339
+ end
340
+
226
341
  def detect_was_authorized_by_scope_items
227
342
  if @context &&
228
343
  (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
@@ -233,9 +348,17 @@ module GraphQL
233
348
  end
234
349
  end
235
350
 
236
- # @param argument [nil, Integer] `first` or `last`, as provided by the client
237
- # @param max_page_size [nil, Integer]
238
- # @return [nil, Integer] `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size`
351
+ # **Parameters**
352
+ #
353
+ # - `argument` (`nil, Integer`) — `first` or `last`, as provided by the client
354
+ # - `max_page_size` (`nil, Integer`)
355
+ #
356
+ # **Returns**
357
+ #
358
+ # - `nil, Integer` — `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size`
359
+ #
360
+ # :call-seq:
361
+ # limit_pagination_argument(nil | Integer argument, nil | Integer max_page_size) -> nil | Integer
239
362
  def limit_pagination_argument(argument, max_page_size)
240
363
  if argument
241
364
  if argument < 0
@@ -255,11 +378,14 @@ module GraphQL
255
378
  context.schema.cursor_encoder.encode(cursor, nonce: true)
256
379
  end
257
380
 
258
- # A wrapper around paginated items. It includes a {cursor} for pagination
381
+ # A wrapper around paginated items. It includes a [cursor](rdoc-ref:#cursor) for pagination
259
382
  # and could be extended with custom relationship-level data.
260
383
  class Edge
261
384
  attr_reader :node
262
385
 
386
+ # The zero-based position of this edge in the page
387
+ attr_accessor :graphql_connection_position # :nodoc:
388
+
263
389
  def initialize(node, connection)
264
390
  @connection = connection
265
391
  @node = node
@@ -270,7 +396,11 @@ module GraphQL
270
396
  end
271
397
 
272
398
  def cursor
273
- @cursor ||= @connection.cursor_for(@node)
399
+ @cursor ||= if @graphql_connection_position
400
+ @connection.cursor_for_position(@node, @graphql_connection_position)
401
+ else
402
+ @connection.cursor_for(@node)
403
+ end
274
404
  end
275
405
 
276
406
  def was_authorized_by_scope_items?
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
 
3
4
  module GraphQL
4
5
  module Pagination
@@ -6,17 +7,25 @@ module GraphQL
6
7
  #
7
8
  # Attach as a plugin.
8
9
  #
9
- # @example Adding a custom wrapper
10
- # class MySchema < GraphQL::Schema
11
- # connections.add(MyApp::SearchResults, MyApp::SearchResultsConnection)
12
- # end
10
+ # See [Schema.connections](rdoc-ref:Schema.connections)
13
11
  #
14
- # @example Removing default connection support for arrays (they can still be manually wrapped)
15
- # class MySchema < GraphQL::Schema
16
- # connections.delete(Array)
17
- # end
12
+ # **Examples**
18
13
  #
19
- # @see {Schema.connections}
14
+ # **Example: Adding a custom wrapper**
15
+ #
16
+ # ```ruby
17
+ # class MySchema < GraphQL::Schema
18
+ # connections.add(MyApp::SearchResults, MyApp::SearchResultsConnection)
19
+ # end
20
+ # ```
21
+ #
22
+ # **Example: Removing default connection support for arrays (they can still be manually wrapped)**
23
+ #
24
+ # ```ruby
25
+ # class MySchema < GraphQL::Schema
26
+ # connections.delete(Array)
27
+ # end
28
+ # ```
20
29
  class Connections
21
30
  class ImplementationMissingError < GraphQL::Error
22
31
  end
@@ -46,7 +55,7 @@ module GraphQL
46
55
  end
47
56
 
48
57
  def wrapper_for(items, wrappers: all_wrappers)
49
- impl = nil
58
+ impl = nil #: untyped
50
59
 
51
60
  items.class.ancestors.each { |cls|
52
61
  impl = wrappers[cls]
@@ -57,8 +66,7 @@ module GraphQL
57
66
  end
58
67
 
59
68
  # Used by the runtime to wrap values in connection wrappers.
60
- # @api Private
61
- def wrap(field, parent, items, arguments, context)
69
+ def wrap(field, parent, items, arguments, context) # :nodoc:
62
70
  return items if GraphQL::Execution::Interpreter::RawValue === items
63
71
  wrappers = context ? context.namespace(:connections)[:all_wrappers] : all_wrappers
64
72
  impl = wrapper_for(items, wrappers: wrappers)
@@ -114,8 +122,7 @@ module GraphQL
114
122
  end
115
123
  end
116
124
  # use an override if there is one
117
- # @api private
118
- def edge_class_for_field(field)
125
+ def edge_class_for_field(field) # :nodoc:
119
126
  conn_type = field.type.unwrap
120
127
  conn_type_edge_type = conn_type.respond_to?(:edge_class) && conn_type.edge_class
121
128
  if conn_type_edge_type && conn_type_edge_type != Pagination::Connection::Edge
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
  require "graphql/pagination/relation_connection"
3
4
 
4
5
  module GraphQL
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
  require "graphql/pagination/connection"
3
4
  require "monitor"
4
5
 
@@ -29,6 +30,12 @@ module GraphQL
29
30
  end
30
31
 
31
32
  def has_next_page
33
+ # If the query loads the page anyway, load it now so its extra row answers this
34
+ if @has_next_page.nil? && peek_next_page? &&
35
+ (lookahead.selects?(:nodes) || lookahead.selects?(:edges) ||
36
+ page_info_selection.selects?(:start_cursor) || page_info_selection.selects?(:end_cursor))
37
+ load_nodes
38
+ end
32
39
  if @has_next_page.nil?
33
40
  @has_next_page = if before_offset && before_offset > 0
34
41
  true
@@ -47,48 +54,110 @@ module GraphQL
47
54
 
48
55
  def cursor_for(item)
49
56
  load_nodes
50
- # index in nodes + existing offset + 1 (because it's offset, not index)
51
- offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0) - (relation_offset(items) || 0)
52
- encode(offset.to_s)
57
+ position = cursor_index_for(item, nodes)
58
+ offset_cursor_for_position(position)
59
+ end
60
+
61
+ def cursor_for_position(item, position) # :nodoc:
62
+ if self.class.custom_cursor_for?(RelationConnection)
63
+ super
64
+ else
65
+ offset_cursor_for_position(position)
66
+ end
53
67
  end
54
68
 
55
69
  private
56
70
 
57
- # @param relation [Object] A database query object
58
- # @param _initial_offset [Integer] The number of items already excluded from the relation
59
- # @param size [Integer] The value against which we check the relation size
60
- # @return [Boolean] True if the number of items in this relation is larger than `size`
71
+ def offset_cursor_for_position(position)
72
+ # position in nodes + existing offset + 1 (because it's offset, not index)
73
+ offset = position + 1 + (@paged_nodes_offset || 0) - (relation_offset(items) || 0)
74
+ encode(offset.to_s)
75
+ end
76
+
77
+ # **Parameters**
78
+ #
79
+ # - `relation` (`Object`) — A database query object
80
+ # - `_initial_offset` (`Integer`) — The number of items already excluded from the relation
81
+ # - `size` (`Integer`) — The value against which we check the relation size
82
+ #
83
+ # **Returns**
84
+ #
85
+ # - `Boolean` — True if the number of items in this relation is larger than `size`
86
+ #
87
+ # :call-seq:
88
+ # relation_larger_than(Object relation, Integer _initial_offset, Integer size) -> bool
61
89
  def relation_larger_than(relation, _initial_offset, size)
62
90
  relation_count(set_limit(relation, size + 1)) == size + 1
63
91
  end
64
92
 
65
- # @param relation [Object] A database query object
66
- # @return [Integer, nil] The offset value, or nil if there isn't one
93
+ # **Parameters**
94
+ #
95
+ # - `relation` (`Object`) — A database query object
96
+ #
97
+ # **Returns**
98
+ #
99
+ # - `Integer, nil` — The offset value, or nil if there isn't one
100
+ #
101
+ # :call-seq:
102
+ # relation_offset(Object relation) -> Integer | nil
67
103
  def relation_offset(relation)
68
104
  raise "#{self.class}#relation_offset(relation) must return the offset value for a #{relation.class} (#{relation.inspect})"
69
105
  end
70
106
 
71
- # @param relation [Object] A database query object
72
- # @return [Integer, nil] The limit value, or nil if there isn't one
107
+ # **Parameters**
108
+ #
109
+ # - `relation` (`Object`) — A database query object
110
+ #
111
+ # **Returns**
112
+ #
113
+ # - `Integer, nil` — The limit value, or nil if there isn't one
114
+ #
115
+ # :call-seq:
116
+ # relation_limit(Object relation) -> Integer | nil
73
117
  def relation_limit(relation)
74
118
  raise "#{self.class}#relation_limit(relation) must return the limit value for a #{relation.class} (#{relation.inspect})"
75
119
  end
76
120
 
77
- # @param relation [Object] A database query object
78
- # @return [Integer, nil] The number of items in this relation (hopefully determined without loading all records into memory!)
121
+ # **Parameters**
122
+ #
123
+ # - `relation` (`Object`) — A database query object
124
+ #
125
+ # **Returns**
126
+ #
127
+ # - `Integer, nil` — The number of items in this relation (hopefully determined without loading all records into memory!)
128
+ #
129
+ # :call-seq:
130
+ # relation_count(Object relation) -> Integer | nil
79
131
  def relation_count(relation)
80
132
  raise "#{self.class}#relation_count(relation) must return the count of records for a #{relation.class} (#{relation.inspect})"
81
133
  end
82
134
 
83
- # @param relation [Object] A database query object
84
- # @return [Object] A modified query object which will return no records
135
+ # **Parameters**
136
+ #
137
+ # - `relation` (`Object`) — A database query object
138
+ #
139
+ # **Returns**
140
+ #
141
+ # - `Object` — A modified query object which will return no records
142
+ #
143
+ # :call-seq:
144
+ # null_relation(Object relation) -> Object
85
145
  def null_relation(relation)
86
146
  raise "#{self.class}#null_relation(relation) must return an empty relation for a #{relation.class} (#{relation.inspect})"
87
147
  end
88
148
 
89
- # @return [Integer]
149
+ # **Returns**
150
+ #
151
+ # - `Integer`
152
+ #
153
+ # :call-seq:
154
+ # offset_from_cursor(cursor) -> Integer
90
155
  def offset_from_cursor(cursor)
91
- decode(cursor).to_i
156
+ offset = Integer(decode(cursor), 10, exception: false) #: Integer?
157
+ if offset.nil? || offset <= 0
158
+ raise GraphQL::ExecutionError, "Invalid cursor: #{cursor.inspect}"
159
+ end
160
+ offset
92
161
  end
93
162
 
94
163
  # Abstract this operation so we can always ignore inputs less than zero.
@@ -166,12 +235,22 @@ module GraphQL
166
235
  end
167
236
  end
168
237
 
169
- # @return [Integer, nil]
238
+ # **Returns**
239
+ #
240
+ # - `Integer, nil`
241
+ #
242
+ # :call-seq:
243
+ # before_offset() -> Integer | nil
170
244
  def before_offset
171
245
  @before_offset ||= before && offset_from_cursor(before)
172
246
  end
173
247
 
174
- # @return [Integer, nil]
248
+ # **Returns**
249
+ #
250
+ # - `Integer, nil`
251
+ #
252
+ # :call-seq:
253
+ # after_offset() -> Integer | nil
175
254
  def after_offset
176
255
  @after_offset ||= after && offset_from_cursor(after)
177
256
  end
@@ -230,7 +309,7 @@ module GraphQL
230
309
  end
231
310
 
232
311
  def async_dataloader?
233
- @context&.[](:dataloader).is_a?(GraphQL::Dataloader::AsyncDataloader)
312
+ @context&.dataloader.is_a?(GraphQL::Dataloader::AsyncDataloader)
234
313
  end
235
314
 
236
315
  # Load nodes after applying first/last/before/after,
@@ -242,12 +321,34 @@ module GraphQL
242
321
  # `AsyncDataloader` may resolve sibling fields (eg, `edges` and `pageInfo`)
243
322
  # in separate Fibers, so several callers can get here before `@nodes` is set.
244
323
  (@load_monitor ||= Monitor.new).synchronize do
245
- @nodes ||= limited_nodes.to_a
324
+ # Not `||=`: Sorbet assumes `@nodes` is still falsy in this block
325
+ @nodes = load_page unless @nodes
246
326
  end
247
327
  else
248
- @nodes = limited_nodes.to_a
328
+ @nodes = load_page
249
329
  end
250
330
  end
331
+
332
+ # Fetch one row past `first` so `has_next_page` needs no extra query.
333
+ # Skips arrays (no query to save) and relations limited below `first` (no next page).
334
+ def load_page
335
+ page = limited_nodes
336
+ return page.to_a if page.is_a?(Array) || !peek_next_page? || relation_limit(page) != first
337
+
338
+ rows = set_limit(page, first + 1).to_a
339
+ @has_next_page = rows.size > first
340
+ rows.first(first)
341
+ end
342
+
343
+ # Peek on forward pages that select `hasNextPage`; `last` and `before` keep the separate check
344
+ def peek_next_page?
345
+ return @peek_next_page if defined?(@peek_next_page)
346
+ @peek_next_page = first&.positive? && last.nil? && before.nil? && page_info_selection.selects?(:has_next_page)
347
+ end
348
+
349
+ def page_info_selection
350
+ @page_info_selection ||= (lookahead || GraphQL::Execution::Lookahead::NULL_LOOKAHEAD).selection(:page_info)
351
+ end
251
352
  end
252
353
  end
253
354
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
  require "graphql/pagination/relation_connection"
3
4
 
4
5
  module GraphQL
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
  require "graphql/pagination/array_connection"
3
4
  require "graphql/pagination/active_record_relation_connection"
4
5
  require "graphql/pagination/connections"
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
  module GraphQL
3
4
  class ParseError < GraphQL::Error
4
5
  attr_reader :line, :col, :query