graphql 1.6.8 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +5 -0
  3. data/lib/graphql/analysis/analyze_query.rb +21 -17
  4. data/lib/graphql/argument.rb +6 -2
  5. data/lib/graphql/backtrace.rb +50 -0
  6. data/lib/graphql/backtrace/inspect_result.rb +51 -0
  7. data/lib/graphql/backtrace/table.rb +120 -0
  8. data/lib/graphql/backtrace/traced_error.rb +55 -0
  9. data/lib/graphql/backtrace/tracer.rb +50 -0
  10. data/lib/graphql/enum_type.rb +1 -10
  11. data/lib/graphql/execution.rb +1 -2
  12. data/lib/graphql/execution/execute.rb +98 -89
  13. data/lib/graphql/execution/flatten.rb +40 -0
  14. data/lib/graphql/execution/lazy/resolve.rb +7 -7
  15. data/lib/graphql/execution/multiplex.rb +29 -29
  16. data/lib/graphql/field.rb +5 -1
  17. data/lib/graphql/internal_representation/node.rb +16 -0
  18. data/lib/graphql/invalid_name_error.rb +11 -0
  19. data/lib/graphql/language/parser.rb +11 -5
  20. data/lib/graphql/language/parser.y +11 -5
  21. data/lib/graphql/name_validator.rb +16 -0
  22. data/lib/graphql/object_type.rb +5 -0
  23. data/lib/graphql/query.rb +28 -7
  24. data/lib/graphql/query/context.rb +155 -52
  25. data/lib/graphql/query/literal_input.rb +36 -9
  26. data/lib/graphql/query/null_context.rb +7 -1
  27. data/lib/graphql/query/result.rb +63 -0
  28. data/lib/graphql/query/serial_execution/field_resolution.rb +3 -4
  29. data/lib/graphql/query/serial_execution/value_resolution.rb +3 -4
  30. data/lib/graphql/query/variables.rb +1 -1
  31. data/lib/graphql/schema.rb +31 -0
  32. data/lib/graphql/schema/traversal.rb +16 -1
  33. data/lib/graphql/schema/warden.rb +40 -4
  34. data/lib/graphql/static_validation/validator.rb +20 -18
  35. data/lib/graphql/subscriptions.rb +129 -0
  36. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +122 -0
  37. data/lib/graphql/subscriptions/event.rb +52 -0
  38. data/lib/graphql/subscriptions/instrumentation.rb +68 -0
  39. data/lib/graphql/tracing.rb +80 -0
  40. data/lib/graphql/tracing/active_support_notifications_tracing.rb +31 -0
  41. data/lib/graphql/version.rb +1 -1
  42. data/readme.md +1 -1
  43. data/spec/graphql/analysis/analyze_query_spec.rb +19 -0
  44. data/spec/graphql/argument_spec.rb +28 -0
  45. data/spec/graphql/backtrace_spec.rb +144 -0
  46. data/spec/graphql/define/assign_argument_spec.rb +12 -0
  47. data/spec/graphql/enum_type_spec.rb +1 -1
  48. data/spec/graphql/execution/execute_spec.rb +66 -0
  49. data/spec/graphql/execution/lazy_spec.rb +4 -3
  50. data/spec/graphql/language/parser_spec.rb +16 -0
  51. data/spec/graphql/object_type_spec.rb +14 -0
  52. data/spec/graphql/query/context_spec.rb +134 -27
  53. data/spec/graphql/query/result_spec.rb +29 -0
  54. data/spec/graphql/query/variables_spec.rb +13 -0
  55. data/spec/graphql/query_spec.rb +22 -0
  56. data/spec/graphql/schema/build_from_definition_spec.rb +2 -0
  57. data/spec/graphql/schema/traversal_spec.rb +70 -12
  58. data/spec/graphql/schema/warden_spec.rb +67 -1
  59. data/spec/graphql/schema_spec.rb +29 -0
  60. data/spec/graphql/static_validation/validator_spec.rb +16 -0
  61. data/spec/graphql/subscriptions_spec.rb +331 -0
  62. data/spec/graphql/tracing/active_support_notifications_tracing_spec.rb +57 -0
  63. data/spec/graphql/tracing_spec.rb +47 -0
  64. data/spec/spec_helper.rb +32 -0
  65. data/spec/support/star_wars/schema.rb +39 -0
  66. metadata +27 -4
  67. data/lib/graphql/execution/field_result.rb +0 -54
  68. data/lib/graphql/execution/selection_result.rb +0 -90
