graphql 2.0.31 → 2.6.1

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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  4. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  5. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  6. data/lib/generators/graphql/install_generator.rb +49 -0
  7. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  8. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  9. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  10. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  11. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  12. data/lib/generators/graphql/templates/base_field.erb +2 -0
  13. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  14. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  15. data/lib/generators/graphql/templates/base_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  17. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  18. data/lib/generators/graphql/templates/base_union.erb +2 -0
  19. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +5 -0
  26. data/lib/generators/graphql/type_generator.rb +1 -1
  27. data/lib/graphql/analysis/analyzer.rb +90 -0
  28. data/lib/graphql/analysis/field_usage.rb +82 -0
  29. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  30. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  31. data/lib/graphql/analysis/query_complexity.rb +263 -0
  32. data/lib/graphql/analysis/query_depth.rb +58 -0
  33. data/lib/graphql/analysis/visitor.rb +280 -0
  34. data/lib/graphql/analysis.rb +102 -1
  35. data/lib/graphql/autoload.rb +38 -0
  36. data/lib/graphql/backtrace/table.rb +118 -55
  37. data/lib/graphql/backtrace.rb +1 -19
  38. data/lib/graphql/coercion_error.rb +1 -9
  39. data/lib/graphql/current.rb +57 -0
  40. data/lib/graphql/dashboard/application_controller.rb +41 -0
  41. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  42. data/lib/graphql/dashboard/installable.rb +22 -0
  43. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  44. data/lib/graphql/dashboard/limiters.rb +93 -0
  45. data/lib/graphql/dashboard/operation_store.rb +199 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  47. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  48. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  49. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  50. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  51. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  52. data/lib/graphql/dashboard/statics/icon.png +0 -0
  53. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  54. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  68. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  69. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  70. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  71. data/lib/graphql/dashboard.rb +96 -0
  72. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  73. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  74. data/lib/graphql/dataloader/async_dataloader.rb +112 -0
  75. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  76. data/lib/graphql/dataloader/request.rb +5 -0
  77. data/lib/graphql/dataloader/source.rb +35 -12
  78. data/lib/graphql/dataloader.rb +224 -149
  79. data/lib/graphql/date_encoding_error.rb +1 -1
  80. data/lib/graphql/dig.rb +2 -1
  81. data/lib/graphql/duration_encoding_error.rb +16 -0
  82. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  83. data/lib/graphql/execution/finalize.rb +217 -0
  84. data/lib/graphql/execution/input_values.rb +261 -0
  85. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  86. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  87. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  88. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  89. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +228 -0
  90. data/lib/graphql/execution/interpreter/runtime.rb +365 -435
  91. data/lib/graphql/execution/interpreter.rb +87 -163
  92. data/lib/graphql/execution/lazy.rb +1 -1
  93. data/lib/graphql/execution/load_argument_step.rb +64 -0
  94. data/lib/graphql/execution/lookahead.rb +105 -31
  95. data/lib/graphql/execution/multiplex.rb +7 -6
  96. data/lib/graphql/execution/next.rb +90 -0
  97. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  98. data/lib/graphql/execution/runner.rb +410 -0
  99. data/lib/graphql/execution/selections_step.rb +91 -0
  100. data/lib/graphql/execution.rb +8 -4
  101. data/lib/graphql/execution_error.rb +17 -10
  102. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  103. data/lib/graphql/introspection/directive_type.rb +7 -3
  104. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  105. data/lib/graphql/introspection/entry_points.rb +20 -6
  106. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  107. data/lib/graphql/introspection/field_type.rb +13 -5
  108. data/lib/graphql/introspection/input_value_type.rb +21 -13
  109. data/lib/graphql/introspection/schema_type.rb +8 -11
  110. data/lib/graphql/introspection/type_type.rb +64 -28
  111. data/lib/graphql/invalid_name_error.rb +1 -1
  112. data/lib/graphql/invalid_null_error.rb +26 -17
  113. data/lib/graphql/language/block_string.rb +34 -18
  114. data/lib/graphql/language/cache.rb +13 -0
  115. data/lib/graphql/language/comment.rb +18 -0
  116. data/lib/graphql/language/definition_slice.rb +1 -1
  117. data/lib/graphql/language/document_from_schema_definition.rb +90 -61
  118. data/lib/graphql/language/lexer.rb +323 -193
  119. data/lib/graphql/language/nodes.rb +139 -77
  120. data/lib/graphql/language/parser.rb +807 -1985
  121. data/lib/graphql/language/printer.rb +324 -151
  122. data/lib/graphql/language/sanitized_printer.rb +21 -23
  123. data/lib/graphql/language/static_visitor.rb +171 -0
  124. data/lib/graphql/language/visitor.rb +62 -119
  125. data/lib/graphql/language.rb +71 -1
  126. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  127. data/lib/graphql/pagination/array_connection.rb +6 -6
  128. data/lib/graphql/pagination/connection.rb +30 -1
  129. data/lib/graphql/pagination/connections.rb +32 -0
  130. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  131. data/lib/graphql/query/context/scoped_context.rb +101 -0
  132. data/lib/graphql/query/context.rb +88 -144
  133. data/lib/graphql/query/null_context.rb +15 -18
  134. data/lib/graphql/query/partial.rb +194 -0
  135. data/lib/graphql/query/validation_pipeline.rb +4 -4
  136. data/lib/graphql/query/variable_validation_error.rb +1 -1
  137. data/lib/graphql/query/variables.rb +3 -3
  138. data/lib/graphql/query.rb +135 -81
  139. data/lib/graphql/railtie.rb +16 -6
  140. data/lib/graphql/rake_task.rb +3 -12
  141. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  142. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  143. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  144. data/lib/graphql/rubocop.rb +2 -0
  145. data/lib/graphql/runtime_error.rb +6 -0
  146. data/lib/graphql/schema/addition.rb +26 -13
  147. data/lib/graphql/schema/always_visible.rb +7 -2
  148. data/lib/graphql/schema/argument.rb +78 -14
  149. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  150. data/lib/graphql/schema/build_from_definition.rb +140 -66
  151. data/lib/graphql/schema/directive/flagged.rb +4 -2
  152. data/lib/graphql/schema/directive/one_of.rb +12 -0
  153. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  154. data/lib/graphql/schema/directive.rb +78 -12
  155. data/lib/graphql/schema/enum.rb +110 -27
  156. data/lib/graphql/schema/enum_value.rb +11 -3
  157. data/lib/graphql/schema/field/connection_extension.rb +4 -51
  158. data/lib/graphql/schema/field/scope_extension.rb +19 -7
  159. data/lib/graphql/schema/field.rb +245 -119
  160. data/lib/graphql/schema/field_extension.rb +12 -9
  161. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  162. data/lib/graphql/schema/input_object.rb +123 -65
  163. data/lib/graphql/schema/interface.rb +60 -16
  164. data/lib/graphql/schema/introspection_system.rb +8 -17
  165. data/lib/graphql/schema/late_bound_type.rb +4 -0
  166. data/lib/graphql/schema/list.rb +8 -4
  167. data/lib/graphql/schema/loader.rb +3 -4
  168. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -12
  169. data/lib/graphql/schema/member/has_arguments.rb +132 -100
  170. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  171. data/lib/graphql/schema/member/has_dataloader.rb +99 -0
  172. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  173. data/lib/graphql/schema/member/has_directives.rb +5 -5
  174. data/lib/graphql/schema/member/has_fields.rb +121 -17
  175. data/lib/graphql/schema/member/has_interfaces.rb +27 -13
  176. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  177. data/lib/graphql/schema/member/has_validators.rb +1 -1
  178. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  179. data/lib/graphql/schema/member/scoped.rb +19 -0
  180. data/lib/graphql/schema/member/type_system_helpers.rb +18 -5
  181. data/lib/graphql/schema/member/validates_input.rb +3 -3
  182. data/lib/graphql/schema/member.rb +6 -0
  183. data/lib/graphql/schema/mutation.rb +7 -0
  184. data/lib/graphql/schema/non_null.rb +1 -1
  185. data/lib/graphql/schema/object.rb +34 -8
  186. data/lib/graphql/schema/printer.rb +9 -7
  187. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  188. data/lib/graphql/schema/relay_classic_mutation.rb +6 -129
  189. data/lib/graphql/schema/resolver.rb +128 -32
  190. data/lib/graphql/schema/scalar.rb +4 -9
  191. data/lib/graphql/schema/subscription.rb +63 -12
  192. data/lib/graphql/schema/timeout.rb +19 -2
  193. data/lib/graphql/schema/type_expression.rb +2 -2
  194. data/lib/graphql/schema/union.rb +2 -2
  195. data/lib/graphql/schema/unique_within_type.rb +1 -1
  196. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  197. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  198. data/lib/graphql/schema/validator.rb +3 -1
  199. data/lib/graphql/schema/visibility/migration.rb +188 -0
  200. data/lib/graphql/schema/visibility/profile.rb +464 -0
  201. data/lib/graphql/schema/visibility/visit.rb +190 -0
  202. data/lib/graphql/schema/visibility.rb +311 -0
  203. data/lib/graphql/schema/warden.rb +275 -103
  204. data/lib/graphql/schema/wrapper.rb +7 -1
  205. data/lib/graphql/schema.rb +954 -212
  206. data/lib/graphql/static_validation/all_rules.rb +3 -3
  207. data/lib/graphql/static_validation/base_visitor.rb +96 -71
  208. data/lib/graphql/static_validation/literal_validator.rb +6 -7
  209. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +2 -2
  210. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  211. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +6 -2
  212. data/lib/graphql/static_validation/rules/directives_are_defined.rb +6 -3
  213. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  214. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +14 -3
  215. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +59 -15
  216. data/lib/graphql/static_validation/rules/fields_will_merge.rb +391 -262
  217. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  218. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +6 -6
  219. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +15 -2
  220. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  221. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  222. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  223. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  224. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  225. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +28 -8
  226. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  227. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  228. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  229. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  230. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  231. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  232. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +14 -2
  233. data/lib/graphql/static_validation/validation_context.rb +22 -6
  234. data/lib/graphql/static_validation/validator.rb +9 -1
  235. data/lib/graphql/static_validation.rb +0 -1
  236. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -5
  237. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  238. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +45 -19
  239. data/lib/graphql/subscriptions/event.rb +22 -4
  240. data/lib/graphql/subscriptions/serialize.rb +3 -1
  241. data/lib/graphql/subscriptions.rb +56 -17
  242. data/lib/graphql/testing/helpers.rb +161 -0
  243. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  244. data/lib/graphql/testing.rb +3 -0
  245. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  246. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  247. data/lib/graphql/tracing/appoptics_trace.rb +11 -3
  248. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  249. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  250. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  251. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  252. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  253. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  254. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  255. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  256. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  257. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  258. data/lib/graphql/tracing/legacy_hooks_trace.rb +75 -0
  259. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  260. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  261. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  262. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  263. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  264. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  265. data/lib/graphql/tracing/null_trace.rb +9 -0
  266. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  267. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  268. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  269. data/lib/graphql/tracing/platform_trace.rb +5 -0
  270. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  271. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +5 -1
  272. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  273. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  274. data/lib/graphql/tracing/scout_trace.rb +32 -55
  275. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  276. data/lib/graphql/tracing/sentry_trace.rb +82 -0
  277. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  278. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  279. data/lib/graphql/tracing/trace.rb +118 -1
  280. data/lib/graphql/tracing.rb +31 -28
  281. data/lib/graphql/type_kinds.rb +2 -1
  282. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  283. data/lib/graphql/types/relay/connection_behaviors.rb +45 -3
  284. data/lib/graphql/types/relay/edge_behaviors.rb +19 -1
  285. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  286. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  287. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  288. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  289. data/lib/graphql/types.rb +18 -10
  290. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  291. data/lib/graphql/unauthorized_error.rb +9 -1
  292. data/lib/graphql/version.rb +1 -1
  293. data/lib/graphql.rb +69 -54
  294. data/readme.md +12 -2
  295. metadata +236 -40
  296. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  297. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  298. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  299. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  300. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  301. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  302. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  303. data/lib/graphql/analysis/ast.rb +0 -81
  304. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  305. data/lib/graphql/backtrace/trace.rb +0 -96
  306. data/lib/graphql/backtrace/tracer.rb +0 -80
  307. data/lib/graphql/deprecation.rb +0 -9
  308. data/lib/graphql/filter.rb +0 -59
  309. data/lib/graphql/language/parser.y +0 -560
  310. data/lib/graphql/language/token.rb +0 -34
  311. data/lib/graphql/schema/base_64_bp.rb +0 -26
  312. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  313. data/lib/graphql/schema/null_mask.rb +0 -11
  314. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  315. data/lib/graphql/static_validation/type_stack.rb +0 -216
  316. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ module RactorShareable
5
+ def self.extended(schema_class)
6
+ schema_class.extend(SchemaExtension)
7
+ schema_class.freeze_schema
8
+ end
9
+
10
+ module SchemaExtension
11
+
12
+ def freeze_error_handlers(handlers)
13
+ handlers[:subclass_handlers].default_proc = nil
14
+ handlers[:subclass_handlers].each do |_class, subclass_handlers|
15
+ freeze_error_handlers(subclass_handlers)
16
+ end
17
+ Ractor.make_shareable(handlers)
18
+ end
19
+
20
+ def freeze_schema
21
+ # warm some ivars:
22
+ default_analysis_engine
23
+ default_execution_strategy
24
+ GraphQL.default_parser
25
+ default_logger
26
+ freeze_error_handlers(error_handlers)
27
+ # TODO: this freezes errors of parent classes which could cause trouble
28
+ parent_class = superclass
29
+ while parent_class.respond_to?(:error_handlers)
30
+ freeze_error_handlers(parent_class.error_handlers)
31
+ parent_class = parent_class.superclass
32
+ end
33
+
34
+ own_tracers.freeze
35
+ @frozen_tracers = tracers.freeze
36
+ own_trace_modes.each do |m|
37
+ trace_options_for(m)
38
+ build_trace_mode(m)
39
+ end
40
+ build_trace_mode(:default)
41
+ Ractor.make_shareable(@trace_options_for_mode)
42
+ Ractor.make_shareable(own_trace_modes)
43
+ Ractor.make_shareable(own_multiplex_analyzers)
44
+ @frozen_multiplex_analyzers = Ractor.make_shareable(multiplex_analyzers)
45
+ Ractor.make_shareable(own_query_analyzers)
46
+ @frozen_query_analyzers = Ractor.make_shareable(query_analyzers)
47
+ Ractor.make_shareable(own_plugins)
48
+ own_plugins.each do |(plugin, options)|
49
+ Ractor.make_shareable(plugin)
50
+ Ractor.make_shareable(options)
51
+ end
52
+ @frozen_plugins = Ractor.make_shareable(plugins)
53
+ Ractor.make_shareable(own_references_to)
54
+ @frozen_directives = Ractor.make_shareable(directives)
55
+
56
+ Ractor.make_shareable(visibility)
57
+ Ractor.make_shareable(introspection_system)
58
+ extend(FrozenMethods)
59
+
60
+ Ractor.make_shareable(self)
61
+ superclass.respond_to?(:freeze_schema) && superclass.freeze_schema
62
+ end
63
+
64
+ module FrozenMethods
65
+ def tracers; @frozen_tracers; end
66
+ def multiplex_analyzers; @frozen_multiplex_analyzers; end
67
+ def query_analyzers; @frozen_query_analyzers; end
68
+ def plugins; @frozen_plugins; end
69
+ def directives; @frozen_directives; end
70
+
71
+ # This actually accumulates info during execution...
72
+ # How to support it?
73
+ def lazy?(_obj); false; end
74
+ def sync_lazy(obj); obj; end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/types/string"
3
2
 