@@ -14,9 +14,22 @@ module GraphQL
14
14
  else
15
15
  case type
16
16
  when GraphQL::ScalarType
17
- type.coerce_input(ast_node, variables.context)
17
+ # TODO smell
18
+ # This gets used for plain values during subscriber.trigger
19
+ if variables
20
+ type.coerce_input(ast_node, variables.context)
21
+ else
22
+ type.coerce_isolated_input(ast_node)
23
+ end
18
24
  when GraphQL::EnumType
19
- type.coerce_input(ast_node.name, variables.context)
25
+ # TODO smell
26
+ # This gets used for plain values sometimes
27
+ v = ast_node.is_a?(GraphQL::Language::Nodes::Enum) ? ast_node.name : ast_node
28
+ if variables
29
+ type.coerce_input(v, variables.context)
30
+ else
31
+ type.coerce_isolated_input(v)
32
+ end
20
33
  when GraphQL::NonNullType
21
34
  LiteralInput.coerce(type.of_type, ast_node, variables)
22
35
  when GraphQL::ListType
@@ -26,7 +39,9 @@ module GraphQL
26
39
  [LiteralInput.coerce(type.of_type, ast_node, variables)]
27
40
  end
28
41
  when GraphQL::InputObjectType
29
- from_arguments(ast_node.arguments, type.arguments, variables)
42
+ # TODO smell: handling AST vs handling plain Ruby
43
+ next_args = ast_node.is_a?(Hash) ? ast_node : ast_node.arguments
44
+ from_arguments(next_args, type.arguments, variables)
30
45
  end
31
46
  end
32
47
  end
@@ -43,7 +58,14 @@ module GraphQL
43
58
  # Variables is nil when making .defaults_for
44
59
  context = variables ? variables.context : nil
45
60
  values_hash = {}
46
- indexed_arguments = ast_arguments.each_with_object({}) { |a, memo| memo[a.name] = a }
61
+ indexed_arguments = case ast_arguments
62
+ when Hash
63
+ ast_arguments
64
+ when Array
65
+ ast_arguments.each_with_object({}) { |a, memo| memo[a.name] = a }
66
+ else
67
+ raise ArgumentError, "Unexpected ast_arguments: #{ast_arguments}"
68
+ end
47
69
 
48
70
  argument_defns.each do |arg_name, arg_defn|
49
71
  ast_arg = indexed_arguments[arg_name]
@@ -51,12 +73,17 @@ module GraphQL
51
73
  # If the value is a variable,
52
74
  # only add a value if the variable is actually present.
53
75
  # Otherwise, coerce the value in the AST, prepare the value and add it.
54
- if ast_arg
55
- value_is_a_variable = ast_arg.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
76
+ #
77
+ # TODO: since indexed_arguments can come from a plain Ruby hash,
78
+ # have to check for `false` or `nil` as hash values. This is getting smelly :S
79
+ if indexed_arguments.key?(arg_name)
80
+ arg_value = ast_arg.is_a?(GraphQL::Language::Nodes::Argument) ? ast_arg.value : ast_arg
81
+
82
+ value_is_a_variable = arg_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
56
83
 
57
- if (!value_is_a_variable || (value_is_a_variable && variables.key?(ast_arg.value.name)))
84
+ if (!value_is_a_variable || (value_is_a_variable && variables.key?(arg_value.name)))
58
85
 