4
3
  module GraphQL
5
4
  class Schema
@@ -21,6 +20,10 @@ module GraphQL
21
20
  # @see {GraphQL::Schema::Mutation} for an example, it's basically the same.
22
21
  #
23
22
  class RelayClassicMutation < GraphQL::Schema::Mutation
23
+ include GraphQL::Schema::HasSingleInputArgument
24
+
25
+ argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
26
+
24
27
  # The payload should always include this field
25
28
  field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
26
29
  # Relay classic default:
@@ -31,34 +34,14 @@ module GraphQL
31
34
  def resolve_with_support(**inputs)
32
35
  input = inputs[:input].to_kwargs
33
36
 
34
- new_extras = field ? field.extras : []
35
- all_extras = self.class.extras + new_extras
36
-
37
- # Transfer these from the top-level hash to the
38
- # shortcutted `input:` object
39
- all_extras.each do |ext|
40
- # It's possible that the `extra` was not passed along by this point,
41
- # don't re-add it if it wasn't given here.
42
- if inputs.key?(ext)
43
- input[ext] = inputs[ext]
44
- end
45
- end
46
-
47
37
  if input
48
38
  # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
49
39
  input_kwargs = input.to_h
50
40
  client_mutation_id = input_kwargs.delete(:client_mutation_id)
51
- else
52
- # Relay Classic Mutations with no `argument`s
53
- # don't require `input:`
54
- input_kwargs = {}
41
+ inputs[:input] = input_kwargs
55
42
  end
56
43
 
57
- return_value = if input_kwargs.any?
58
- super(**input_kwargs)
59
- else
60
- super()
61
- end
44
+ return_value = super(**inputs)
62
45
 
63
46
  context.query.after_lazy(return_value) do |return_hash|
64
47
  # It might be an error
@@ -68,112 +51,6 @@ module GraphQL
68
51
  return_hash
69
52
  end
70
53
  end
71
-
72
- class << self
73
- def dummy
74
- @dummy ||= begin
75
- d = Class.new(GraphQL::Schema::Resolver)
76
- d.argument_class(self.argument_class)
77
- # TODO make this lazier?
78
- d.argument(:input, input_type, description: "Parameters for #{self.graphql_name}")
79
- d
80
- end
81
- end
82
-
83
- def field_arguments(context = GraphQL::Query::NullContext)
84
- dummy.arguments(context)
85
- end
86
-
87
- def get_field_argument(name, context = GraphQL::Query::NullContext)
88
- dummy.get_argument(name, context)
89
- end
90
-
91
- def own_field_arguments
92
- dummy.own_arguments
93
- end
94
-
95
- def all_field_argument_definitions
96
- dummy.all_argument_definitions
97
- end
98
-
99
- # Also apply this argument to the input type:
100
- def argument(*args, own_argument: false, **kwargs, &block)
101
- it = input_type # make sure any inherited arguments are already added to it
102
- arg = super(*args, **kwargs, &block)
103
-
104
- # This definition might be overriding something inherited;
105
- # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
106
- prev_args = it.own_arguments[arg.graphql_name]
107
- case prev_args
108
- when GraphQL::Schema::Argument
109
- if prev_args.owner != self
110
- it.own_arguments.delete(arg.graphql_name)
111
- end
112
- when Array
113
- prev_args.reject! { |a| a.owner != self }
114
- if prev_args.empty?
115
- it.own_arguments.delete(arg.graphql_name)
116
- end
117
- end
118
-
119
- it.add_argument(arg)
120
- arg
121
- end
122
-
123
- # The base class for generated input object types
124
- # @param new_class [Class] The base class to use for generating input object definitions
125
- # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
126
- def input_object_class(new_class = nil)
127
- if new_class
128
- @input_object_class = new_class
129
- end
130
- @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject)
131
- end
132
-
133
- # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type
134
- # @return [Class] The generated {Schema::InputObject} class for this mutation's `input`
135
- def input_type(new_input_type = nil)
136
- if new_input_type
137
- @input_type = new_input_type
138
- end
139
- @input_type ||= generate_input_type
140
- end
141
-
142
- private
143
-
144
- # Generate the input type for the `input:` argument
145
- # To customize how input objects are generated, override this method
146
- # @return [Class] a subclass of {.input_object_class}
147
- def generate_input_type
148
- mutation_args = all_argument_definitions
149
- mutation_class = self
150
- Class.new(input_object_class) do
151
- class << self
152
- def default_graphql_name
153
- "#{self.mutation.graphql_name}Input"
154
- end
155
-
156
- def description(new_desc = nil)
157
- super || "Autogenerated input type of #{self.mutation.graphql_name}"
158
- end
159
- end
160
- mutation(mutation_class)
161
- # these might be inherited:
162
- mutation_args.each do |arg|
163
- add_argument(arg)
164
- end
165
- argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
166
- end
167
- end
168
- end
169
-
170
- private
171
-
172
- def authorize_arguments(args, values)
173
- # remove the `input` wrapper to match values
174
- input_args = args["input"].type.unwrap.arguments(context)
175
- super(input_args, values)
176
- end
177
54
  end