59
- value = coerce(arg_defn.type, ast_arg.value, variables)
86
+ value = coerce(arg_defn.type, arg_value, variables)
60
87
  value = arg_defn.prepare(value, context)
61
88
 
62
89
  if value.is_a?(GraphQL::ExecutionError)
@@ -64,7 +91,7 @@ module GraphQL
64
91
  raise value
65
92
  end
66
93
 
67
- values_hash[ast_arg.name] = value
94
+ values_hash[arg_name] = value
68
95
  end
69
96
  end
70
97
 
@@ -3,12 +3,18 @@ module GraphQL
3
3
  class Query
4
4
  # This object can be `ctx` in places where there is no query
5
5
  class NullContext
6
+ class NullWarden < GraphQL::Schema::Warden
7
+ def visible?(t); true; end
8
+ def visible_field?(t); true; end
9
+ def visible_type?(t); true; end
10
+ end
11
+
6
12
  attr_reader :schema, :query, :warden
7
13
 
8
14
  def initialize
9
15
  @query = nil
10
16
  @schema = GraphQL::Schema.new
11
- @warden = GraphQL::Schema::Warden.new(
17
+ @warden = NullWarden.new(
12
18
  GraphQL::Filter.new,
13
19
  context: self,
14
20
  schema: @schema,
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Query
5
+ # A result from {Schema#execute}.
6
+ # It provides the requested data and
7
+ # access to the {Query} and {Query::Context}.
8
+ class Result
9
+ extend GraphQL::Delegate
10
+
11
+ def initialize(query:, values:)
12
+ @query = query
13
+ @to_h = values
14
+ end
15
+
16
+ # @return [GraphQL::Query] The query that was executed
17
+ attr_reader :query
18
+
19
+ # @return [Hash] The resulting hash of "data" and/or "errors"
20
+ attr_reader :to_h
21
+
22
+ def_delegators :@query, :context, :mutation?, :query?, :subscription?
23
+
24
+ def_delegators :@to_h, :[], :keys, :values, :to_json, :as_json
25
+
26
+ # Delegate any hash-like method to the underlying hash.
27
+ def method_missing(method_name, *args, &block)
28
+ if @to_h.respond_to?(method_name)
29
+ @to_h.public_send(method_name, *args, &block)
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ def respond_to_missing?(method_name, include_private = false)
36
+ @to_h.respond_to?(method_name) || super
37
+ end
38
+
39
+ def inspect
40
+ "#<GraphQL::Query::Result @query=... @to_h=#{@to_h} >"
41
+ end
42
+
43
+ # A result is equal to another object when:
44
+ #
45
+ # - The other object is a Hash whose value matches `result.to_h`
46
+ # - The other object is a Result whose value matches `result.to_h`
47
+ #
48
+ # (The query is ignored for comparing result equality.)
49
+ #
50
+ # @return [Boolean]
51
+ def ==(other)
52
+ case other
53
+ when Hash
54
+ @to_h == other
55
+ when Query::Result
56
+ @to_h == other.to_h
57
+ else
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -12,11 +12,10 @@ module GraphQL
12
12
  @target = target
13
13
  @query = query_ctx.query
14
14
  @field = irep_node.definition
15
- @field_ctx = query_ctx.spawn(
15
+ @field_ctx = query_ctx.spawn_child(
16
16
  key: irep_node.name,
17
- selection: selection,
18
- parent_type: parent_type,
19
- field: field,
17
+ object: target,
18
+ irep_node: irep_node,
20
19
  )
21
20
  @arguments = @query.arguments_for(irep_node, @field)
22
21
  end
@@ -23,11 +23,10 @@ module GraphQL
23
23
  result = []
24
24
  i = 0
25
25
  value.each do |inner_value|
26
- inner_ctx = query_ctx.spawn(
26
+ inner_ctx = query_ctx.spawn_child(
27
27
  key: i,
28
- selection: selection,
29
- parent_type: wrapped_type,
30
- field: field_defn,
28
+ object: inner_value,
29
+ irep_node: selection,
31
30
  )
32
31
 
33
32
  result << resolve(
@@ -44,7 +44,7 @@ module GraphQL
44
44
  end
45
45
  end
46
46
 
47
- def_delegators :@storage, :length, :key?, :[], :fetch
47
+ def_delegators :@storage, :length, :key?, :[], :fetch, :to_h
48
48
  end
49
49
  end
50
50
  end
@@ -81,6 +81,10 @@ module GraphQL
81
81
  :cursor_encoder,
82
82
  :raise_definition_error
83
83
 
84
+ # Single, long-lived instance of the provided subscriptions class, if there is one.
85
+ # @return [GraphQL::Subscriptions]
86
+ attr_accessor :subscriptions
87
+
84
88
  # @return [MiddlewareChain] MiddlewareChain which is applied to fields during execution
85
89
  attr_accessor :middleware
86
90
 
@@ -212,6 +216,14 @@ module GraphQL
212
216
  end
213
217
  end
214
218
 
219
+ # @return [Array<GraphQL::BaseType>] The root types of this schema
220
+ def root_types
221
+ @root_types ||= begin
222
+ rebuild_artifacts
223
+ @root_types
224
+ end
225
+ end
226
+
215
227
  # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
216
228
  # @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema
217
229
  def types
@@ -221,6 +233,22 @@ module GraphQL
221
233
  end
222
234
  end
223
235
 
236
+ # Returns a list of Arguments and Fields referencing a certain type
237
+ # @param type_name [String]
238
+ # @return [Hash]
239
+ def references_to(type_name)
240
+ rebuild_artifacts unless defined?(@type_reference_map)
241
+ @type_reference_map.fetch(type_name, [])
242
+ end
243
+
244
+ # Returns a list of Union types in which a type is a member
245
+ # @param type [GraphQL::ObjectType]
246
+ # @return [Array<GraphQL::UnionType>] list of union types of which the type is a member
247
+ def union_memberships(type)
248
+ rebuild_artifacts unless defined?(@union_memberships)
249
+ @union_memberships.fetch(type.name, [])
250
+ end
251
+
224
252
  # Execute a query on itself. Raises an error if the schema definition is invalid.
225
253
  # @see {Query#initialize} for arguments.
226
254
  # @return [Hash] query result, ready to be serialized as JSON
@@ -544,7 +572,10 @@ module GraphQL
544
572
  @rebuilding_artifacts = true
545
573
  traversal = Traversal.new(self)
546
574
  @types = traversal.type_map
575
+ @root_types = [query, mutation, subscription]
547
576
  @instrumented_field_map = traversal.instrumented_field_map
577
+ @type_reference_map = traversal.type_reference_map
578
+ @union_memberships = traversal.union_memberships
548
579
  end
549
580
  ensure
550
581
  @rebuilding_artifacts = false
@@ -10,6 +10,12 @@ module GraphQL
10
10
  # @return [Hash<String => Hash<String => GraphQL::Field>>]
11
11
  attr_reader :instrumented_field_map
12
12
 
13
+ # @return [Hash<String => Array<GraphQL::Field || GraphQL::Argument || GraphQL::Directive>]
14
+ attr_reader :type_reference_map
15
+
16
+ # @return [Hash<String => Array<GraphQL::BaseType>]
17
+ attr_reader :union_memberships
18
+
13
19
  # @param schema [GraphQL::Schema]
14
20
  def initialize(schema, introspection: true)
15
21
  @schema = schema
@@ -21,6 +27,8 @@ module GraphQL
21
27
 
22
28
  @type_map = {}
23
29
  @instrumented_field_map = Hash.new { |h, k| h[k] = {} }
30
+ @type_reference_map = Hash.new { |h, k| h[k] = [] }
31
+ @union_memberships = Hash.new { |h, k| h[k] = [] }
24
32
  visit(schema, nil)
25
33
  end
26
34
 
@@ -40,6 +48,7 @@ module GraphQL
40
48
  visit_roots.each { |t| visit(t, t.name) }
41
49
  when GraphQL::Directive
42
50
  member.arguments.each do |name, argument|
51
+ @type_reference_map[argument.type.unwrap.to_s] << argument
43
52
  visit(argument.type, "Directive argument #{member.name}.#{name}")
44
53
  end
45
54
  when GraphQL::BaseType
@@ -56,9 +65,13 @@ module GraphQL
56
65
  when GraphQL::InterfaceType
57
66
  visit_fields(type_defn)
58
67
  when GraphQL::UnionType
59
- type_defn.possible_types.each { |t| visit(t, "Possible type for #{type_defn.name}") }
68
+ type_defn.possible_types.each do |t|
69
+ @union_memberships[t.name] << type_defn
70
+ visit(t, "Possible type for #{type_defn.name}")
71
+ end
60
72
  when GraphQL::InputObjectType
61
73
  type_defn.arguments.each do |name, arg|
74
+ @type_reference_map[arg.type.unwrap.to_s] << arg
62
75
  visit(arg.type, "Input field #{type_defn.name}.#{name}")
63
76
  end
64
77
  end
@@ -78,8 +91,10 @@ module GraphQL
78
91
  inst.instrument(type_defn, defn)
79
92
  end
80
93
  @instrumented_field_map[type_defn.name][instrumented_field_defn.name] = instrumented_field_defn
94
+ @type_reference_map[instrumented_field_defn.type.unwrap.name] << instrumented_field_defn
81
95
  visit(instrumented_field_defn.type, "Field #{type_defn.name}.#{instrumented_field_defn.name}'s return type")
82
96
  instrumented_field_defn.arguments.each do |name, arg|
97
+ @type_reference_map[arg.type.unwrap.to_s] << arg
83
98
  visit(arg.type, "Argument #{name} on #{type_defn.name}.#{instrumented_field_defn.name}")
84
99
  end
85
100
  end
@@ -45,14 +45,14 @@ module GraphQL
45
45
 
46
46
  # @return [Array<GraphQL::BaseType>] Visible types in the schema
47
47
  def types
48
- @types ||= @schema.types.each_value.select { |t| visible?(t) }
48
+ @types ||= @schema.types.each_value.select { |t| visible_type?(t) }
49
49
  end
50
50
 
51
51
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
52
52
  def get_type(type_name)
53
53
  @visible_types ||= read_through do |name|
54
54
  type_defn = @schema.types.fetch(name, nil)
55
- if type_defn && visible?(type_defn)
55
+ if type_defn && visible_type?(type_defn)
56
56
  type_defn
57
57
  else
58
58
  nil
@@ -81,7 +81,7 @@ module GraphQL
81
81
 
82
82
  # @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
83
83
  def possible_types(type_defn)
84
- @visible_possible_types ||= read_through { |type_defn| @schema.possible_types(type_defn).select { |t| visible?(t) } }
84
+ @visible_possible_types ||= read_through { |type_defn| @schema.possible_types(type_defn).select { |t| visible_type?(t) } }
85
85
  @visible_possible_types[type_defn]
86
86
  end
87
87
 
@@ -126,8 +126,44 @@ module GraphQL
126
126
 
127
127
  private
128
128
 
129
+ def union_memberships(obj_type)
130
+ @unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } }
131
+ @unions[obj_type]
132
+ end
133
+
129
134
  def visible_field?(field_defn)
130
- visible?(field_defn) && visible?(field_defn.type.unwrap)
135
+ visible?(field_defn) && visible_type?(field_defn.type.unwrap)
136
+ end
137
+
138
+ def visible_type?(type_defn)
139
+ visible?(type_defn) && (
140
+ root_type?(type_defn) ||
141
+ type_defn.introspection? ||
142
+ referenced?(type_defn) ||
143
+ visible_abstract_type?(type_defn) ||
144
+ possible_types?(type_defn)
145
+ )
146
+ end
147
+
148
+ def root_type?(type_defn)
149
+ @schema.root_types.include?(type_defn)
150
+ end
151
+
152
+ def referenced?(type_defn)
153
+ members = @schema.references_to(type_defn.unwrap.name)
154
+ members.any? { |m| visible?(m) }
155
+ end
156
+
157
+ def visible_abstract_type?(type_defn)
158
+ type_defn.kind.object? && (
159
+ interfaces(type_defn).any? ||
160
+ union_memberships(type_defn).any?
161
+ )
162
+ end
163
+
164
+ def possible_types?(type_defn)
165
+ (type_defn.kind.union? || type_defn.kind.interface?) &&
166
+ @schema.possible_types(type_defn).any? { |t| visible_type?(t) }
131
167
  end
132
168
 
133
169
  def visible?(member)
@@ -22,29 +22,31 @@ module GraphQL
22
22
  # @param query [GraphQL::Query]
23
23
  # @return [Array<Hash>]
24
24
  def validate(query, validate: true)
25
- context = GraphQL::StaticValidation::ValidationContext.new(query)
26
- rewrite = GraphQL::InternalRepresentation::Rewrite.new
25
+ GraphQL::Tracing.trace("validate", { validate: validate, query: query }) do
26
+ context = GraphQL::StaticValidation::ValidationContext.new(query)
27
+ rewrite = GraphQL::InternalRepresentation::Rewrite.new
27
28
 
28
- # Put this first so its enters and exits are always called
29
- rewrite.validate(context)
29
+ # Put this first so its enters and exits are always called
30
+ rewrite.validate(context)
30
31
 
31
- # If the caller opted out of validation, don't attach these
32
- if validate
33
- @rules.each do |rules|
34
- rules.new.validate(context)
32
+ # If the caller opted out of validation, don't attach these
33
+ if validate
34
+ @rules.each do |rules|
35
+ rules.new.validate(context)
36
+ end
35
37
  end
36
- end
37
38
 
38
- context.visitor.visit
39
- # Post-validation: allow validators to register handlers on rewritten query nodes
40
- rewrite_result = rewrite.document
41
- GraphQL::InternalRepresentation::Visit.visit_each_node(rewrite_result.operation_definitions, context.each_irep_node_handlers)
39
+ context.visitor.visit
40
+ # Post-validation: allow validators to register handlers on rewritten query nodes
41
+ rewrite_result = rewrite.document
42
+ GraphQL::InternalRepresentation::Visit.visit_each_node(rewrite_result.operation_definitions, context.each_irep_node_handlers)
42
43
 
43
- {
44
- errors: context.errors,
45
- # If there were errors, the irep is garbage
46
- irep: context.errors.any? ? nil : rewrite_result,
47
- }
44
+ {
45
+ errors: context.errors,
46
+ # If there were errors, the irep is garbage
47
+ irep: context.errors.any? ? nil : rewrite_result,
48
+ }
49
+ end
48
50
  end
49
51
  end
50
52
  end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/subscriptions/event"
3
+ require "graphql/subscriptions/instrumentation"
4
+ if defined?(ActionCable)
5
+ require "graphql/subscriptions/action_cable_subscriptions"
6
+ end
7
+
8
+ module GraphQL
9
+ class Subscriptions
10
+ def self.use(defn, options = {})
11
+ schema = defn.target
12
+ options[:schema] = schema
13
+ schema.subscriptions = self.new(options)
14
+ instrumentation = Subscriptions::Instrumentation.new(schema: schema)
15
+ defn.instrument(:field, instrumentation)
16
+ defn.instrument(:query, instrumentation)
17
+ nil
18
+ end
19
+
20
+ def initialize(kwargs)
21
+ @schema = kwargs[:schema]
22
+ end
23
+
24
+ # Fetch subscriptions matching this field + arguments pair
25
+ # And pass them off to the queue.
26
+ # @param event_name [String]
27
+ # @param args [Hash]
28
+ # @param object [Object]
29
+ # @param scope [Symbol, String]
30
+ # @return [void]
31
+ def trigger(event_name, args, object, scope: nil)
32
+ field = @schema.get_field("Subscription", event_name)
33
+ if !field
34
+ raise "No subscription matching trigger: #{event_name}"
35
+ end
36
+
37
+ event = Subscriptions::Event.new(
38
+ name: event_name,
39
+ arguments: args,
40
+ field: field,
41
+ scope: scope,
42
+ )
43
+ execute_all(event, object)
44
+ end
45
+
46
+ def initialize(schema:, **rest)
47
+ @schema = schema
48
+ end
49
+
50
+ # `event` was triggered on `object`, and `subscription_id` was subscribed,
51
+ # so it should be updated.
52
+ #
53
+ # Load `subscription_id`'s GraphQL data, re-evaluate the query, and deliver the result.
54
+ #
55
+ # This is where a queue may be inserted to push updates in the background.
56
+ #
57
+ # @param subscription_id [String]
58
+ # @param event [GraphQL::Subscriptions::Event] The event which was triggered
59
+ # @param object [Object] The value for the subscription field
60
+ # @return [void]
61
+ def execute(subscription_id, event, object)
62
+ # Lookup the saved data for this subscription
63
+ query_data = read_subscription(subscription_id)
64
+ # Fetch the required keys from the saved data
65
+ query_string = query_data.fetch(:query_string)
66
+ variables = query_data.fetch(:variables)
67
+ context = query_data.fetch(:context)
68
+ operation_name = query_data.fetch(:operation_name)
69
+ # Re-evaluate the saved query
70
+ result = @schema.execute(
71
+ {
72
+ query: query_string,
73
+ context: context,
74
+ subscription_topic: event.topic,
75
+ operation_name: operation_name,
76
+ variables: variables,
77
+ root_value: object,
78
+ }
79
+ )
80
+ deliver(subscription_id, result)
81
+ end
82
+
83
+ # Event `event` occurred on `object`,
84
+ # Update all subscribers.
85
+ # @param event [Subscriptions::Event]
86
+ # @param object [Object]
87
+ # @return [void]
88
+ def execute_all(event, object)
89
+ each_subscription_id(event) do |subscription_id|
90
+ execute(subscription_id, event, object)
91
+ end
92
+ end
93
+
94
+ # Get each `subscription_id` subscribed to `event.topic` and yield them
95
+ # @param event [GraphQL::Subscriptions::Event]
96
+ # @yieldparam subscription_id [String]
97
+ # @return [void]
98
+ def each_subscription_id(event)
99
+ raise NotImplementedError
100
+ end
101
+
102
+ # The system wants to send an update to this subscription.
103
+ # Read its data and return it.
104
+ # @param subscription_id [String]
105
+ # @return [Hash] Containing required keys
106
+ def read_subscription(subscription_id)
107
+ raise NotImplementedError
108
+ end
109
+
110
+ # A subscription query was re-evaluated, returning `result`.
111
+ # The result should be send to `subscription_id`.
112
+ # @param subscription_id [String]
113
+ # @param result [Hash]
114
+ # @param context [GraphQL::Query::Context]
115
+ # @return [void]
116
+ def deliver(subscription_id, result, context)
117
+ raise NotImplementedError
118
+ end
119
+
120
+ # `query` was executed and found subscriptions to `events`.
121
+ # Update the database to reflect this new state.
122
+ # @param query [GraphQL::Query]
123
+ # @param events [Array<GraphQL::Subscriptions::Event>]
124
+ # @return [void]
125
+ def write_subscription(query, events)
126
+ raise NotImplementedError
127
+ end
128
+ end
129
+ end