178
55
  end
179
56
  end
@@ -8,6 +8,7 @@ module GraphQL
8
8
  # - Arguments, via `.argument(...)` helper, which will be applied to the field.
9
9
  # - Return type, via `.type(..., null: ...)`, which will be applied to the field.
10
10
  # - Description, via `.description(...)`, which will be applied to the field
11
+ # - Comment, via `.comment(...)`, which will be applied to the field
11
12
  # - Resolution, via `#resolve(**args)` method, which will be called to resolve the field.
12
13
  # - `#object` and `#context` accessors for use during `#resolve`.
13
14
  #
@@ -19,12 +20,16 @@ module GraphQL
19
20
  # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function`
20
21
  class Resolver
21
22
  include Schema::Member::GraphQLTypeNames
22
- # Really we only need description from here, but:
23
+ # Really we only need description & comment from here, but:
23
24
  extend Schema::Member::BaseDSLMethods
24
25
  extend GraphQL::Schema::Member::HasArguments
26
+ extend GraphQL::Schema::Member::HasAuthorization
25
27
  extend GraphQL::Schema::Member::HasValidators
26
28
  include Schema::Member::HasPath
27
29
  extend Schema::Member::HasPath
30
+ extend Schema::Member::HasDirectives
31
+ include Schema::Member::HasDataloader
32
+ extend Schema::Member::HasDeprecationReason
28
33
 
29
34
  # @param object [Object] The application object that this field is being resolved on
30
35
  # @param context [GraphQL::Query::Context]
@@ -35,26 +40,98 @@ module GraphQL
35
40
  @field = field
36
41
  # Since this hash is constantly rebuilt, cache it for this call
37
42
  @arguments_by_keyword = {}
38
- self.class.arguments(context).each do |name, arg|
43
+ context.types.arguments(self.class).each do |arg|
39
44
  @arguments_by_keyword[arg.keyword] = arg
40
45
  end
41
46
  @prepared_arguments = nil
42
47
  end
43
48
 
49
+ attr_accessor :exec_result, :exec_index, :field_resolve_step, :raw_arguments
50
+
44
51
  # @return [Object] The application object this field is being resolved on
45
- attr_reader :object
52
+ attr_accessor :object
46
53
 
47
54
  # @return [GraphQL::Query::Context]
48
55
  attr_reader :context
49
56
 
50
- # @return [GraphQL::Dataloader]
51
- def dataloader
52
- context.dataloader
53
- end
54
-
55
57
  # @return [GraphQL::Schema::Field]
56
58
  attr_reader :field
57
59
 
60
+ attr_writer :prepared_arguments
61
+
62
+ def call
63
+ if self.class < Schema::HasSingleInputArgument
64
+ @prepared_arguments = @prepared_arguments[:input]
65
+ end
66
+ q = context.query
67
+ trace_objs = [object]
68
+ q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
69
+ is_ready = ready?(**@prepared_arguments)
70
+ runner = @field_resolve_step.runner
71
+ if runner.resolves_lazies && runner.schema.lazy?(is_ready)
72
+ is_ready, new_return_value = runner.schema.sync_lazy(is_ready)
73
+ end
74
+
75
+ if is_ready.is_a?(Array)
76
+ is_ready, new_return_value = is_ready
77
+ if is_ready != false
78
+ raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{new_return_value.inspect}]"
79
+ else
80
+ new_return_value
81
+ end
82
+ end
83
+
84
+ if is_ready
85
+ begin
86
+ is_authed, new_return_value = authorized?(**@prepared_arguments)
87
+ rescue GraphQL::UnauthorizedError => err
88
+ new_return_value = q.schema.unauthorized_object(err)
89
+ is_authed = true # the error was handled
90
+ end
91
+ end
92
+
93
+ if runner.resolves_lazies && runner.schema.lazy?(is_authed)
94
+ is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
95
+ end
96
+
97
+ result = if is_authed
98
+ Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
99
+ if q.subscription? && @field.owner == context.schema.subscription
100
+ # This needs to use arguments without `loads:`. TODO extract this into subscription-related code somehow?
101
+ @original_arguments = @field_resolve_step.runner.input_values[q].argument_values(@field, @field_resolve_step.ast_node.arguments, nil)
102
+ end
103
+ call_resolve(@prepared_arguments)
104
+ elsif new_return_value.nil?
105
+ err = UnauthorizedFieldError.new(object: object, type: @field_resolve_step.parent_type, context: context, field: @field)
106
+ context.schema.unauthorized_field(err)
107
+ else
108
+ new_return_value
109
+ end
110
+ q = context.query
111
+ q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
112
+ exec_result[exec_index] = result
113
+ rescue GraphQL::UnauthorizedError => auth_err
114
+ exec_result[exec_index] = begin
115
+ context.schema.unauthorized_object(auth_err)
116
+ rescue GraphQL::ExecutionError => exec_err
117
+ exec_err
118
+ end
119
+ rescue GraphQL::RuntimeError => err
120
+ exec_result[exec_index] = err
121
+ rescue StandardError => stderr
122
+ exec_result[exec_index] = begin
123
+ context.query.handle_or_reraise(stderr)
124
+ rescue GraphQL::ExecutionError => ex_err
125
+ ex_err
126
+ end
127
+ ensure
128
+ field_pending_steps = field_resolve_step.pending_steps
129
+ field_pending_steps.delete(self)
130
+ if field_pending_steps.size == 0 && field_resolve_step.field_results
131
+ field_resolve_step.runner.add_step(field_resolve_step)
132
+ end
133
+ end
134
+
58
135
  def arguments
59
136
  @prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
60
137
  end
@@ -65,7 +142,7 @@ module GraphQL
65
142
  # @api private
66
143
  def resolve_with_support(**args)
67
144
  # First call the ready? hook which may raise
68
- raw_ready_val = if args.any?
145
+ raw_ready_val = if !args.empty?
69
146
  ready?(**args)
70
147
  else
71
148
  ready?
@@ -86,7 +163,7 @@ module GraphQL
86
163
  @prepared_arguments = loaded_args
87
164
  Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
88
165
  # Then call `authorized?`, which may raise or may return a lazy object
89
- raw_authorized_val = if loaded_args.any?
166
+ raw_authorized_val = if !loaded_args.empty?
90
167
  authorized?(**loaded_args)
91
168
  else
92
169
  authorized?
@@ -103,11 +180,7 @@ module GraphQL
103
180
  end
104
181
  elsif authorized_val
105
182
  # Finally, all the hooks have passed, so resolve it
106
- if loaded_args.any?
107
- public_send(self.class.resolve_method, **loaded_args)
108
- else
109
- public_send(self.class.resolve_method)
110
- end
183
+ call_resolve(loaded_args)
111
184
  else
112
185
  raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
113
186
  end
@@ -117,6 +190,15 @@ module GraphQL
117
190
  end
118
191
  end
119
192
 
193
+ # @api private {GraphQL::Schema::Mutation} uses this to clear the dataloader cache
194
+ def call_resolve(args_hash)
195
+ if !args_hash.empty?
196
+ public_send(self.class.resolve_method, **args_hash)
197
+ else
198
+ public_send(self.class.resolve_method)
199
+ end
200
+ end
201
+
120
202
  # Do the work. Everything happens here.
121
203
  # @return [Object] An object corresponding to the return type
122
204
  def resolve(**args)
@@ -146,10 +228,14 @@ module GraphQL
146
228
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
147
229
  def authorized?(**inputs)
148
230
  arg_owner = @field # || self.class
149
- args = arg_owner.arguments(context)
231
+ args = context.types.arguments(arg_owner)
150
232
  authorize_arguments(args, inputs)
151
233
  end
152
234
 
235
+ def self.authorizes?(context)
236
+ self.instance_method(:authorized?).owner != GraphQL::Schema::Resolver
237
+ end
238
+
153
239
  # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
154
240
  #
155
241
  # By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
@@ -163,19 +249,22 @@ module GraphQL
163
249
  private
164
250
 
165
251
  def authorize_arguments(args, inputs)
166
- args.each_value do |argument|
252
+ args.each do |argument|
167
253
  arg_keyword = argument.keyword
168
254
  if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
169
- arg_auth, err = argument.authorized?(self, arg_value, context)
170
- if !arg_auth
171
- return arg_auth, err
172
- else
173
- true
255
+ auth_result = argument.authorized?(self, arg_value, context)
256
+ if auth_result.is_a?(Array)
257
+ # only return this second value if the application returned a second value
258
+ arg_auth, err = auth_result
259
+ if !arg_auth
260
+ return arg_auth, err
261
+ end
262
+ elsif auth_result == false
263
+ return auth_result
174
264
  end
175
- else
176
- true
177
265
  end
178
266
  end
267
+ true
179
268
  end
180
269
 
181
270
  def load_arguments(args)
@@ -198,23 +287,27 @@ module GraphQL
198
287
  end
199
288
 
200
289
  # Avoid returning a lazy if none are needed
201
- if prepare_lazies.any?
290
+ if !prepare_lazies.empty?
202
291
  GraphQL::Execution::Lazy.all(prepare_lazies).then { prepared_args }
203
292
  else
204
293
  prepared_args
205
294
  end
206
295
  end
207
296
 
208
- def get_argument(name, context = GraphQL::Query::NullContext)
297
+ def get_argument(name, context = GraphQL::Query::NullContext.instance)
209
298
  self.class.get_argument(name, context)
210
299
  end
211
300
 
212
301
  class << self
213
- def field_arguments(context = GraphQL::Query::NullContext)
302
+ def field_arguments(context = GraphQL::Query::NullContext.instance)
214
303
  arguments(context)
215
304
  end
216
305
 
217
- def get_field_argument(name, context = GraphQL::Query::NullContext)
306
+ def any_field_arguments?
307
+ any_arguments?
308
+ end
309
+
310
+ def get_field_argument(name, context = GraphQL::Query::NullContext.instance)
218
311
  get_argument(name, context)
219
312
  end
220
313
 
@@ -380,7 +473,7 @@ module GraphQL
380
473
  if superclass.respond_to?(:extensions)
381
474
  s_exts = superclass.extensions
382
475
  if own_exts
383
- if s_exts.any?
476
+ if !s_exts.empty?
384
477
  own_exts + s_exts
385
478
  else
386
479
  own_exts
@@ -393,11 +486,14 @@ module GraphQL
393
486
  end
394
487
  end
395
488
 
489
+ def inherited(child_class)
490
+ child_class.description(description)
491
+ super
492
+ end
493
+
396
494
  private
397
495
 
398
- def own_extensions
399
- @own_extensions
400
- end
496
+ attr_reader :own_extensions
401
497
  end
402
498
  end
403
499
  end
@@ -19,9 +19,9 @@ module GraphQL
19
19
 
20
20
  def specified_by_url(new_url = nil)
21
21
  if new_url
22
- @specified_by_url = new_url
23
- elsif defined?(@specified_by_url)
24
- @specified_by_url
22
+ directive(GraphQL::Schema::Directive::SpecifiedBy, url: new_url)
23
+ elsif (directive = directives.find { |dir| dir.graphql_name == "specifiedBy" })
24
+ directive.arguments[:url] # rubocop:disable Development/ContextIsPassedCop
25
25
  elsif superclass.respond_to?(:specified_by_url)
26
26
  superclass.specified_by_url
27
27
  else
@@ -50,12 +50,7 @@ module GraphQL
50
50
  end
51
51
 
52
52
  if coerced_result.nil?
53
- str_value = if value == Float::INFINITY
54
- ""
55
- else
56
- " #{GraphQL::Language.serialize(value)}"
57
- end
58
- Query::InputValidationResult.from_problem("Could not coerce value#{str_value} to #{graphql_name}")
53
+ Query::InputValidationResult.from_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{graphql_name}")
59
54
  elsif coerced_result.is_a?(GraphQL::CoercionError)
60
55
  Query::InputValidationResult.from_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions)
61
56
  else
@@ -15,33 +15,47 @@ module GraphQL
15
15
  extend GraphQL::Schema::Resolver::HasPayloadType
16
16
  extend GraphQL::Schema::Member::HasFields
17
17
  NO_UPDATE = :no_update
18
- # The generated payload type is required; If there's no payload,
19
- # propagate null.
20
18
  null false
21
19
 
20
+ # @api private
22
21
  def initialize(object:, context:, field:)
23
22
  super
24
23
  # Figure out whether this is an update or an initial subscription
25
24
  @mode = context.query.subscription_update? ? :update : :subscribe
25
+ @subscription_written = false
26
+ @original_arguments = nil
27
+ if (subs_ns = context.namespace(:subscriptions)) &&
28
+ (sub_insts = subs_ns[:subscriptions])
29
+ sub_insts[context.current_path] = self
30
+ end
26
31
  end
27
32
 
33
+ # @api private
28
34
  def resolve_with_support(**args)
35
+ @original_arguments = args # before `loads:` have been run
29
36
  result = nil
30
37
  unsubscribed = true
31
- catch :graphql_subscription_unsubscribed do
38
+ unsubscribed_result = catch :graphql_subscription_unsubscribed do
32
39
  result = super
33
40
  unsubscribed = false
34
41
  end
35
42
 
36
43
 
37
44
  if unsubscribed
38
- context.skip
45
+ if unsubscribed_result
46
+ context.namespace(:subscriptions)[:final_update] = true
47
+ unsubscribed_result
48
+ else
49
+ context.skip
50
+ end
39
51
  else
40
52
  result
41
53
  end
42
54
  end
43
55
 
44
- # Implement the {Resolve} API
56
+ # Implement the {Resolve} API.
57
+ # You can implement this if you want code to run for _both_ the initial subscription
58
+ # and for later updates. Or, implement {#subscribe} and {#update}
45
59
  def resolve(**args)
46
60
  # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
47
61
  # have an unexpected `@mode`
@@ -49,8 +63,9 @@ module GraphQL
49
63
  end
50
64
 
51
65
  # Wrap the user-defined `#subscribe` hook
66
+ # @api private
52
67
  def resolve_subscribe(**args)
53
- ret_val = args.any? ? subscribe(**args) : subscribe
68
+ ret_val = !args.empty? ? subscribe(**args) : subscribe
54
69
  if ret_val == :no_response
55
70
  context.skip
56
71
  else
@@ -66,8 +81,9 @@ module GraphQL
66
81
  end
67
82
 
68
83
  # Wrap the user-provided `#update` hook
84
+ # @api private
69
85
  def resolve_update(**args)
70
- ret_val = args.any? ? update(**args) : update
86
+ ret_val = !args.empty? ? update(**args) : update
71
87
  if ret_val == NO_UPDATE
72
88
  context.namespace(:subscriptions)[:no_update] = true
73
89
  context.skip
@@ -94,19 +110,20 @@ module GraphQL
94
110
  end
95
111
 
96
112
  # Call this to halt execution and remove this subscription from the system
97
- def unsubscribe
113
+ # @param update_value [Object] if present, deliver this update before unsubscribing
114
+ # @return [void]
115
+ def unsubscribe(update_value = nil)
98
116
  context.namespace(:subscriptions)[:unsubscribed] = true
99
- throw :graphql_subscription_unsubscribed
117
+ throw :graphql_subscription_unsubscribed, update_value
100
118
  end
101
119
 
102
- READING_SCOPE = ::Object.new
103
120
  # Call this method to provide a new subscription_scope; OR
104
121
  # call it without an argument to get the subscription_scope
105
122
  # @param new_scope [Symbol]
106
123
  # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
107
124
  # @return [Symbol]
108
- def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
109
- if new_scope != READING_SCOPE
125
+ def self.subscription_scope(new_scope = NOT_CONFIGURED, optional: false)
126
+ if new_scope != NOT_CONFIGURED
110
127
  @subscription_scope = new_scope
111
128
  @subscription_scope_optional = optional
112
129
  elsif defined?(@subscription_scope)
@@ -143,6 +160,40 @@ module GraphQL
143
160
  def self.topic_for(arguments:, field:, scope:)
144
161
  Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
145
162
  end
163
+
164
+ # Calls through to `schema.subscriptions` to register this subscription with the backend.
165
+ # This is automatically called by GraphQL-Ruby after a query finishes successfully,
166
+ # but if you need to commit the subscription during `#subscribe`, you can call it there.
167
+ # (This method also sets a flag showing that this subscription was already written.)
168
+ #
169
+ # If you call this method yourself, you may also need to {#unsubscribe}
170
+ # or call `subscriptions.delete_subscription` to clean up the database if the query crashes with an error
171
+ # later in execution.
172
+ # @return [void]
173
+ def write_subscription
174
+ if subscription_written?
175
+ raise GraphQL::Error, "`write_subscription` was called but `#{self.class}#subscription_written?` is already true. Remove a call to `write subscription`."
176
+ else
177
+ @subscription_written = true
178
+ context.schema.subscriptions.write_subscription(context.query, [event])
179
+ end
180
+ nil
181
+ end
182
+
183
+ # @return [Boolean] `true` if {#write_subscription} was called already
184
+ def subscription_written?
185
+ @subscription_written
186
+ end
187
+
188
+ # @return [Subscriptions::Event] This object is used as a representation of this subscription for the backend
189
+ def event
190
+ @event ||= Subscriptions::Event.new(
191
+ name: field.name,
192
+ arguments: @original_arguments,
193
+ context: context,
194
+ field: field,
195
+ )
196
+ end
146
197
  end
147
198
  end
148
199
  end